From 6ca062bf86d69c783f7acf8e9ddad7247772c128 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Sat, 20 Jun 2026 20:23:23 -0700 Subject: [PATCH] [V5] `openbb-platform-api`: Refactor for V5 Spec, Config, Packaging, Test Coverage (#7494) * stash some changes * add more robust testing * mypy * point PR at V5 * introduce spec file * codespell * test fix * fix workflow environment setup * fix workflow environment setup * fix workflow environment setup * add pyyaml to dependencies * split lint jobs * fix workflow environment setup * fix workflow environment setup * workflow env setup * workflow env setup * clean up code comments * add auth hook entrypoints * codespell * add codegen feature * codespell * move _unpack into dispatchers for consistency with codegen packages * surface nested models in the response * fix missing coverage in CI * socrata updates * test fix * detect plotly output * add --include and --exclude flags from generate-extension command * refactor openbb-platform-api for V5 operations and spec file handling * delete poetry.lock * add test workflow * add temp branch id for test workflow * add httpx to dev dependencies * add a conftest.py * tweak conftest * tweak conftest * python 3.10 issues * more python 3.10 * more python 3.10 * more python 3.10 * more python 3.10 * fix url template resolution * multi-spec support * fix a windows path issue * cap test matrix at python 3.14 * no 3.15 runners for platform-api * no useless comments * platform controller command description split * merge URL overloads from path params * exclude none and unset from model dump * markdown lint * ignore get/post endpoints that are return SSE * fix remaining fastapi 0.137 fallout --------- Co-authored-by: deeleeramone <> Co-authored-by: Copilot --- .github/workflows/test-unit-platform-api.yml | 89 + openbb_platform/core/openbb/.build.lock | 2 +- .../core/openbb/assets/reference.json | 29889 +++++++++++++++- .../core/openbb/package/__init__.py | 3 +- .../extensions/platform_api/.coveragerc | 24 + .../extensions/platform_api/README.md | 334 +- .../openbb_platform_api/app/__init__.py | 25 + .../openbb_platform_api/app/app.py | 435 + .../openbb_platform_api/app/args.py | 456 + .../openbb_platform_api/app/bootstrap.py | 229 + .../openbb_platform_api/app/config.py | 350 + .../openbb_platform_api/app/middleware.py | 185 + .../openbb_platform_api/app/spec.py | 906 + .../platform_api/openbb_platform_api/main.py | 394 +- .../openbb_platform_api/models/__init__.py | 20 + .../openbb_platform_api/models/query.py | 67 + .../openbb_platform_api/models/response.py | 328 + .../openbb_platform_api/query_models.py | 66 +- .../openbb_platform_api/response_models.py | 304 +- .../openbb_platform_api/service/__init__.py | 31 + .../service/agents_service.py | 69 + .../service/apps_service.py | 59 + .../service/widgets_service.py | 214 + .../openbb_platform_api/utils/__init__.py | 15 +- .../openbb_platform_api/utils/api.py | 503 +- .../openbb_platform_api/utils/merge_agents.py | 63 +- .../openbb_platform_api/utils/merge_apps.py | 54 +- .../utils/merge_widgets.py | 33 +- .../openbb_platform_api/utils/network.py | 53 + .../openbb_platform_api/utils/openapi.py | 280 +- .../openbb_platform_api/utils/widgets.py | 82 +- .../extensions/platform_api/pyproject.toml | 138 +- .../tests/{test_api.py => app/test_app.py} | 114 +- .../platform_api/tests/app/test_app_routes.py | 1049 + .../platform_api/tests/app/test_args.py | 370 + .../platform_api/tests/app/test_bootstrap.py | 403 + .../platform_api/tests/app/test_config.py | 553 + .../platform_api/tests/app/test_middleware.py | 267 + .../platform_api/tests/app/test_spec.py | 2127 ++ .../extensions/platform_api/tests/conftest.py | 21 + .../platform_api/tests/models/test_query.py | 65 + .../tests/models/test_response.py | 245 + .../tests/service/test_agents_service.py | 171 + .../tests/service/test_apps_service.py | 123 + .../tests/service/test_widgets_service.py | 588 + .../platform_api/tests/test_compat_shims.py | 152 + .../platform_api/tests/test_merge_agents.py | 55 - .../platform_api/tests/test_merge_apps.py | 55 - .../platform_api/tests/test_merge_widgets.py | 136 - .../platform_api/tests/test_openapi_utils.py | 585 - .../platform_api/tests/test_widgets_utils.py | 508 - .../tests/{ => utils}/mock_openapi.json | 0 .../tests/{ => utils}/mock_widgets.json | 45 +- .../tests/utils/test_merge_widgets.py | 290 + .../platform_api/tests/utils/test_openapi.py | 2946 ++ .../platform_api/tests/utils/test_widgets.py | 1961 + .../extensions/platform_api/uv.lock | 3163 ++ 57 files changed, 48798 insertions(+), 2894 deletions(-) create mode 100644 .github/workflows/test-unit-platform-api.yml create mode 100644 openbb_platform/extensions/platform_api/.coveragerc create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/app/__init__.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/app/app.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/app/args.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/app/bootstrap.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/app/config.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/app/middleware.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/app/spec.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/models/__init__.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/models/query.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/models/response.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/service/__init__.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/service/agents_service.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/service/apps_service.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/service/widgets_service.py create mode 100644 openbb_platform/extensions/platform_api/openbb_platform_api/utils/network.py rename openbb_platform/extensions/platform_api/tests/{test_api.py => app/test_app.py} (91%) create mode 100644 openbb_platform/extensions/platform_api/tests/app/test_app_routes.py create mode 100644 openbb_platform/extensions/platform_api/tests/app/test_args.py create mode 100644 openbb_platform/extensions/platform_api/tests/app/test_bootstrap.py create mode 100644 openbb_platform/extensions/platform_api/tests/app/test_config.py create mode 100644 openbb_platform/extensions/platform_api/tests/app/test_middleware.py create mode 100644 openbb_platform/extensions/platform_api/tests/app/test_spec.py create mode 100644 openbb_platform/extensions/platform_api/tests/conftest.py create mode 100644 openbb_platform/extensions/platform_api/tests/models/test_query.py create mode 100644 openbb_platform/extensions/platform_api/tests/models/test_response.py create mode 100644 openbb_platform/extensions/platform_api/tests/service/test_agents_service.py create mode 100644 openbb_platform/extensions/platform_api/tests/service/test_apps_service.py create mode 100644 openbb_platform/extensions/platform_api/tests/service/test_widgets_service.py create mode 100644 openbb_platform/extensions/platform_api/tests/test_compat_shims.py delete mode 100644 openbb_platform/extensions/platform_api/tests/test_merge_agents.py delete mode 100644 openbb_platform/extensions/platform_api/tests/test_merge_apps.py delete mode 100644 openbb_platform/extensions/platform_api/tests/test_merge_widgets.py delete mode 100644 openbb_platform/extensions/platform_api/tests/test_openapi_utils.py delete mode 100644 openbb_platform/extensions/platform_api/tests/test_widgets_utils.py rename openbb_platform/extensions/platform_api/tests/{ => utils}/mock_openapi.json (100%) rename openbb_platform/extensions/platform_api/tests/{ => utils}/mock_widgets.json (99%) create mode 100644 openbb_platform/extensions/platform_api/tests/utils/test_merge_widgets.py create mode 100644 openbb_platform/extensions/platform_api/tests/utils/test_openapi.py create mode 100644 openbb_platform/extensions/platform_api/tests/utils/test_widgets.py create mode 100644 openbb_platform/extensions/platform_api/uv.lock diff --git a/.github/workflows/test-unit-platform-api.yml b/.github/workflows/test-unit-platform-api.yml new file mode 100644 index 00000000000..30d0e774de0 --- /dev/null +++ b/.github/workflows/test-unit-platform-api.yml @@ -0,0 +1,89 @@ +name: 🛰️ openbb-platform-api + +on: + pull_request: + branches: + - develop + - v5 + - feature/cli-2 + paths: + - 'openbb_platform/extensions/platform_api/**' + - 'openbb_platform/core/**' + - 'openbb_platform/extensions/devtools/**' + - '.github/workflows/test-unit-platform-api.yml' + merge_group: + types: [checks_requested] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + working-directory: openbb_platform/extensions/platform_api + +jobs: + test: + name: Test (${{ matrix.os }} / Python ${{ matrix.python_version }}) + runs-on: ${{ matrix.os }} + env: + UV_CACHE_DIR: ${{ github.workspace }}/.uv-cache + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + # Linux: full sweep across every supported Python. + - { os: ubuntu-latest, python_version: "3.10" } + - { os: ubuntu-latest, python_version: "3.11" } + - { os: ubuntu-latest, python_version: "3.12" } + - { os: ubuntu-latest, python_version: "3.13" } + - { os: ubuntu-latest, python_version: "3.14" } + # macOS and Windows: min and max supported. + - { os: macos-latest, python_version: "3.10" } + - { os: macos-latest, python_version: "3.14" } + - { os: windows-latest, python_version: "3.10" } + - { os: windows-latest, python_version: "3.14" } + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Install Python ${{ matrix.python_version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python_version }} + allow-prereleases: true + cache: "pip" + + - name: Cache uv (built wheels) + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/.uv-cache + key: uv-${{ runner.os }}-py${{ matrix.python_version }}-${{ hashFiles('openbb_platform/extensions/platform_api/pyproject.toml', 'openbb_platform/extensions/platform_api/uv.lock', 'openbb_platform/core/pyproject.toml', 'openbb_platform/extensions/devtools/pyproject.toml') }} + restore-keys: | + uv-${{ runner.os }}-py${{ matrix.python_version }}- + + - name: Install openbb-platform-api + run: | + python -m pip install --upgrade pip uv + python -m uv pip install --system -e . --group dev + + - name: Run unit tests + run: | + python -m pytest tests \ + --cov=openbb_platform_api \ + --cov-config=.coveragerc \ + --cov-report=term-missing \ + --cov-report=xml:coverage-unit.xml + + - name: Upload unit-test coverage artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: openbb-platform-api-coverage-unit-${{ matrix.os }}-py${{ matrix.python_version }} + path: openbb_platform/extensions/platform_api/coverage-unit.xml diff --git a/openbb_platform/core/openbb/.build.lock b/openbb_platform/core/openbb/.build.lock index ad44b1ade28..0c3fdca90b6 100644 --- a/openbb_platform/core/openbb/.build.lock +++ b/openbb_platform/core/openbb/.build.lock @@ -1 +1 @@ -16825 \ No newline at end of file +7295 \ No newline at end of file diff --git a/openbb_platform/core/openbb/assets/reference.json b/openbb_platform/core/openbb/assets/reference.json index 292b7157e3a..28f0d119bfc 100644 --- a/openbb_platform/core/openbb/assets/reference.json +++ b/openbb_platform/core/openbb/assets/reference.json @@ -5,11 +5,29890 @@ "description": "Investment research for everyone, anywhere.", "core": "2.0.0", "extensions": { - "openbb_core_extension": [], - "openbb_provider_extension": [], - "openbb_obbject_extension": [] + "openbb_core_extension": [ + "econometrics@2.0.0", + "economy@1.6.1", + "equity@1.6.1", + "oecd@2.0.0", + "quantitative@2.0.0", + "technical@2.0.0", + "yfinance@2.0.0" + ], + "openbb_provider_extension": [ + "oecd@2.0.0", + "yfinance@2.0.0" + ], + "openbb_obbject_extension": [ + "openbb_charting@3.0.0" + ] } }, - "paths": {}, - "routers": {} + "paths": { + "/econometrics/correlation_matrix": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the correlation matrix of an input dataset.\n\nThe correlation matrix shows how strongly the variables in the dataset move in\nrelation to one another. Scores range from -1 (perfect negative correlation) through\n0 (no correlation) to 1 (perfect positive correlation).", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get the correlation matrix of a dataset.\nstock_data = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').to_df()\nobb.econometrics.correlation_matrix(data=stock_data)\nobb.econometrics.correlation_matrix(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "method", + "type": "Literal['pearson', 'kendall', 'spearman']", + "description": "Correlation method: 'pearson' (standard), 'kendall' (Kendall Tau), or 'spearman' (rank correlation).", + "default": "pearson", + "optional": true + }, + { + "name": "min_periods", + "type": "int", + "description": "Minimum number of observations required per pair of columns to compute a correlation.", + "default": 1, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[CorrelationMatrixData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "comp_to", + "type": "str", + "description": "The variable each correlation in the row is measured against.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/summary_statistics": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute descriptive summary statistics for each numeric column.\n\nReturns per-column count, mean, standard deviation, minimum, quartiles, maximum,\nskewness, and excess kurtosis - a quick overview of each variable's distribution.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.summary_statistics(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[SummaryStatisticsData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "column", + "type": "str", + "description": "Name of the column.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "count", + "type": "int", + "description": "Number of non-null observations.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "mean", + "type": "float", + "description": "Arithmetic mean.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "std", + "type": "float", + "description": "Sample standard deviation.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "min", + "type": "float", + "description": "Minimum value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p25", + "type": "float", + "description": "25th percentile.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "median", + "type": "float", + "description": "Median (50th percentile).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p75", + "type": "float", + "description": "75th percentile.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "max", + "type": "float", + "description": "Maximum value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "skew", + "type": "float", + "description": "Skewness of the distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kurtosis", + "type": "float", + "description": "Excess kurtosis of the distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/ols_regression": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform Ordinary Least Squares (OLS) regression.\n\nOLS fits the best linear relationship between a dependent variable and one or more\nindependent variables. Returns the estimated coefficient table - one row per\nregressor, with standard errors, t-statistics, p-values, and confidence intervals.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Perform Ordinary Least Squares (OLS) regression.\nstock_data = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').to_df()\nobb.econometrics.ols_regression(data=stock_data, y_column=\"close\", x_columns=[\"open\", \"high\", \"low\"])\nobb.econometrics.ols_regression(y_column='close', x_columns=['open', 'high', 'low'], data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "cov_type", + "type": "Literal['nonrobust', 'HC0', 'HC1', 'HC2', 'HC3']", + "description": "Covariance estimator for the standard errors: 'nonrobust', or heteroskedasticity-robust 'HC0', 'HC1', 'HC2', or 'HC3'.", + "default": "nonrobust", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[OlsRegressionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "variable", + "type": "str", + "description": "Name of the regressor ('const' is the intercept).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "Estimated coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "standard_error", + "type": "float", + "description": "Standard error of the coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "t_statistic", + "type": "float", + "description": "t-statistic for the null hypothesis that the coefficient is zero.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "Two-sided p-value of the t-statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_lower", + "type": "float", + "description": "Lower bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_upper", + "type": "float", + "description": "Upper bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/ols_regression_summary": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform Ordinary Least Squares (OLS) regression and return the fit summary.\n\nReturns the model-level goodness-of-fit statistics - R-squared, F-statistic,\ninformation criteria, log-likelihood, and degrees of freedom - along with the full\nstatsmodels text summary.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.ols_regression_summary(y_column='close', x_columns=['open', 'high', 'low'], data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "cov_type", + "type": "Literal['nonrobust', 'HC0', 'HC1', 'HC2', 'HC3']", + "description": "Covariance estimator for the standard errors: 'nonrobust', or heteroskedasticity-robust 'HC0', 'HC1', 'HC2', or 'HC3'.", + "default": "nonrobust", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "OlsRegressionSummaryData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "r_squared", + "type": "float", + "description": "Coefficient of determination.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adjusted_r_squared", + "type": "float", + "description": "R-squared adjusted for the number of regressors.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "f_statistic", + "type": "float", + "description": "F-statistic of the overall regression.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "f_p_value", + "type": "float", + "description": "p-value of the F-statistic.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "aic", + "type": "float", + "description": "Akaike Information Criterion.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "bic", + "type": "float", + "description": "Bayesian Information Criterion.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "log_likelihood", + "type": "float", + "description": "Log-likelihood of the fitted model.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "nobs", + "type": "int", + "description": "Number of observations.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "df_model", + "type": "float", + "description": "Model degrees of freedom.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "df_residuals", + "type": "float", + "description": "Residual degrees of freedom.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "summary", + "type": "str", + "description": "Full statsmodels text summary.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/autocorrelation": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform the Durbin-Watson test for autocorrelation in regression residuals.\n\nThe Durbin-Watson statistic ranges from 0 to 4. A value near 2 suggests no\nautocorrelation; values toward 0 indicate positive autocorrelation and values\ntoward 4 indicate negative autocorrelation.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.autocorrelation(y_column='close', x_columns=['open', 'high', 'low'], data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "AutocorrelationData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "durbin_watson", + "type": "float", + "description": "Durbin-Watson statistic (near 2 = no autocorrelation).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/residual_autocorrelation": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform the Breusch-Godfrey LM test for residual autocorrelation.\n\nThe Breusch-Godfrey test detects autocorrelation in regression residuals up to a\nchosen number of lags. Significant statistics indicate the model has not captured\nsome of the data's serial dependence.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.residual_autocorrelation(y_column='close', x_columns=['open', 'high', 'low'], data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "lags", + "type": "int", + "description": "Number of lags to include in the test.", + "default": 1, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "ResidualAutocorrelationData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "lm_statistic", + "type": "float", + "description": "Lagrange Multiplier test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "lm_p_value", + "type": "float", + "description": "p-value of the LM statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "f_statistic", + "type": "float", + "description": "F-statistic of the auxiliary regression.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "f_p_value", + "type": "float", + "description": "p-value of the F-statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/heteroskedasticity": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform the Breusch-Pagan and White tests for heteroskedasticity.\n\nHeteroskedasticity - non-constant variance of the regression residuals - violates a\nkey OLS assumption and makes standard errors unreliable. The Breusch-Pagan test\ndetects linear forms; the White test additionally captures non-linear forms.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.heteroskedasticity(y_column='close', x_columns=['open', 'high', 'low'], data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "robust", + "type": "bool", + "description": "Use the robust (studentized Koenker) Breusch-Pagan variant, which does not assume normally distributed residuals.", + "default": true, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "HeteroskedasticityData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "breusch_pagan_lm_statistic", + "type": "float", + "description": "Breusch-Pagan LM statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "breusch_pagan_lm_p_value", + "type": "float", + "description": "p-value of the Breusch-Pagan LM statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "breusch_pagan_f_statistic", + "type": "float", + "description": "Breusch-Pagan F statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "breusch_pagan_f_p_value", + "type": "float", + "description": "p-value of the Breusch-Pagan F statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "white_lm_statistic", + "type": "float", + "description": "White LM statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "white_lm_p_value", + "type": "float", + "description": "p-value of the White LM statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "white_f_statistic", + "type": "float", + "description": "White F statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "white_f_p_value", + "type": "float", + "description": "p-value of the White F statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/normality": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform the Jarque-Bera normality test on regression residuals.\n\nThe Jarque-Bera test checks whether OLS residuals are normally distributed by\ncomparing their skewness and kurtosis to those of a normal distribution.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.normality(y_column='close', x_columns=['open', 'high', 'low'], data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "NormalityData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "jarque_bera", + "type": "float", + "description": "Jarque-Bera test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "skew", + "type": "float", + "description": "Skewness of the residuals.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kurtosis", + "type": "float", + "description": "Kurtosis of the residuals.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/variance_inflation_factor": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the variance inflation factor (VIF) to detect multicollinearity.\n\nVIF quantifies how much the variance of an estimated coefficient is inflated by\ncollinearity with the other regressors. A VIF between 1 and 5 is generally\nacceptable; values above 5 indicate high collinearity.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Calculate the variance inflation factor.\nstock_data = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').to_df()\nobb.econometrics.variance_inflation_factor(data=stock_data, columns=[\"open\", \"high\", \"low\", \"close\"])\nobb.econometrics.variance_inflation_factor(columns=['open', 'high', 'low'], data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "columns", + "type": "list[str]", + "description": "Columns to test for collinearity. If omitted, all numeric columns.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[VarianceInflationFactorData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "variable", + "type": "str", + "description": "Name of the variable.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "vif", + "type": "float", + "description": "Variance inflation factor (above 5 indicates high collinearity).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/unit_root": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform the Augmented Dickey-Fuller (ADF) unit root test.\n\nThe ADF test checks whether a series has a unit root - a sign of non-stationarity.\nRejecting the null hypothesis indicates the series is stationary.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.unit_root(column='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "column", + "type": "str", + "description": "Column to test for a unit root.", + "default": null, + "optional": false + }, + { + "name": "regression", + "type": "Literal['c', 'ct', 'ctt']", + "description": "Deterministic terms: 'c' constant, 'ct' constant and trend, 'ctt' constant, trend, and quadratic trend.", + "default": "c", + "optional": true + }, + { + "name": "maxlag", + "type": "Annotated[int, Ge(ge=0)]", + "description": "Maximum lag to include. If None, a data-dependent default is used.", + "default": null, + "optional": true + }, + { + "name": "autolag", + "type": "None", + "description": "Lag-selection method: 'AIC', 'BIC', or 't-stat'; None uses maxlag directly without selection.", + "default": "AIC", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "UnitRootData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "adf_stat", + "type": "float", + "description": "Test statistic (more negative favours stationarity).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "MacKinnon p-value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "used_lag", + "type": "int", + "description": "Number of lags used.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "nobs", + "type": "int", + "description": "Number of observations used.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "ic_best", + "type": "float", + "description": "Maximised information criterion (None when autolag is disabled).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "critical_values", + "type": "dict", + "description": "Critical values at the 1%, 5%, and 10% levels.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/kpss": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform the KPSS test for stationarity.\n\nThe KPSS null hypothesis is that the series IS stationary - the opposite of the ADF\ntest - so running both tests gives a more robust read on stationarity.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.kpss(column='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "column", + "type": "str", + "description": "Column to test.", + "default": null, + "optional": false + }, + { + "name": "regression", + "type": "Literal['c', 'ct']", + "description": "Null hypothesis: 'c' level-stationary, 'ct' trend-stationary.", + "default": "c", + "optional": true + }, + { + "name": "nlags", + "type": "Annotated[int, Ge(ge=0)]", + "description": "Lags for the variance estimator: 'auto', 'legacy', or an explicit non-negative integer.", + "default": "auto", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "KpssData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "kpss_stat", + "type": "float", + "description": "Test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value_interpolated", + "type": "bool", + "description": "False when p_value is a clamped table boundary (the true p-value is more extreme than reported).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "lags", + "type": "int", + "description": "Number of lags used in the test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "critical_values", + "type": "dict", + "description": "Critical values at the 10%, 5%, 2.5%, and 1% levels.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/cointegration": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Test pairs of time series for cointegration via the Engle-Granger two-step test.\n\nCointegration means two non-stationary series share a long-run equilibrium. The\nEngle-Granger test regresses one series on the other and checks the residuals for\nstationarity.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.cointegration(columns=['open', 'close'], data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "columns", + "type": "list[str]", + "description": "Columns to test pairwise for cointegration.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[CointegrationData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "pair", + "type": "str", + "description": "The tested column pair, formatted as 'x/y'.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "c", + "type": "float", + "description": "Constant of the long-run relationship.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "gamma", + "type": "float", + "description": "Slope of the long-run relationship.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "alpha", + "type": "float", + "description": "Error-correction speed of adjustment.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_stat", + "type": "float", + "description": "Dickey-Fuller statistic on the residuals.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the Dickey-Fuller statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/cointegration_johansen": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform the Johansen cointegration test on multiple time series.\n\nThe Johansen test detects cointegrating relationships among two or more series\nsimultaneously, reporting the number of cointegrating vectors via the trace and\nmaximum-eigenvalue statistics.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.cointegration_johansen(columns=['open', 'close'], data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "columns", + "type": "list[str]", + "description": "Columns (time series) to test jointly for cointegration.", + "default": null, + "optional": false + }, + { + "name": "deterministic_order", + "type": "Literal[-1, 0, 1]", + "description": "Deterministic trend: -1 none, 0 constant, 1 constant and trend.", + "default": 0, + "optional": true + }, + { + "name": "k_ar_diff", + "type": "int", + "description": "Number of lagged differences in the model.", + "default": 1, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[CointegrationJohansenData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "rank", + "type": "int", + "description": "Cointegration rank hypothesis (r <= rank).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "eigenvalue", + "type": "float", + "description": "Eigenvalue associated with the rank.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "trace_statistic", + "type": "float", + "description": "Trace test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "trace_crit_90", + "type": "float", + "description": "Trace critical value at the 90% level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "trace_crit_95", + "type": "float", + "description": "Trace critical value at the 95% level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "trace_crit_99", + "type": "float", + "description": "Trace critical value at the 99% level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "max_eig_statistic", + "type": "float", + "description": "Maximum-eigenvalue test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "max_eig_crit_90", + "type": "float", + "description": "Maximum-eigenvalue critical value at the 90% level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "max_eig_crit_95", + "type": "float", + "description": "Maximum-eigenvalue critical value at the 95% level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "max_eig_crit_99", + "type": "float", + "description": "Maximum-eigenvalue critical value at the 99% level.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/causality": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Perform the Granger causality test to assess whether X helps predict y.\n\nGranger causality tests whether past values of one series improve the forecast of\nanother. It is a predictive, not a structural, notion of causality.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Granger causality test with mock data.\nobb.econometrics.causality(y_column='close', x_column='open', lag=1, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Target column.", + "default": null, + "optional": false + }, + { + "name": "x_column", + "type": "str", + "description": "Candidate causal column.", + "default": null, + "optional": false + }, + { + "name": "lag", + "type": "int", + "description": "Number of lags to test.", + "default": 3, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[CausalityData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "test", + "type": "str", + "description": "Name of the Granger sub-test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "statistic", + "type": "float", + "description": "Test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "lag", + "type": "int", + "description": "Number of lags tested.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/garch": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Fit a configurable volatility model from the GARCH family to a series.\n\nThe mean model, volatility process, lag orders, power, and innovation distribution\nare all configurable. The estimated conditional volatility series is returned; the\nfitted parameters and information criteria are attached to ``OBBject.extra``. Best\napplied to a return series.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.garch(column='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "column", + "type": "str", + "description": "Column to model. Best applied to a return series.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Exogenous regressor columns for the mean model (used by the 'LS', 'ARX', and 'HARX' mean models).", + "default": null, + "optional": true + }, + { + "name": "mean", + "type": "Literal['Constant', 'Zero', 'LS', 'AR', 'ARX', 'HAR', 'HARX']", + "description": "Mean model: 'Constant' (constant mean), 'Zero' (zero mean), 'LS' (least squares), 'AR'/'ARX' (autoregressive, 'ARX' adds exogenous regressors), 'HAR'/'HARX' (heterogeneous autoregressive).", + "default": "Constant", + "optional": true + }, + { + "name": "lags", + "type": "int", + "description": "Lag order of the 'AR', 'ARX', 'HAR', and 'HARX' mean models.", + "default": 0, + "optional": true + }, + { + "name": "vol", + "type": "Literal['GARCH', 'ARCH', 'EGARCH', 'FIGARCH', 'APARCH', 'HARCH']", + "description": "Volatility process: 'GARCH', 'ARCH', 'EGARCH' (exponential GARCH, captures leverage), 'FIGARCH' (fractionally integrated, long memory), 'APARCH' (asymmetric power ARCH), 'HARCH' (heterogeneous ARCH).", + "default": "GARCH", + "optional": true + }, + { + "name": "p", + "type": "int", + "description": "Order of the symmetric ARCH (innovation) term.", + "default": 1, + "optional": true + }, + { + "name": "o", + "type": "int", + "description": "Order of the asymmetric (leverage) term.", + "default": 0, + "optional": true + }, + { + "name": "q", + "type": "int", + "description": "Order of the lagged volatility (GARCH) term.", + "default": 1, + "optional": true + }, + { + "name": "power", + "type": "float", + "description": "Power applied to the conditional volatility (2.0 is standard GARCH; other values give power-GARCH and APARCH variants).", + "default": 2.0, + "optional": true + }, + { + "name": "distribution", + "type": "Literal['normal', 't', 'skewt', 'ged']", + "description": "Distribution of the model innovations: 'normal' (Gaussian), 't' (Student's t, fat tails), 'skewt' (skewed Student's t), or 'ged' (generalized error distribution).", + "default": "normal", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[GarchData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | int | str", + "description": "Observation date (integer position when the input has no date).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conditional_volatility", + "type": "float", + "description": "Estimated conditional volatility at the observation.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/panel_random_effects": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Estimate a one-way Random Effects model for panel data.\n\nThe Random Effects model treats entity-specific deviations as random draws,\ncapturing variation across entities while pooling information for efficient\nestimation. Requires at least three observations.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.panel_random_effects(y_column='portfolio_value', x_columns=['risk_free_rate'], data=[{'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 0, 'portfolio_value': 100000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 1, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 0, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 1, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 0, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 1, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 0, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 1, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 0, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 1, 'portfolio_value': 116666.67, 'risk_free_rate': 0.02}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input panel dataset, indexed by entity and time.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "cov_type", + "type": "Literal['unadjusted', 'robust']", + "description": "Covariance estimator: 'unadjusted' (homoskedastic) or 'robust' (heteroskedasticity-robust standard errors).", + "default": "unadjusted", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[PanelRegressionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "variable", + "type": "str", + "description": "Name of the regressor.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "Estimated coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "standard_error", + "type": "float", + "description": "Standard error of the coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "t_statistic", + "type": "float", + "description": "t-statistic for the null hypothesis that the coefficient is zero.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the t-statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_lower", + "type": "float", + "description": "Lower bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_upper", + "type": "float", + "description": "Upper bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/panel_between": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Estimate a Between regression on panel data.\n\nThe Between estimator regresses the entity-level means, summarising the long-run\ncross-sectional relationship between the regressors and the target.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.panel_between(y_column='portfolio_value', x_columns=['risk_free_rate'], data=[{'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 0, 'portfolio_value': 100000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 1, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 0, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 1, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 0, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 1, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 0, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 1, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 0, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 1, 'portfolio_value': 116666.67, 'risk_free_rate': 0.02}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input panel dataset, indexed by entity and time.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "cov_type", + "type": "Literal['unadjusted', 'robust']", + "description": "Covariance estimator: 'unadjusted' (homoskedastic) or 'robust' (heteroskedasticity-robust standard errors).", + "default": "unadjusted", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[PanelRegressionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "variable", + "type": "str", + "description": "Name of the regressor.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "Estimated coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "standard_error", + "type": "float", + "description": "Standard error of the coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "t_statistic", + "type": "float", + "description": "t-statistic for the null hypothesis that the coefficient is zero.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the t-statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_lower", + "type": "float", + "description": "Lower bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_upper", + "type": "float", + "description": "Upper bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/panel_pooled": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Estimate a Pooled OLS regression on panel data.\n\nThe Pooled estimator treats the panel as one large cross-section, assuming the\nregressors have a uniform effect across all entities and time periods.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.panel_pooled(y_column='portfolio_value', x_columns=['risk_free_rate'], data=[{'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 0, 'portfolio_value': 100000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 1, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 0, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 1, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 0, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 1, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 0, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 1, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 0, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 1, 'portfolio_value': 116666.67, 'risk_free_rate': 0.02}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input panel dataset, indexed by entity and time.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "cov_type", + "type": "Literal['unadjusted', 'robust']", + "description": "Covariance estimator: 'unadjusted' (homoskedastic) or 'robust' (heteroskedasticity-robust standard errors).", + "default": "unadjusted", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[PanelRegressionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "variable", + "type": "str", + "description": "Name of the regressor.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "Estimated coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "standard_error", + "type": "float", + "description": "Standard error of the coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "t_statistic", + "type": "float", + "description": "t-statistic for the null hypothesis that the coefficient is zero.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the t-statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_lower", + "type": "float", + "description": "Lower bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_upper", + "type": "float", + "description": "Upper bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/panel_fixed": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Estimate a Fixed Effects regression on panel data.\n\nThe Fixed Effects estimator controls for entity- and/or time-specific effects,\nisolating the effect of the regressors from unobserved heterogeneity.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.panel_fixed(y_column='portfolio_value', x_columns=['risk_free_rate'], data=[{'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 0, 'portfolio_value': 100000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 1, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 0, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 1, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 0, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 1, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 0, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 1, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 0, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 1, 'portfolio_value': 116666.67, 'risk_free_rate': 0.02}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input panel dataset, indexed by entity and time.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "cov_type", + "type": "Literal['unadjusted', 'robust']", + "description": "Covariance estimator: 'unadjusted' (homoskedastic) or 'robust' (heteroskedasticity-robust standard errors).", + "default": "unadjusted", + "optional": true + }, + { + "name": "entity_effects", + "type": "bool", + "description": "Include entity-specific fixed effects.", + "default": true, + "optional": true + }, + { + "name": "time_effects", + "type": "bool", + "description": "Include time-specific fixed effects.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[PanelRegressionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "variable", + "type": "str", + "description": "Name of the regressor.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "Estimated coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "standard_error", + "type": "float", + "description": "Standard error of the coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "t_statistic", + "type": "float", + "description": "t-statistic for the null hypothesis that the coefficient is zero.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the t-statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_lower", + "type": "float", + "description": "Lower bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_upper", + "type": "float", + "description": "Upper bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/panel_first_difference": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Estimate a First-Difference regression on panel data.\n\nDifferencing consecutive observations removes time-invariant entity effects, so the\nestimator measures how changes in the regressors relate to changes in the target.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.panel_first_difference(y_column='portfolio_value', x_columns=['risk_free_rate'], data=[{'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 0, 'portfolio_value': 100000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 1, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 0, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 1, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 0, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 1, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 0, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 1, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 0, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 1, 'portfolio_value': 116666.67, 'risk_free_rate': 0.02}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input panel dataset, indexed by entity and time.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "cov_type", + "type": "Literal['unadjusted', 'robust']", + "description": "Covariance estimator: 'unadjusted' (homoskedastic) or 'robust' (heteroskedasticity-robust standard errors).", + "default": "unadjusted", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[PanelRegressionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "variable", + "type": "str", + "description": "Name of the regressor.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "Estimated coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "standard_error", + "type": "float", + "description": "Standard error of the coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "t_statistic", + "type": "float", + "description": "t-statistic for the null hypothesis that the coefficient is zero.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the t-statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_lower", + "type": "float", + "description": "Lower bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_upper", + "type": "float", + "description": "Upper bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/econometrics/panel_fmac": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Estimate a Fama-MacBeth regression on panel data.\n\nThe Fama-MacBeth procedure runs a cross-sectional regression each period and\naverages the coefficients over time - widely used to estimate risk premia.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.econometrics.panel_fmac(y_column='portfolio_value', x_columns=['risk_free_rate'], data=[{'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 0, 'portfolio_value': 100000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_1', 'time': 1, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 0, 'portfolio_value': 150000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_2', 'time': 1, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 0, 'portfolio_value': 133333.33, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_3', 'time': 1, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 0, 'portfolio_value': 125000.0, 'risk_free_rate': 0.03}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_4', 'time': 1, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 0, 'portfolio_value': 120000.0, 'risk_free_rate': 0.02}, {'is_multiindex': True, 'multiindex_names': \"['asset_manager', 'time']\", 'asset_manager': 'asset_manager_5', 'time': 1, 'portfolio_value': 116666.67, 'risk_free_rate': 0.02}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input panel dataset, indexed by entity and time.", + "default": null, + "optional": false + }, + { + "name": "y_column", + "type": "str", + "description": "Name of the dependent (target) column.", + "default": null, + "optional": false + }, + { + "name": "x_columns", + "type": "list[str]", + "description": "Names of the independent (exogenous) columns.", + "default": null, + "optional": false + }, + { + "name": "cov_type", + "type": "Literal['unadjusted', 'robust']", + "description": "Covariance estimator: 'unadjusted' (homoskedastic) or 'robust' (heteroskedasticity-robust standard errors).", + "default": "unadjusted", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[PanelRegressionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "variable", + "type": "str", + "description": "Name of the regressor.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "Estimated coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "standard_error", + "type": "float", + "description": "Standard error of the coefficient.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "t_statistic", + "type": "float", + "description": "t-statistic for the null hypothesis that the coefficient is zero.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "p-value of the t-statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_lower", + "type": "float", + "description": "Lower bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "conf_int_upper", + "type": "float", + "description": "Upper bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/economy/gdp/forecast": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get Forecasted GDP Data.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.gdp.forecast(provider='oecd')\nobb.economy.gdp.forecast(country='united_states,germany,france', frequency='annual', units='capita', provider='oecd')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "Country, or countries, to get forward GDP projections for. Default is all. Multiple items allowed.", + "default": "all", + "optional": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "dae", + "denmark", + "estonia", + "eu22_oecd", + "euro_area_17_countries", + "finland", + "former_ussr", + "france", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "nafta", + "netherlands", + "new_zealand", + "non_oecd_economies", + "norway", + "oecd", + "oecd_asia_oceania", + "opec", + "other_countries", + "other_major_oil", + "other_oil_producers", + "peru", + "poland", + "portugal", + "rest_of_world", + "romania", + "russia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "thailand", + "turkiye", + "united_kingdom", + "united_states", + "world", + "all" + ], + "multiple_items_allowed": true, + "json_schema_extra": {} + }, + { + "name": "frequency", + "type": "Literal['annual', 'quarter'] | None", + "description": "Frequency of the data, default is annual.", + "default": "annual", + "optional": true, + "choices": [ + "annual", + "quarter" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "annual", + "quarter" + ] + } + }, + { + "name": "units", + "type": "Literal['current_prices', 'volume', 'capita', 'growth', 'deflator'] | None", + "description": "Units of the data, default is volume (chain linked volume, 2015).\n'current_prices', 'volume', and 'capita' are expressed in USD; 'growth' as a percent; 'deflator' as an index.", + "default": "volume", + "optional": true, + "choices": [ + "current_prices", + "volume", + "capita", + "growth", + "deflator" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "current_prices", + "volume", + "capita", + "growth", + "deflator" + ] + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "GdpForecast", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str", + "description": "None", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "int", + "description": "Forecasted GDP value for the country and date.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ], + "oecd": [] + }, + "model": "GdpForecast", + "openapi_extra": { + "model": "GdpForecast" + } + }, + "/economy/gdp/nominal": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get Nominal GDP Data.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.gdp.nominal(provider='oecd')\nobb.economy.gdp.nominal(units='capita', country='all', frequency='annual', provider='oecd')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "The country to get data. Use 'all' to get data for all available countries. Multiple items allowed.", + "default": "united_states", + "optional": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "eu15", + "eu27", + "euro_area_20_countries", + "finland", + "france", + "g20", + "g7", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "oecd", + "oecd_26_countries", + "oecd_europe", + "poland", + "portugal", + "romania", + "russia", + "saudi_arabia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "usmca", + "all" + ], + "multiple_items_allowed": true, + "json_schema_extra": {} + }, + { + "name": "frequency", + "type": "Literal['quarter', 'annual'] | None", + "description": "Frequency of the data.", + "default": "quarter", + "optional": true, + "choices": [ + "quarter", + "annual" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "quarter", + "annual" + ] + } + }, + { + "name": "units", + "type": "Literal['level', 'index', 'capita'] | None", + "description": "The unit of measurement for the data.Both 'level' and 'capita' (per) are measured in USD.", + "default": "level", + "optional": true, + "choices": [ + "level", + "index", + "capita" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "level", + "index", + "capita" + ] + } + }, + { + "name": "price_base", + "type": "Literal['current_prices', 'volume'] | None", + "description": "Price base for the data, volume is chain linked volume.", + "default": "current_prices", + "optional": true, + "choices": [ + "current_prices", + "volume" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "current_prices", + "volume" + ] + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "GdpNominal", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "The country represented by the GDP value.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "int", + "description": "GDP value for the country and date.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ], + "oecd": [] + }, + "model": "GdpNominal", + "openapi_extra": { + "model": "GdpNominal" + } + }, + "/economy/gdp/real": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get Real GDP Data.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.gdp.real(provider='oecd')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "The country to get data. Use 'all' to get data for all available countries. Multiple items allowed.", + "default": "united_states", + "optional": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "eu15", + "eu27", + "euro_area_20_countries", + "finland", + "france", + "g20", + "g7", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "oecd", + "oecd_26_countries", + "oecd_europe", + "poland", + "portugal", + "romania", + "russia", + "saudi_arabia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "usmca", + "all" + ], + "multiple_items_allowed": true, + "json_schema_extra": {} + }, + { + "name": "frequency", + "type": "Literal['quarter', 'annual'] | None", + "description": "Frequency of the data.", + "default": "quarter", + "optional": true, + "choices": [ + "quarter", + "annual" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "quarter", + "annual" + ] + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "GdpReal", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "The country represented by the Real GDP value.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "int", + "description": "Real GDP value for the country and date.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ], + "oecd": [] + }, + "model": "GdpReal", + "openapi_extra": { + "model": "GdpReal" + } + }, + "/economy/calendar": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the upcoming, or historical, economic calendar of global events.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EconomicCalendar", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "datetime | None | str", + "description": "The date of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "Country of event.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "category", + "type": "str | None", + "description": "Category of event.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "event", + "type": "str | None", + "description": "Event name.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "importance", + "type": "str | None", + "description": "The importance level for the event.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "source", + "type": "str | None", + "description": "Source of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "unit", + "type": "str | None", + "description": "Unit of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "consensus", + "type": "str | None", + "description": "Average forecast among a representative group of economists.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "previous", + "type": "str | None", + "description": "Value for the previous period after the revision (if revision is applicable).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "revised", + "type": "str | None", + "description": "Revised previous value, if applicable.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "actual", + "type": "str | None", + "description": "Latest released value.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "reference_period", + "type": "str | None", + "description": "The reference period the release is for.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EconomicCalendar", + "openapi_extra": { + "model": "EconomicCalendar" + } + }, + "/economy/cpi": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get Consumer Price Index (CPI) data by country.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Use the `transform` parameter to define the reference period for the change in values. Default is YoY.\nobb.economy.cpi(country='united_states,united_kingdom', transform='period', provider='oecd')\n# Get the latest reported weightings of a country's CPI basket, from IMF.\nres = obb.economy.cpi(provider='imf', country='CAN', transform='weight_percent', expenditure='all', limit=1)\nprint(res.model_dump(include='results')['results'])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "The country to get data. Multiple items allowed for provider(s): oecd.", + "default": "united_states", + "optional": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "eu27", + "euro_area_20_countries", + "finland", + "france", + "g20", + "g7", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "oecd", + "oecd_europe", + "poland", + "portugal", + "romania", + "russia", + "saudi_arabia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "multiple_items_allowed": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "eu27", + "euro_area_20_countries", + "finland", + "france", + "g20", + "g7", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "oecd", + "oecd_europe", + "poland", + "portugal", + "romania", + "russia", + "saudi_arabia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ] + } + } + }, + { + "name": "transform", + "type": "str | None", + "description": "Transformation of the CPI data.", + "default": "yoy", + "optional": true, + "choices": [ + "index", + "yoy", + "period" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "choices": [ + "index", + "yoy", + "period" + ] + } + } + }, + { + "name": "frequency", + "type": "Literal['annual', 'quarter', 'monthly'] | None", + "description": "The frequency of the data.", + "default": "monthly", + "optional": true, + "choices": [ + "annual", + "quarter", + "monthly" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "choices": [ + "annual", + "quarter", + "monthly" + ] + } + } + }, + { + "name": "harmonized", + "type": "bool | None", + "description": "If true, returns harmonized data.", + "default": false, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "expenditure", + "type": "str | None | list[str | None]", + "description": "Expenditure component of CPI. Multiple items allowed.", + "default": "total", + "optional": true, + "choices": [ + "actual_rentals", + "alcoholic_beverages_tobacco_narcotics", + "all_non_food_non_energy", + "clothing_footwear", + "communication", + "education", + "electricity_gas_other_fuels", + "energy", + "food_non_alcoholic_beverages", + "fuels_lubricants_personal", + "furniture_household_equipment", + "goods", + "health", + "housing", + "housing_excluding_rentals", + "housing_water_electricity_gas", + "imputed_rentals", + "maintenance_repair_dwelling", + "miscellaneous_goods_services", + "overall_excl_energy_food_alcohol_tobacco", + "recreation_culture", + "residuals", + "restaurants_hotels", + "services", + "services_less_house_excl_rentals", + "services_less_housing", + "total", + "transport", + "water_supply_other_services", + "all" + ], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "ConsumerPriceIndex", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str", + "description": "None", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "float", + "description": "CPI index value or period change.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "unit", + "type": "str", + "description": "Unit of measurement.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "unit_multiplier", + "type": "int", + "description": "Unit multiplier for the observation value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "country_code", + "type": "str", + "description": "ISO3 country code.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "series_id", + "type": "str", + "description": "OECD series identifier.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "expenditure", + "type": "str", + "description": "Expenditure component of CPI.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "title", + "type": "str", + "description": "Complete reference title for the series.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "order", + "type": "int | None", + "description": "Sort order for expenditure categories.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "ConsumerPriceIndex", + "openapi_extra": { + "model": "ConsumerPriceIndex" + } + }, + "/economy/balance_of_payments": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Balance of Payments Reports.", + "examples": "", + "parameters": { + "standard": [], + "oecd": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "The country to get data. Multiple items allowed.", + "default": "united_states", + "optional": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "czechia", + "denmark", + "estonia", + "euro_area_20_countries", + "eu27", + "finland", + "france", + "g20", + "g7", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "oecd", + "poland", + "portugal", + "russia", + "saudi_arabia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ], + "multiple_items_allowed": true, + "json_schema_extra": {} + }, + { + "name": "frequency", + "type": "Literal['annual', 'quarterly'] | None", + "description": "Frequency of the data.", + "default": "quarterly", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "BalanceOfPayments", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "period", + "type": "date | None", + "description": "The date representing the beginning of the reporting period.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "balance_percent_of_gdp", + "type": "float | None", + "description": "Current Account Balance as Percent of GDP", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "balance_total", + "type": "float | None", + "description": "Current Account Total Balance (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "balance_total_services", + "type": "float | None", + "description": "Current Account Total Services Balance (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "balance_total_secondary_income", + "type": "float | None", + "description": "Current Account Total Secondary Income Balance (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "balance_total_goods", + "type": "float | None", + "description": "Current Account Total Goods Balance (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "balance_total_primary_income", + "type": "float | None", + "description": "Current Account Total Primary Income Balance (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "credits_services_percent_of_goods_and_services", + "type": "float | None", + "description": "Current Account Credits Services as Percent of Goods and Services", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "credits_services_percent_of_current_account", + "type": "float | None", + "description": "Current Account Credits Services as Percent of Current Account", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "credits_total_services", + "type": "float | None", + "description": "Current Account Credits Total Services (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "credits_total_goods", + "type": "float | None", + "description": "Current Account Credits Total Goods (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "credits_total_primary_income", + "type": "float | None", + "description": "Current Account Credits Total Primary Income (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "credits_total_secondary_income", + "type": "float | None", + "description": "Current Account Credits Total Secondary Income (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "credits_total", + "type": "float | None", + "description": "Current Account Credits Total (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "debits_services_percent_of_goods_and_services", + "type": "float | None", + "description": "Current Account Debits Services as Percent of Goods and Services", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "debits_services_percent_of_current_account", + "type": "float | None", + "description": "Current Account Debits Services as Percent of Current Account", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "debits_total_services", + "type": "float | None", + "description": "Current Account Debits Total Services (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "debits_total_goods", + "type": "float | None", + "description": "Current Account Debits Total Goods (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "debits_total_primary_income", + "type": "float | None", + "description": "Current Account Debits Total Primary Income (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "debits_total", + "type": "float | None", + "description": "Current Account Debits Total (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "debits_total_secondary_income", + "type": "float | None", + "description": "Current Account Debits Total Secondary Income (USD)", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "oecd": [] + }, + "model": "BalanceOfPayments", + "openapi_extra": { + "model": "BalanceOfPayments" + } + }, + "/economy/unemployment": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get global unemployment data.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.unemployment(provider='oecd')\nobb.economy.unemployment(country='all', frequency='quarter', provider='oecd')\n# Demographics for the statistics are selected with the `age` parameter.\nobb.economy.unemployment(country='all', frequency='quarter', age='total', provider='oecd')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "The country to get data. Multiple items allowed for provider(s): oecd.", + "default": "united_states", + "optional": true, + "choices": null, + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "multiple_items_allowed": true, + "choices": [ + "australia", + "austria", + "belgium", + "bulgaria", + "canada", + "chile", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "eu27", + "euro_area", + "finland", + "france", + "g7", + "germany", + "greece", + "hungary", + "iceland", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "oecd", + "poland", + "portugal", + "romania", + "slovak_republic", + "slovenia", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ] + } + } + }, + { + "name": "frequency", + "type": "Literal['monthly', 'quarter', 'annual'] | None", + "description": "The frequency of the data.", + "default": "monthly", + "optional": true, + "choices": [ + "monthly", + "quarter", + "annual" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "monthly", + "quarter", + "annual" + ] + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "country", + "type": "str | None", + "description": "The country to get data.", + "default": "united_states", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "sex", + "type": "Literal['total', 'male', 'female'] | None", + "description": "Sex to get unemployment for.", + "default": "total", + "optional": true, + "choices": [ + "total", + "male", + "female" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "total", + "male", + "female" + ] + } + }, + { + "name": "age", + "type": "Literal['total', '15-24', '25+'] | None", + "description": "Age group to get unemployment for. Total indicates 15 years or over", + "default": "total", + "optional": true, + "choices": [ + "total", + "15-24", + "25+" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "total", + "15-24", + "25+" + ] + } + }, + { + "name": "seasonal_adjustment", + "type": "bool | None", + "description": "Whether to get seasonally adjusted unemployment. Defaults to False.", + "default": false, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "Unemployment", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | None | str", + "description": "The date of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "Country for which unemployment rate is given", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "float | None", + "description": "Unemployment rate, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + } + ], + "oecd": [] + }, + "model": "Unemployment", + "openapi_extra": { + "model": "Unemployment" + } + }, + "/economy/composite_leading_indicator": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the composite leading indicator (CLI).\n\nIt is designed to provide early signals of turning points\nin business cycles showing fluctuation of the economic activity around its long term potential level.\n\nCLIs show short-term economic movements in qualitative rather than quantitative terms.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.composite_leading_indicator(provider='oecd')\nobb.economy.composite_leading_indicator(country='all', provider='oecd', growth_rate=True)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "Country to get the CLI for, default is G20. Multiple items allowed.", + "default": "g20", + "optional": true, + "choices": [ + "asia5", + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "eu27", + "euro_area_19_countries", + "euro_area_20_countries", + "europe4", + "finland", + "france", + "g20", + "g7", + "germany", + "greece", + "hungary", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "nafta", + "netherlands", + "new_zealand", + "norway", + "oecd", + "oecd_europe", + "oecd_non_euro_area", + "poland", + "portugal", + "romania", + "russia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ], + "multiple_items_allowed": true, + "json_schema_extra": {} + }, + { + "name": "adjustment", + "type": "Literal['amplitude', 'normalized'] | None", + "description": "Adjustment of the data, either 'amplitude' or 'normalized'. Default is amplitude.", + "default": "amplitude", + "optional": true, + "choices": [ + "amplitude", + "normalized" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "amplitude", + "normalized" + ] + } + }, + { + "name": "growth_rate", + "type": "bool | None", + "description": "Return the 1-year growth rate (%) of the CLI, default is False.", + "default": false, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "CompositeLeadingIndicator", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "float | None", + "description": "CLI value", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "index" + } + }, + { + "name": "country", + "type": "str", + "description": "Country for the CLI value.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ], + "oecd": [] + }, + "model": "CompositeLeadingIndicator", + "openapi_extra": { + "model": "CompositeLeadingIndicator" + } + }, + "/economy/available_indicators": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the available economic indicators for a provider.", + "examples": "", + "parameters": { + "standard": [], + "oecd": [ + { + "name": "topic", + "type": "str | None", + "description": "Filter to a topic ID (e.g. 'ECO', 'HEA'). Use list_topics() to see available topics.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "label": "Topic", + "description": "Filter by topic. Leave blank for all topics.", + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/list_topic_choices", + "style": { + "popupWidth": 600 + }, + "optional": true + } + } + }, + { + "name": "dataflows", + "type": "str | None", + "description": "Filter by dataflow ID(s). Comma-separated or list. e.g. 'DF_KEI' or 'DF_KEI,DF_QNA'.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "query", + "type": "str | None", + "description": "Search string. Use quotes for exact phrases, + for AND, | for OR. e.g. 'GDP growth', 'CPI | inflation', 'balance +trade'.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "keywords", + "type": "str | None", + "description": "Single-word keyword filter(s). Prefix with 'not' to exclude.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "AvailableIndicators", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol_root", + "type": "str | None", + "description": "The root symbol representing the indicator.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str | None", + "description": "Symbol representing the entity requested in the data. The root symbol with additional codes.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "The name of the country, region, or entity represented by the symbol.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "iso", + "type": "str | None", + "description": "The ISO code of the country, region, or entity represented by the symbol.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "description", + "type": "str | None", + "description": "The description of the indicator.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "frequency", + "type": "str | None", + "description": "The frequency of the indicator data.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "label", + "type": "str | None", + "description": "Short human-readable name of the indicator.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dataflow_id", + "type": "str", + "description": "Dataflow identifier.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "dataflow_name", + "type": "str | None", + "description": "Human-readable dataflow name.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dimension_id", + "type": "str | None", + "description": "Dimension containing the indicator.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "parent", + "type": "str | None", + "description": "Parent indicator code in the codelist hierarchy.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "frequencies", + "type": "list[str] | None", + "description": "Available observation frequencies (e.g. Annual, Quarterly, Monthly).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "transformations", + "type": "list[str] | None", + "description": "Available data transformations (e.g. Growth rate, Index).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "member_of", + "type": "list[str] | None", + "description": "Other OECD dataflows that also contain this indicator code.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "AvailableIndicators", + "openapi_extra": { + "model": "AvailableIndicators" + } + }, + "/economy/indicators": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get economic indicators by country and indicator.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): oecd.", + "default": null, + "optional": false, + "choices": null, + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "multiple_items_allowed": true, + "x-widget_config": { + "multiSelect": false, + "multiple": false, + "type": "text" + } + } + } + }, + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "The country to get data. Multiple items allowed for provider(s): oecd.", + "default": null, + "optional": true, + "choices": null, + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "multiple_items_allowed": true, + "x-widget_config": { + "type": "endpoint", + "multiSelect": false, + "optionsEndpoint": "/api/v1/oecd/indicator_choices", + "optionsParams": { + "symbol": "$symbol", + "country": "true", + "dimension_values": "$dimension_values" + }, + "style": { + "popupWidth": 500 + } + } + } + } + }, + { + "name": "frequency", + "type": "str | None", + "description": "The frequency of the data.", + "default": null, + "optional": true, + "choices": null, + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "x-widget_config": { + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/indicator_choices", + "optionsParams": { + "symbol": "$symbol", + "country": "$country", + "frequency": "true", + "dimension_values": "$dimension_values" + } + } + } + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "symbol", + "type": "str", + "description": "OECD indicator symbol(s). Format: 'DATAFLOW::INDICATOR' or 'DATAFLOW::TABLE_ID'. Multiple symbols from the same dataflow can be comma-separated.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "Country name or ISO code. Comma-separated for multiples. 'all' for all.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "frequency", + "type": "str | None", + "description": "Frequency: 'annual'/'yearly', 'quarterly'/'quarter', 'monthly'/'month', or SDMX code (A/Q/M).", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "transform", + "type": "str | None", + "description": "Transformation code (dataflow-specific, e.g. 'GY' for growth rate).", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/indicator_choices", + "optionsParams": { + "symbol": "$symbol", + "country": "$country", + "frequency": "$frequency", + "transform": "true", + "dimension_values": "$dimension_values" + }, + "style": { + "popupWidth": 500 + } + } + } + }, + { + "name": "dimension_values", + "type": "list[str] | None | list[list[str] | None]", + "description": "Additional dimension constraints. Format: 'DIM_ID:VALUE'. e.g. ['SECTOR:S1', 'UNIT_MEASURE:USD_PPP'] Multiple items allowed.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": { + "x-widget_config": { + "type": "text", + "value": null, + "multiple": true, + "multiSelect": false + } + } + }, + { + "name": "limit", + "type": "int | None", + "description": "Maximum number of most recent observations per series.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "label": "Limit", + "type": "number" + } + } + }, + { + "name": "pivot", + "type": "bool | None", + "description": "If True, pivot dates to columns for presentation-table output.", + "default": false, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EconomicIndicators", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | None | str", + "description": "The date of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "symbol_root", + "type": "str | None", + "description": "The root symbol for the indicator (e.g. GDP).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str | None", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "The country represented by the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "int | None", + "description": "", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "unit", + "type": "str | None", + "description": "Unit of measurement.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "unit_multiplier", + "type": "int | None", + "description": "Unit multiplier (power of 10).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "scale", + "type": "str | None", + "description": "Scale description (e.g. Thousands, Millions).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "order", + "type": "int | None", + "description": "Sort order within the table hierarchy.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "level", + "type": "int | None", + "description": "Indentation level in the table hierarchy.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "title", + "type": "str | None", + "description": "Human-readable title of the series.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "description", + "type": "str | None", + "description": "Indicator description.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "country_code", + "type": "str | None", + "description": "ISO country code.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EconomicIndicators", + "openapi_extra": { + "model": "EconomicIndicators" + } + }, + "/economy/share_price_index": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the Share Price Index by country from the OECD Short-Term Economics Statistics.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.share_price_index(provider='oecd')\n# Multiple countries can be passed in as a list.\nobb.economy.share_price_index(country='united_kingdom,germany', frequency='quarter', provider='oecd')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "The country to get data. Multiple items allowed for provider(s): oecd.", + "default": "united_states", + "optional": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "euro_area_19_countries", + "euro_area_20_countries", + "finland", + "france", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "poland", + "portugal", + "romania", + "russia", + "saudi_arabia", + "sdr", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "multiple_items_allowed": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "euro_area_19_countries", + "euro_area_20_countries", + "finland", + "france", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "poland", + "portugal", + "romania", + "russia", + "saudi_arabia", + "sdr", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ] + } + } + }, + { + "name": "frequency", + "type": "Literal['monthly', 'quarter', 'annual'] | None", + "description": "The frequency of the data.", + "default": "monthly", + "optional": true, + "choices": [ + "monthly", + "quarter", + "annual" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "monthly", + "quarter", + "annual" + ] + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "SharePriceIndex", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | None | str", + "description": "The date of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "float | None", + "description": "Share price index value.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "oecd": [] + }, + "model": "SharePriceIndex", + "openapi_extra": { + "model": "SharePriceIndex" + } + }, + "/economy/house_price_index": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the House Price Index by country from the OECD Short-Term Economics Statistics.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.house_price_index(provider='oecd')\n# Multiple countries can be passed in as a list.\nobb.economy.house_price_index(country='united_kingdom,germany', frequency='quarter', provider='oecd')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "The country to get data. Multiple items allowed for provider(s): oecd.", + "default": "united_states", + "optional": true, + "choices": null, + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "multiple_items_allowed": true, + "choices": [ + "australia", + "austria", + "belgium", + "brazil", + "bulgaria", + "canada", + "chile", + "china", + "colombia", + "croatia", + "czechia", + "denmark", + "estonia", + "eu_27", + "euro_area_20_countries", + "finland", + "france", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "poland", + "portugal", + "russia", + "saudi_arabia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ] + } + } + }, + { + "name": "frequency", + "type": "Literal['monthly', 'quarter', 'annual'] | None", + "description": "The frequency of the data.", + "default": "quarter", + "optional": true, + "choices": [ + "monthly", + "quarter", + "annual" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "monthly", + "quarter", + "annual" + ] + } + }, + { + "name": "transform", + "type": "Literal['index', 'yoy', 'period'] | None", + "description": "Transformation of the CPI data. Period represents the change since previous. Defaults to change from one year ago (yoy).", + "default": "index", + "optional": true, + "choices": [ + "index", + "yoy", + "period" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "choices": [ + "index", + "yoy", + "period" + ] + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "country", + "type": "str | None", + "description": "The country to get data.", + "default": "united_states", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "HousePriceIndex", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | None | str", + "description": "The date of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "float | None", + "description": "Share price index value.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "oecd": [] + }, + "model": "HousePriceIndex", + "openapi_extra": { + "model": "HousePriceIndex" + } + }, + "/economy/interest_rates": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get interest rates by country(s) and duration.\nMost OECD countries publish short-term, a long-term, and immediate rates monthly.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.interest_rates(provider='oecd')\n# For OECD, duration can be 'immediate', 'short', or 'long'. Default is 'short', which is the 3-month rate. Overnight interbank rate is 'immediate', and 10-year rate is 'long'.\nobb.economy.interest_rates(provider='oecd', country='all', duration='immediate', frequency='quarter')\n# Multiple countries can be passed in as a list.\nobb.economy.interest_rates(duration='long', country='united_kingdom,germany', frequency='monthly', provider='oecd')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "country", + "type": "str | None | list[str | None]", + "description": "The country to get data. Multiple items allowed for provider(s): oecd.", + "default": "united_states", + "optional": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "eu27", + "euro_area", + "euro_area_19_countries", + "euro_area_20_countries", + "finland", + "france", + "g20", + "g7", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "oecd", + "oecd_europe", + "poland", + "portugal", + "russia", + "saudi_arabia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ], + "multiple_items_allowed": false, + "json_schema_extra": { + "oecd": { + "multiple_items_allowed": true, + "choices": [ + "argentina", + "australia", + "austria", + "belgium", + "brazil", + "canada", + "chile", + "china", + "colombia", + "costa_rica", + "croatia", + "czechia", + "denmark", + "estonia", + "eu27", + "euro_area", + "euro_area_19_countries", + "euro_area_20_countries", + "finland", + "france", + "g20", + "g7", + "germany", + "greece", + "hungary", + "iceland", + "india", + "indonesia", + "ireland", + "israel", + "italy", + "japan", + "korea", + "latvia", + "lithuania", + "luxembourg", + "mexico", + "netherlands", + "new_zealand", + "norway", + "oecd", + "oecd_europe", + "poland", + "portugal", + "russia", + "saudi_arabia", + "slovak_republic", + "slovenia", + "south_africa", + "spain", + "sweden", + "switzerland", + "turkiye", + "united_kingdom", + "united_states", + "all" + ] + } + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "oecd": [ + { + "name": "duration", + "type": "Literal['immediate', 'short', 'long'] | None", + "description": "Duration of the interest rate. 'immediate' is the overnight rate, 'short' is the 3-month rate, and 'long' is the 10-year rate.", + "default": "short", + "optional": true, + "choices": [ + "immediate", + "short", + "long" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "frequency", + "type": "Literal['monthly', 'quarter', 'annual'] | None", + "description": "Frequency to get interest rate for for.", + "default": "monthly", + "optional": true, + "choices": [ + "monthly", + "quarter", + "annual" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "CountryInterestRates", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['oecd'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | None | str", + "description": "The date of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "float | None", + "description": "The interest rate value.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurment": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "country", + "type": "str | None", + "description": "Country for which the interest rate is given.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "oecd": [] + }, + "model": "CountryInterestRates", + "openapi_extra": { + "model": "CountryInterestRates" + } + }, + "/equity/calendar/ipo": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical and upcoming initial public offerings (IPOs).", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | None", + "description": "Symbol to get data for.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": 100, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "CalendarIpo", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str | None", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ipo_date", + "type": "date | None", + "description": "The date of the IPO, when the stock first trades on a major exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "name", + "type": "str | None", + "description": "The name of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange", + "type": "str | None", + "description": "The exchange where the company will be listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "filing_date", + "type": "date | None", + "description": "The date of the IPO filing.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_from", + "type": "float | None", + "description": "The low end of the expected price range.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_to", + "type": "float | None", + "description": "The high end of the expected price range.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float | None", + "description": "The offer price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "The currency of the offer.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "shares", + "type": "float | None", + "description": "The number of shares.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "action", + "type": "str | None", + "description": "The status of the IPO (e.g. Expected, Priced).", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "CalendarIpo", + "openapi_extra": { + "model": "CalendarIpo" + } + }, + "/equity/calendar/splits": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical and upcoming stock split operations.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "CalendarSplits", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "numerator", + "type": "float", + "description": "Numerator of the stock split.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "denominator", + "type": "float", + "description": "Denominator of the stock split.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "symbol", + "type": "str | None", + "description": "The ticker symbol.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "optionable", + "type": "bool | None", + "description": "Whether the company has options.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "CalendarSplits", + "openapi_extra": { + "model": "CalendarSplits" + } + }, + "/equity/calendar/earnings": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical and upcoming company earnings releases. Includes earnings per share (EPS) and revenue data.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "The maximum number of results to return. Yahoo caps at 100.", + "default": 100, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "CalendarEarnings", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "report_date", + "type": "date", + "description": "The date of the earnings report.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_previous", + "type": "float | None", + "description": "The earnings-per-share from the same previously reported period.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_consensus", + "type": "float | None", + "description": "The analyst conesus earnings-per-share estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "market_cap", + "type": "float | None", + "description": "Market capitalization of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "timing", + "type": "str | None", + "description": "The timing of the earnings event (e.g. BMO, AMC).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_surprise_percent", + "type": "float | None", + "description": "The earnings surprise, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 1 + } + } + ] + }, + "model": "CalendarEarnings", + "openapi_extra": { + "model": "CalendarEarnings" + } + }, + "/equity/estimates/price_target": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get analyst price targets by company.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | None | list[str | None]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + }, + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "PriceTarget", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "published_date", + "type": "date", + "description": "Published date of the price target.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "published_time", + "type": "datetime.time | None", + "description": "Time of the original rating, UTC.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "exchange", + "type": "str | None", + "description": "Exchange where the company is traded.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "company_name", + "type": "str | None", + "description": "Name of company that is the subject of rating.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "analyst_name", + "type": "str | None", + "description": "Analyst name.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "analyst_firm", + "type": "str | None", + "description": "Name of the analyst firm that published the price target.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency the data is denominated in.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_target", + "type": "float | None", + "description": "The current price target.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "adj_price_target", + "type": "float | None", + "description": "Adjusted price target for splits and stock dividends.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_target_previous", + "type": "float | None", + "description": "Previous price target.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "previous_adj_price_target", + "type": "float | None", + "description": "Previous adjusted price target.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_when_posted", + "type": "float | None", + "description": "Price when posted.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "rating_current", + "type": "str | None", + "description": "The analyst's rating for the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "rating_previous", + "type": "str | None", + "description": "Previous analyst rating for the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "action", + "type": "str | None", + "description": "Description of the change in rating from firm's last rating.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "price_target_action", + "type": "str | None", + "description": "The price target action (e.g. up, down, init).", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "PriceTarget", + "openapi_extra": { + "model": "PriceTarget" + } + }, + "/equity/estimates/consensus": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get consensus price target and recommendation.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.estimates.consensus(symbol='AAPL,MSFT', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | None | list[str | None]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "PriceTargetConsensus", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The company name", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "target_high", + "type": "float | None", + "description": "High target of the price target consensus.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "target_low", + "type": "float | None", + "description": "Low target of the price target consensus.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "target_consensus", + "type": "float | None", + "description": "Consensus target of the price target consensus.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "target_median", + "type": "float | None", + "description": "Median target of the price target consensus.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "recommendation", + "type": "str | None", + "description": "Recommendation - buy, sell, etc.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "recommendation_mean", + "type": "float | None", + "description": "Mean recommendation score where 1 is strong buy and 5 is strong sell.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "number_of_analysts", + "type": "int | None", + "description": "Number of analysts providing opinions.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "current_price", + "type": "float | None", + "description": "Current price of the stock.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency the stock is priced in.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "PriceTargetConsensus", + "openapi_extra": { + "model": "PriceTargetConsensus" + } + }, + "/equity/discovery/gainers": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the top price gainers in the stock market.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.discovery.gainers(provider='yfinance')\nobb.equity.discovery.gainers(sort='desc', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "sort", + "type": "Literal['asc', 'desc'] | None", + "description": "Sort order. Possible values: 'asc', 'desc'. Default: 'desc'.", + "default": "desc", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "Limit the number of results.", + "default": 200, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityGainers", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Last price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "change", + "type": "float", + "description": "Change in price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "percent_change", + "type": "float", + "description": "Percent change.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "open", + "type": "float | None", + "description": "Open price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "high", + "type": "float | None", + "description": "High price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "low", + "type": "float | None", + "description": "Low price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "previous_close", + "type": "float | None", + "description": "Previous close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma50", + "type": "float | None", + "description": "50-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma200", + "type": "float | None", + "description": "200-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "52-week high.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_low", + "type": "float | None", + "description": "52-week low.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "market_cap", + "type": "float | None", + "description": "Market Cap.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "shares_outstanding", + "type": "float | None", + "description": "Shares outstanding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "book_value", + "type": "float | None", + "description": "Book value per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Price to book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_ttm", + "type": "float | None", + "description": "Earnings per share over the trailing twelve months.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "eps_forward", + "type": "float | None", + "description": "Forward earnings per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "pe_forward", + "type": "float | None", + "description": "Forward price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "Trailing twelve month dividend yield.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "frontend_multiply": 100 + } + }, + { + "name": "exchange", + "type": "str | None", + "description": "Exchange where the stock is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_timezone", + "type": "str | None", + "description": "Timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_date", + "type": "datetime | None", + "description": "Most recent earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the price data.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityGainers", + "openapi_extra": { + "model": "EquityGainers" + } + }, + "/equity/discovery/losers": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the top price losers in the stock market.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.discovery.losers(provider='yfinance')\nobb.equity.discovery.losers(sort='desc', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "sort", + "type": "Literal['asc', 'desc'] | None", + "description": "Sort order. Possible values: 'asc', 'desc'. Default: 'desc'.", + "default": "desc", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "Limit the number of results.", + "default": 200, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityLosers", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Last price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "change", + "type": "float", + "description": "Change in price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "percent_change", + "type": "float", + "description": "Percent change.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "open", + "type": "float | None", + "description": "Open price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "high", + "type": "float | None", + "description": "High price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "low", + "type": "float | None", + "description": "Low price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "previous_close", + "type": "float | None", + "description": "Previous close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma50", + "type": "float | None", + "description": "50-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma200", + "type": "float | None", + "description": "200-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "52-week high.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_low", + "type": "float | None", + "description": "52-week low.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "market_cap", + "type": "float | None", + "description": "Market Cap.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "shares_outstanding", + "type": "float | None", + "description": "Shares outstanding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "book_value", + "type": "float | None", + "description": "Book value per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Price to book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_ttm", + "type": "float | None", + "description": "Earnings per share over the trailing twelve months.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "eps_forward", + "type": "float | None", + "description": "Forward earnings per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "pe_forward", + "type": "float | None", + "description": "Forward price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "Trailing twelve month dividend yield.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "frontend_multiply": 100 + } + }, + { + "name": "exchange", + "type": "str | None", + "description": "Exchange where the stock is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_timezone", + "type": "str | None", + "description": "Timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_date", + "type": "datetime | None", + "description": "Most recent earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the price data.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityLosers", + "openapi_extra": { + "model": "EquityLosers" + } + }, + "/equity/discovery/active": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the most actively traded stocks based on volume.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.discovery.active(provider='yfinance')\nobb.equity.discovery.active(sort='desc', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "sort", + "type": "Literal['asc', 'desc'] | None", + "description": "Sort order. Possible values: 'asc', 'desc'. Default: 'desc'.", + "default": "desc", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "Limit the number of results.", + "default": 200, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityActive", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Last price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "change", + "type": "float", + "description": "Change in price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "percent_change", + "type": "float", + "description": "Percent change.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "open", + "type": "float | None", + "description": "Open price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "high", + "type": "float | None", + "description": "High price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "low", + "type": "float | None", + "description": "Low price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "previous_close", + "type": "float | None", + "description": "Previous close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma50", + "type": "float | None", + "description": "50-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma200", + "type": "float | None", + "description": "200-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "52-week high.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_low", + "type": "float | None", + "description": "52-week low.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "market_cap", + "type": "float | None", + "description": "Market Cap.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "shares_outstanding", + "type": "float | None", + "description": "Shares outstanding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "book_value", + "type": "float | None", + "description": "Book value per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Price to book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_ttm", + "type": "float | None", + "description": "Earnings per share over the trailing twelve months.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "eps_forward", + "type": "float | None", + "description": "Forward earnings per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "pe_forward", + "type": "float | None", + "description": "Forward price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "Trailing twelve month dividend yield.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "frontend_multiply": 100 + } + }, + { + "name": "exchange", + "type": "str | None", + "description": "Exchange where the stock is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_timezone", + "type": "str | None", + "description": "Timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_date", + "type": "datetime | None", + "description": "Most recent earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the price data.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityActive", + "openapi_extra": { + "model": "EquityActive" + } + }, + "/equity/discovery/undervalued_large_caps": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get potentially undervalued large cap stocks.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.discovery.undervalued_large_caps(provider='yfinance')\nobb.equity.discovery.undervalued_large_caps(sort='desc', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "sort", + "type": "Literal['asc', 'desc'] | None", + "description": "Sort order. Possible values: 'asc', 'desc'. Default: 'desc'.", + "default": "desc", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "Limit the number of results.", + "default": 200, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityUndervaluedLargeCaps", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Last price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "change", + "type": "float", + "description": "Change in price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "percent_change", + "type": "float", + "description": "Percent change.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "open", + "type": "float | None", + "description": "Open price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "high", + "type": "float | None", + "description": "High price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "low", + "type": "float | None", + "description": "Low price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "previous_close", + "type": "float | None", + "description": "Previous close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma50", + "type": "float | None", + "description": "50-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma200", + "type": "float | None", + "description": "200-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "52-week high.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_low", + "type": "float | None", + "description": "52-week low.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "market_cap", + "type": "float | None", + "description": "Market Cap.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "shares_outstanding", + "type": "float | None", + "description": "Shares outstanding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "book_value", + "type": "float | None", + "description": "Book value per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Price to book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_ttm", + "type": "float | None", + "description": "Earnings per share over the trailing twelve months.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "eps_forward", + "type": "float | None", + "description": "Forward earnings per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "pe_forward", + "type": "float | None", + "description": "Forward price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "Trailing twelve month dividend yield.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "frontend_multiply": 100 + } + }, + { + "name": "exchange", + "type": "str | None", + "description": "Exchange where the stock is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_timezone", + "type": "str | None", + "description": "Timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_date", + "type": "datetime | None", + "description": "Most recent earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the price data.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityUndervaluedLargeCaps", + "openapi_extra": { + "model": "EquityUndervaluedLargeCaps" + } + }, + "/equity/discovery/undervalued_growth": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get potentially undervalued growth stocks.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.discovery.undervalued_growth(provider='yfinance')\nobb.equity.discovery.undervalued_growth(sort='desc', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "sort", + "type": "Literal['asc', 'desc'] | None", + "description": "Sort order. Possible values: 'asc', 'desc'. Default: 'desc'.", + "default": "desc", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "Limit the number of results.", + "default": 200, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityUndervaluedGrowth", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Last price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "change", + "type": "float", + "description": "Change in price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "percent_change", + "type": "float", + "description": "Percent change.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "open", + "type": "float | None", + "description": "Open price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "high", + "type": "float | None", + "description": "High price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "low", + "type": "float | None", + "description": "Low price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "previous_close", + "type": "float | None", + "description": "Previous close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma50", + "type": "float | None", + "description": "50-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma200", + "type": "float | None", + "description": "200-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "52-week high.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_low", + "type": "float | None", + "description": "52-week low.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "market_cap", + "type": "float | None", + "description": "Market Cap.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "shares_outstanding", + "type": "float | None", + "description": "Shares outstanding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "book_value", + "type": "float | None", + "description": "Book value per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Price to book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_ttm", + "type": "float | None", + "description": "Earnings per share over the trailing twelve months.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "eps_forward", + "type": "float | None", + "description": "Forward earnings per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "pe_forward", + "type": "float | None", + "description": "Forward price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "Trailing twelve month dividend yield.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "frontend_multiply": 100 + } + }, + { + "name": "exchange", + "type": "str | None", + "description": "Exchange where the stock is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_timezone", + "type": "str | None", + "description": "Timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_date", + "type": "datetime | None", + "description": "Most recent earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the price data.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityUndervaluedGrowth", + "openapi_extra": { + "model": "EquityUndervaluedGrowth" + } + }, + "/equity/discovery/aggressive_small_caps": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get top small cap stocks based on earnings growth.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.discovery.aggressive_small_caps(provider='yfinance')\nobb.equity.discovery.aggressive_small_caps(sort='desc', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "sort", + "type": "Literal['asc', 'desc'] | None", + "description": "Sort order. Possible values: 'asc', 'desc'. Default: 'desc'.", + "default": "desc", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "Limit the number of results. Default is all.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityAggressiveSmallCaps", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Last price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "change", + "type": "float", + "description": "Change in price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "percent_change", + "type": "float", + "description": "Percent change.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "open", + "type": "float | None", + "description": "Open price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "high", + "type": "float | None", + "description": "High price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "low", + "type": "float | None", + "description": "Low price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "previous_close", + "type": "float | None", + "description": "Previous close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma50", + "type": "float | None", + "description": "50-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma200", + "type": "float | None", + "description": "200-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "52-week high.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_low", + "type": "float | None", + "description": "52-week low.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "market_cap", + "type": "float | None", + "description": "Market Cap.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "shares_outstanding", + "type": "float | None", + "description": "Shares outstanding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "book_value", + "type": "float | None", + "description": "Book value per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Price to book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_ttm", + "type": "float | None", + "description": "Earnings per share over the trailing twelve months.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "eps_forward", + "type": "float | None", + "description": "Forward earnings per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "pe_forward", + "type": "float | None", + "description": "Forward price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "Trailing twelve month dividend yield.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "frontend_multiply": 100 + } + }, + { + "name": "exchange", + "type": "str | None", + "description": "Exchange where the stock is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_timezone", + "type": "str | None", + "description": "Timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_date", + "type": "datetime | None", + "description": "Most recent earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the price data.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityAggressiveSmallCaps", + "openapi_extra": { + "model": "EquityAggressiveSmallCaps" + } + }, + "/equity/discovery/growth_tech": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get top tech stocks based on revenue and earnings growth.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.discovery.growth_tech(provider='yfinance')\nobb.equity.discovery.growth_tech(sort='desc', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "sort", + "type": "Literal['asc', 'desc'] | None", + "description": "Sort order. Possible values: 'asc', 'desc'. Default: 'desc'.", + "default": "desc", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "Limit the number of results.", + "default": 200, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "GrowthTechEquities", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Last price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "change", + "type": "float", + "description": "Change in price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "percent_change", + "type": "float", + "description": "Percent change.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "open", + "type": "float | None", + "description": "Open price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "high", + "type": "float | None", + "description": "High price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "low", + "type": "float | None", + "description": "Low price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "previous_close", + "type": "float | None", + "description": "Previous close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma50", + "type": "float | None", + "description": "50-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma200", + "type": "float | None", + "description": "200-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "52-week high.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_low", + "type": "float | None", + "description": "52-week low.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "market_cap", + "type": "float | None", + "description": "Market Cap.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "shares_outstanding", + "type": "float | None", + "description": "Shares outstanding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "book_value", + "type": "float | None", + "description": "Book value per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Price to book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_ttm", + "type": "float | None", + "description": "Earnings per share over the trailing twelve months.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "eps_forward", + "type": "float | None", + "description": "Forward earnings per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "pe_forward", + "type": "float | None", + "description": "Forward price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "Trailing twelve month dividend yield.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "frontend_multiply": 100 + } + }, + { + "name": "exchange", + "type": "str | None", + "description": "Exchange where the stock is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_timezone", + "type": "str | None", + "description": "Timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_date", + "type": "datetime | None", + "description": "Most recent earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the price data.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "GrowthTechEquities", + "openapi_extra": { + "model": "GrowthTechEquities" + } + }, + "/equity/fundamental/balance": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the balance sheet for a given company.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol to get data for.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": null, + "optional": true, + "choices": null, + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": 5, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "Literal['annual', 'quarter'] | None", + "description": "Time period of the data to return.", + "default": "annual", + "optional": true, + "choices": [ + "annual", + "quarter" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "BalanceSheet", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "period_ending", + "type": "date", + "description": "The end date of the reporting period.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "fiscal_period", + "type": "str | None", + "description": "The fiscal period of the report.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "fiscal_year", + "type": "int | None", + "description": "The fiscal year of the fiscal period.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "model": "BalanceSheet", + "openapi_extra": { + "model": "BalanceSheet" + } + }, + "/equity/fundamental/cash": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the cash flow statement for a given company.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol to get data for.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": 5, + "optional": true, + "choices": null, + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": 5, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "Literal['annual', 'quarter'] | None", + "description": "Time period of the data to return.", + "default": "annual", + "optional": true, + "choices": [ + "annual", + "quarter" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "CashFlowStatement", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "period_ending", + "type": "date", + "description": "The end date of the reporting period.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "fiscal_period", + "type": "str | None", + "description": "The fiscal period of the report.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "fiscal_year", + "type": "int | None", + "description": "The fiscal year of the fiscal period.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "model": "CashFlowStatement", + "openapi_extra": { + "model": "CashFlowStatement" + } + }, + "/equity/fundamental/dividends": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical dividend data for a given company.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol to get data for.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "HistoricalDividends", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str | None", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ex_dividend_date", + "type": "date", + "description": "The ex-dividend date - the date on which the stock begins trading without rights to the dividend.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "amount", + "type": "float", + "description": "The dividend amount per share.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "model": "HistoricalDividends", + "openapi_extra": { + "model": "HistoricalDividends" + } + }, + "/equity/fundamental/historical_eps": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical earnings per share data for a given company.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "The number of earnings dates to return.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "HistoricalEps", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "eps_actual", + "type": "int | None", + "description": "Actual EPS from the earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_estimated", + "type": "int | None", + "description": "Estimated EPS for the earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "surprise_percent", + "type": "float | None", + "description": "The earnings surprise, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 1 + } + } + ] + }, + "model": "HistoricalEps", + "openapi_extra": { + "model": "HistoricalEps" + } + }, + "/equity/fundamental/income": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the income statement for a given company.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol to get data for.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": null, + "optional": true, + "choices": null, + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": 5, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "Literal['annual', 'quarter'] | None", + "description": "Time period of the data to return.", + "default": "annual", + "optional": true, + "choices": [ + "annual", + "quarter" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "IncomeStatement", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "period_ending", + "type": "date", + "description": "The end date of the reporting period.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "fiscal_period", + "type": "str | None", + "description": "The fiscal period of the report.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "fiscal_year", + "type": "int | None", + "description": "The fiscal year of the fiscal period.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "model": "IncomeStatement", + "openapi_extra": { + "model": "IncomeStatement" + } + }, + "/equity/fundamental/metrics": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get fundamental metrics for a given company.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "KeyMetrics", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "period_ending", + "type": "date | None", + "description": "End date of the reporting period.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "fiscal_year", + "type": "int | None", + "description": "Fiscal year for the fiscal period, if available.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "fiscal_period", + "type": "str | None", + "description": "Fiscal period for the data, if available.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency in which the data is reported.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "market_cap", + "type": "int | None", + "description": "", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "currency", + "type": "str | None", + "description": "Currency in which the data is presented.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "pe_ratio", + "type": "float | None", + "description": "Price-to-earnings ratio (TTM).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "forward_pe", + "type": "float | None", + "description": "Forward price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "peg_ratio", + "type": "float | None", + "description": "PEG ratio (5-year expected).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "peg_ratio_ttm", + "type": "float | None", + "description": "PEG ratio (TTM).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_ttm", + "type": "float | None", + "description": "Earnings per share (TTM).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_forward", + "type": "float | None", + "description": "Forward earnings per share.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "enterprise_to_ebitda", + "type": "float | None", + "description": "Enterprise value to EBITDA ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_growth", + "type": "float | None", + "description": "Earnings growth (Year Over Year), as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "earnings_growth_quarterly", + "type": "float | None", + "description": "Quarterly earnings growth (Year Over Year), as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "revenue_per_share", + "type": "float | None", + "description": "Revenue per share (TTM).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "revenue_growth", + "type": "float | None", + "description": "Revenue growth (Year Over Year), as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "enterprise_to_revenue", + "type": "float | None", + "description": "Enterprise value to revenue ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "cash_per_share", + "type": "float | None", + "description": "Cash per share.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "quick_ratio", + "type": "float | None", + "description": "Quick ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "current_ratio", + "type": "float | None", + "description": "Current ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "debt_to_equity", + "type": "float | None", + "description": "Debt-to-equity ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "gross_margin", + "type": "float | None", + "description": "Gross margin, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "operating_margin", + "type": "float | None", + "description": "Operating margin, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "ebitda_margin", + "type": "float | None", + "description": "EBITDA margin, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "profit_margin", + "type": "float | None", + "description": "Profit margin, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "return_on_assets", + "type": "float | None", + "description": "Return on assets, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "return_on_equity", + "type": "float | None", + "description": "Return on equity, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "Dividend yield, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "dividend_yield_5y_avg", + "type": "float | None", + "description": "5-year average dividend yield, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "payout_ratio", + "type": "float | None", + "description": "Payout ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "book_value", + "type": "float | None", + "description": "Book value per share.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Price-to-book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "enterprise_value", + "type": "int | None", + "description": "Enterprise value.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "overall_risk", + "type": "float | None", + "description": "Overall risk score.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "audit_risk", + "type": "float | None", + "description": "Audit risk score.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "board_risk", + "type": "float | None", + "description": "Board risk score.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "compensation_risk", + "type": "float | None", + "description": "Compensation risk score.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "shareholder_rights_risk", + "type": "float | None", + "description": "Shareholder rights risk score.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "beta", + "type": "float | None", + "description": "Beta relative to the broad market (5-year monthly).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_return_1y", + "type": "float | None", + "description": "One-year price return, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + } + ] + }, + "model": "KeyMetrics", + "openapi_extra": { + "model": "KeyMetrics" + } + }, + "/equity/fundamental/management": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get executive management team data for a given company.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol to get data for.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "KeyExecutives", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "title", + "type": "str", + "description": "Designation of the key executive.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str", + "description": "Name of the key executive.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "pay", + "type": "int | None", + "description": "Pay of the key executive.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency_pay", + "type": "str | None", + "description": "Currency of the pay.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "gender", + "type": "str | None", + "description": "Gender of the key executive.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "year_born", + "type": "int | None", + "description": "Birth year of the key executive.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "exercised_value", + "type": "int | None", + "description": "Value of shares exercised.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "unexercised_value", + "type": "int | None", + "description": "Value of shares not exercised.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "fiscal_year", + "type": "int | None", + "description": "Fiscal year of the pay.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "KeyExecutives", + "openapi_extra": { + "model": "KeyExecutives" + } + }, + "/equity/fundamental/filings": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get public company filings.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | None | list[str | None]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "CompanyFilings", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "filing_date", + "type": "date", + "description": "The date of the filing.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "report_type", + "type": "str | None", + "description": "Type of filing.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "report_url", + "type": "str", + "description": "URL to the actual report.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "symbol", + "type": "str | None", + "description": "The ticker symbol.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "title", + "type": "str | None", + "description": "The title of the filing.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "CompanyFilings", + "openapi_extra": { + "model": "CompanyFilings" + } + }, + "/equity/ownership/insider_trading": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get data about trading by a company's management team and board of directors.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + }, + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "InsiderTrading", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str | None", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "company_cik", + "type": "str | None", + "description": "CIK number of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "filing_date", + "type": "date | None", + "description": "Filing date of the trade.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "transaction_date", + "type": "date | None", + "description": "Date of the transaction.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "owner_cik", + "type": "int | None", + "description": "Reporting individual's CIK.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "owner_name", + "type": "str | None", + "description": "Name of the reporting individual.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "owner_title", + "type": "str | None", + "description": "The title held by the reporting individual.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ownership_type", + "type": "str | None", + "description": "Type of ownership, e.g., direct or indirect.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "transaction_type", + "type": "str | None", + "description": "Type of transaction being reported.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "acquisition_or_disposition", + "type": "str | None", + "description": "Acquisition or disposition of the shares.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "security_type", + "type": "str | None", + "description": "The type of security transacted.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "securities_owned", + "type": "float | None", + "description": "Number of securities owned by the reporting individual.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "securities_transacted", + "type": "float | None", + "description": "Number of securities transacted by the reporting individual.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "transaction_price", + "type": "float | None", + "description": "The price of the transaction.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "filing_url", + "type": "str | None", + "description": "Link to the filing.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "transaction_value", + "type": "float | None", + "description": "Value of the transaction.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "description", + "type": "str | None", + "description": "Description of the transaction.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "InsiderTrading", + "openapi_extra": { + "model": "InsiderTrading" + } + }, + "/equity/ownership/share_statistics": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get data about share float for a given company.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "ShareStatistics", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "date", + "type": "date | None | str", + "description": "The date of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "free_float", + "type": "float | None", + "description": "Percentage of unrestricted shares of a publicly-traded company.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "float_shares", + "type": "int | None", + "description": "Number of shares available for trading by the general public.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "outstanding_shares", + "type": "int | None", + "description": "Total number of shares of a publicly-traded company.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "implied_shares_outstanding", + "type": "int | None", + "description": "Implied Shares Outstanding of common equity, assuming the conversion of all convertible subsidiary equity into common.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "short_interest", + "type": "int | None", + "description": "Number of shares that are reported short.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "short_percent_of_float", + "type": "float | None", + "description": "Percentage of shares that are reported short, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "days_to_cover", + "type": "float | None", + "description": "Number of days to repurchase the shares as a ratio of average daily volume", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "short_interest_prev_month", + "type": "int | None", + "description": "Number of shares that were reported short in the previous month.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "short_interest_prev_date", + "type": "date | None", + "description": "Date of the previous month's report.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "insider_ownership", + "type": "float | None", + "description": "Percentage of shares held by insiders, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "institution_ownership", + "type": "float | None", + "description": "Percentage of shares held by institutions, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "institution_float_ownership", + "type": "float | None", + "description": "Percentage of float held by institutions, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "institutions_count", + "type": "int | None", + "description": "Number of institutions holding shares.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "ShareStatistics", + "openapi_extra": { + "model": "ShareStatistics" + } + }, + "/equity/price/quote": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the latest quote for a given stock. Quote includes price, volume, and other data.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityQuote", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "asset_type", + "type": "str | None", + "description": "Type of asset - i.e, stock, ETF, etc.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the company or asset.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange", + "type": "str | None", + "description": "The name or symbol of the venue where the data is from.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bid", + "type": "float | None", + "description": "Price of the top bid order.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bid_size", + "type": "int | None", + "description": "This represents the number of round lot orders at the given price. The normal round lot size is 100 shares. A size of 2 means there are 200 shares available at the given price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bid_exchange", + "type": "str | None", + "description": "The specific trading venue where the purchase order was placed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ask", + "type": "float | None", + "description": "Price of the top ask order.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ask_size", + "type": "int | None", + "description": "This represents the number of round lot orders at the given price. The normal round lot size is 100 shares. A size of 2 means there are 200 shares available at the given price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ask_exchange", + "type": "str | None", + "description": "The specific trading venue where the sale order was placed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "quote_conditions", + "type": "str | None", + "description": "Conditions or condition codes applicable to the quote.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "quote_indicators", + "type": "str | None", + "description": "Indicators or indicator codes applicable to the participant quote related to the price bands for the issue, or the affect the quote has on the NBBO.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sales_conditions", + "type": "str | None", + "description": "Conditions or condition codes applicable to the sale.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sequence_number", + "type": "int | None", + "description": "The sequence number represents the sequence in which message events happened. These are increasing and unique per ticker symbol, but will not always be sequential (e.g., 1, 2, 6, 9, 10, 11).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "market_center", + "type": "str | None", + "description": "The ID of the UTP participant that originated the message.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "participant_timestamp", + "type": "datetime | None", + "description": "Timestamp for when the quote was generated by the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "trf_timestamp", + "type": "datetime | None", + "description": "Timestamp for when the TRF (Trade Reporting Facility) received the message.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sip_timestamp", + "type": "datetime | None", + "description": "Timestamp for when the SIP (Security Information Processor) received the message from the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "last_price", + "type": "float | None", + "description": "Price of the last trade.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "last_tick", + "type": "str | None", + "description": "Whether the last sale was an up or down tick.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "last_size", + "type": "int | None", + "description": "Size of the last trade.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "last_timestamp", + "type": "datetime | None", + "description": "Date and Time when the last price was recorded.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "open", + "type": "float | None", + "description": "The open price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float | None", + "description": "The high price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float | None", + "description": "The low price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close", + "type": "float | None", + "description": "The close price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_volume", + "type": "int | None", + "description": "Volume of shares exchanged during the trading day on the specific exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "prev_close", + "type": "float | None", + "description": "The previous close price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "change", + "type": "float | None", + "description": "Change in price from previous close.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "change_percent", + "type": "float | None", + "description": "Change in price as a normalized percentage.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "The one year high (52W High).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "year_low", + "type": "float | None", + "description": "The one year low (52W Low).", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "ma_50d", + "type": "float | None", + "description": "50-day moving average price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ma_200d", + "type": "float | None", + "description": "200-day moving average price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volume_average", + "type": "float | None", + "description": "Average daily trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volume_average_10d", + "type": "float | None", + "description": "Average daily trading volume in the last 10 days.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the price.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityQuote", + "openapi_extra": { + "model": "EquityQuote" + } + }, + "/equity/price/historical": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical price data for a given stock. This includes open, high, low, close, and volume.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "interval", + "type": "Literal['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1W', '1M', '1Q'] | None", + "description": "Time interval of the data to return.", + "default": "1d", + "optional": true, + "choices": [ + "1m", + "2m", + "5m", + "15m", + "30m", + "60m", + "90m", + "1h", + "1d", + "5d", + "1W", + "1M", + "1Q" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "extended_hours", + "type": "bool | None", + "description": "Include Pre and Post market data.", + "default": false, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "include_actions", + "type": "bool | None", + "description": "Include dividends and stock splits in results.", + "default": true, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "adjustment", + "type": "Literal['splits_only', 'splits_and_dividends'] | None", + "description": "The adjustment factor to apply. Default is splits only.", + "default": "splits_only", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityHistorical", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "open", + "type": "float", + "description": "The open price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float", + "description": "The high price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float", + "description": "The low price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "close", + "type": "float", + "description": "The close price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "float | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "vwap", + "type": "float | None", + "description": "Volume Weighted Average Price over the period.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "split_ratio", + "type": "float | None", + "description": "Ratio of the equity split, if a split occurred.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend", + "type": "float | None", + "description": "Dividend amount (split-adjusted), if a dividend was paid.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityHistorical", + "openapi_extra": { + "model": "EquityHistorical" + } + }, + "/equity/screener": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Screen for companies meeting various criteria.\n\nThese criteria include market cap, price, beta, volume, and dividend yield.", + "examples": "", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "asset_type", + "type": "Literal['equity', 'etf', 'fund', 'index', 'future'] | None", + "description": "Asset type to screen. Selects the Yahoo Finance query universe; 'fund' maps to mutual funds. Sector and industry apply to 'equity'; fund_issuer and fund_style apply to 'etf' and 'fund'.", + "default": "equity", + "optional": true, + "choices": [ + "equity", + "etf", + "fund", + "index", + "future" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "universe", + "type": "bool | None", + "description": "Pull every matching symbol for the given filters, bypassing the market-cap/price/volume floors and the liquidity filter. Use it to enumerate a full region/exchange/sector/industry/fund-issuer/fund-style universe. Requires at least one filter.", + "default": false, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "fund_issuer", + "type": "str | None", + "description": "Filter funds and ETFs by fund family (issuer), e.g. 'Vanguard'.", + "default": null, + "optional": true, + "choices": [ + "ALPS", + "AMG Funds", + "AQR Funds", + "Aberdeen", + "Alger", + "AllianceBernstein", + "Allianz Funds", + "American Beacon", + "American Century Investments", + "American Funds", + "Aquila", + "Artisan", + "BMO Funds", + "BNY Mellon Funds", + "Baird", + "Barclays Funds", + "Barings Funds", + "Baron Capital Group", + "BlackRock", + "Brown Advisory Funds", + "Calamos", + "Calvert Investments", + "Catalyst Mutual Funds", + "Cohen & Steers", + "Columbia", + "Commerz Funds Solutions SA", + "Commerzbank AG, Frankfurt am Main", + "Davis Funds", + "Delaware Investments", + "Deutsche Asset Management", + "Deutsche Bank AG", + "Diamond Hill Funds", + "Dimensional Fund Advisors", + "Direxion Funds", + "DoubleLine", + "Dreyfus", + "Dunham Funds", + "Eagle Funds", + "Eaton Vance", + "Federated", + "Fidelity Investments", + "First Investors", + "First Trust", + "Flexshares Trust", + "Franklin Templeton Investments", + "GMO", + "Gabelli", + "Global X Funds", + "Goldman Sachs", + "Great-West Funds", + "Guggenheim Investments", + "GuideStone Funds", + "HSBC", + "Hancock Horizon", + "Harbor", + "Hartford Mutual Funds", + "Henderson Global", + "Hennessy", + "Highland Funds", + "ICON Funds", + "Invesco", + "Ivy Funds", + "JPMorgan", + "Janus", + "John Hancock", + "Lazard", + "Legg Mason", + "Lord Abbett", + "MFS", + "Madison Funds", + "MainStay", + "Manning & Napier", + "Market Vectors", + "MassMutual", + "Matthews Asia Funds", + "Morgan Stanley", + "Nationwide", + "Natixis Funds", + "Neuberger Berman", + "Northern Funds", + "Nuveen", + "OppenheimerFunds", + "PNC Funds", + "Pacific funds series trust", + "Pax World", + "Paydenfunds", + "Pimco", + "Pioneer Investments", + "PowerShares", + "Principal Funds", + "ProFunds", + "ProShares", + "Prudential Investments", + "Putnam", + "RBC Global Asset Management.", + "RidgeWorth", + "Royce", + "Russell", + "Rydex Funds", + "SEI", + "SPDR State Street Global Advisors", + "Salient Funds", + "Saratoga", + "Schwab Funds", + "Sentinel", + "Shelton Capital Management", + "State Farm", + "State Street Global Advisors (Chicago)", + "Sterling Capital Funds", + "SunAmerica", + "T. Rowe Price", + "TCW", + "TIAA-CREF Asset Management", + "Teton Westwood Funds", + "Thornburg", + "Thrivent", + "Timothy Plan", + "Touchstone", + "Transamerica", + "UBS", + "UBS Group AG", + "USAA", + "VALIC", + "Vanguard", + "Vantagepoint Funds", + "Victory", + "Virtus", + "Voya", + "Waddell & Reed", + "Wasatch", + "Wells Fargo Funds", + "William Blair", + "WisdomTree", + "iShares" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "fund_style", + "type": "str | None", + "description": "Filter funds and ETFs by category (style), e.g. 'Large Growth'.", + "default": null, + "optional": true, + "choices": [ + "Allocation--15% to 30% Equity", + "Allocation--30% to 50% Equity", + "Allocation--50% to 70% Equity", + "Allocation--70% to 85% Equity", + "Allocation--85%+ Equity", + "Bank Loan", + "Bear Market", + "China Region", + "Commodities Agriculture", + "Commodities Broad Basket", + "Convertibles", + "Corporate Bond", + "Diversified Emerging Mkts", + "Diversified Pacific/Asia", + "Emerging Markets Bond", + "Emerging-Markets Local-Currency Bond", + "Energy Limited Partnership", + "Equity Energy", + "Equity Precious Metals", + "Europe Stock", + "Financial", + "Foreign Large Blend", + "Foreign Large Growth", + "Foreign Large Value", + "Foreign Small/Mid Blend", + "Foreign Small/Mid Growth", + "Foreign Small/Mid Value", + "Global Real Estate", + "Health", + "High Yield Bond", + "High Yield Muni", + "Inflation-Protected Bond", + "Infrastructure", + "Intermediate Government", + "Intermediate-Term Bond", + "Japan Stock", + "Large Blend", + "Large Growth", + "Large Value", + "Long Government", + "Long-Short Credit", + "Long-Short Equity", + "Long-Term Bond", + "Managed Futures", + "Market Neutral", + "Mid-Cap Blend", + "Mid-Cap Growth", + "Mid-Cap Value", + "Miscellaneous Region", + "Multialternative", + "Multicurrency", + "Multisector Bond", + "Muni California Intermediate", + "Muni California Long", + "Muni Massachusetts", + "Muni Minnesota", + "Muni National Interm", + "Muni National Long", + "Muni National Short", + "Muni New Jersey", + "Muni New York Intermediate", + "Muni New York Long", + "Muni Ohio", + "Muni Pennsylvania", + "Muni Single State Interm", + "Muni Single State Long", + "Muni Single State Short", + "Natural Resources", + "Nontraditional Bond", + "Option Writing", + "Other", + "Other Allocation", + "Pacific/Asia ex-Japan Stk", + "Preferred Stock", + "Real Estate", + "Short Government", + "Short-Term Bond", + "Small Blend", + "Small Growth", + "Small Value", + "Tactical Allocation", + "Target-Date 2000-2010", + "Target-Date 2015", + "Target-Date 2020", + "Target-Date 2025", + "Target-Date 2030", + "Target-Date 2035", + "Target-Date 2040", + "Target-Date 2045", + "Target-Date 2050", + "Target-Date 2055", + "Target-Date 2060+", + "Target-Date Retirement", + "Technology", + "Trading - Leveraged/Inverse Commodities", + "Trading - Leveraged/Inverse Equity", + "Trading--Inverse Equity", + "Trading--Leveraged Equity", + "Ultrashort Bond", + "Utilities", + "World Allocation", + "World Bond", + "World Stock" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "preset", + "type": "str | None", + "description": "Screener preset to run \u2014 either a Yahoo Finance predefined screener (e.g. 'most_actives', 'day_gainers', 'undervalued_large_caps') or the name of a screener preset INI file. The user presets directory is the 'OPENBB_YFINANCE_PRESETS_DIRECTORY' environment variable, or 'yfinance_presets_directory' / '[yfinance] presets_directory' in openbb.toml, defaulting to '/presets/yfinance/'. Bundled presets are copied there on first use and any user preset of the same name takes precedence. When set, the preset defines the full query and the other filter parameters are ignored.", + "default": null, + "optional": true, + "choices": [ + "large_cap_value", + "technology_growth", + "top_etfs", + "aggressive_small_caps", + "day_gainers", + "day_losers", + "growth_technology_stocks", + "most_actives", + "most_shorted_stocks", + "small_cap_gainers", + "undervalued_growth_stocks", + "undervalued_large_caps", + "conservative_foreign_funds", + "high_yield_bond", + "portfolio_anchors", + "solid_large_growth_funds", + "solid_midcap_growth_funds", + "top_mutual_funds" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "config", + "type": "str | None", + "description": "A JSON screener configuration built by the screener-builder widget: {type, limit, sort_field, sort_type, filters:[{field, operator, value}]}. When set, it defines the full query and the other filter parameters are ignored.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "Filter by country. Accepts ISO 3166-1 alpha-2 codes (e.g., 'US', 'DE'), alpha-3 codes (e.g., 'USA'), country names (e.g., 'United States'), or 'all' for all countries.", + "default": "us", + "optional": true, + "choices": [ + "all", + "ar", + "at", + "au", + "be", + "br", + "ca", + "ch", + "cl", + "cn", + "cz", + "de", + "dk", + "ee", + "eg", + "es", + "fi", + "fr", + "gb", + "gr", + "hk", + "hu", + "id", + "ie", + "il", + "in", + "is", + "it", + "jp", + "kr", + "kw", + "lk", + "lt", + "lv", + "mx", + "my", + "nl", + "no", + "nz", + "pe", + "ph", + "pk", + "pl", + "pt", + "qa", + "ro", + "ru", + "sa", + "se", + "sg", + "sr", + "th", + "tr", + "tw", + "us", + "ve", + "vn", + "za" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "exchange", + "type": "Literal['ams', 'aqs', 'ase', 'asx', 'ath', 'ber', 'bru', 'bse', 'bts', 'bud', 'bue', 'bvb', 'bvc', 'ccs', 'cnq', 'cph', 'cxe', 'dfm', 'doh', 'dus', 'ebs', 'fka', 'fra', 'ger', 'ham', 'han', 'hel', 'hkg', 'ice', 'iob', 'ise', 'ist', 'jkt', 'jnb', 'jpx', 'kls', 'kuw', 'lis', 'lit', 'lse', 'mce', 'mex', 'mil', 'mun', 'ncm', 'neo', 'ngm', 'nms', 'nsi', 'nyq', 'nze', 'oem', 'oqb', 'oqx', 'osl', 'par', 'pnk', 'pra', 'ris', 'sau', 'ses', 'set', 'sgo', 'shh', 'shz', 'sto', 'stu', 'tai', 'tal', 'tlv', 'tor', 'two', 'van', 'vie', 'vse', 'wse'] | None", + "description": "Filter by exchange.", + "default": null, + "optional": true, + "choices": [ + "ams", + "aqs", + "ase", + "asx", + "ath", + "ber", + "bru", + "bse", + "bts", + "bud", + "bue", + "bvb", + "bvc", + "ccs", + "cnq", + "cph", + "cxe", + "dfm", + "doh", + "dus", + "ebs", + "fka", + "fra", + "ger", + "ham", + "han", + "hel", + "hkg", + "ice", + "iob", + "ise", + "ist", + "jkt", + "jnb", + "jpx", + "kls", + "kuw", + "lis", + "lit", + "lse", + "mce", + "mex", + "mil", + "mun", + "ncm", + "neo", + "ngm", + "nms", + "nsi", + "nyq", + "nze", + "oem", + "oqb", + "oqx", + "osl", + "par", + "pnk", + "pra", + "ris", + "sau", + "ses", + "set", + "sgo", + "shh", + "shz", + "sto", + "stu", + "tai", + "tal", + "tlv", + "tor", + "two", + "van", + "vie", + "vse", + "wse" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "sector", + "type": "Literal['basic_materials', 'communication_services', 'consumer_cyclical', 'consumer_defensive', 'energy', 'financial_services', 'healthcare', 'industrials', 'real_estate', 'technology', 'utilities'] | None", + "description": "Filter by sector.", + "default": null, + "optional": true, + "choices": [ + "basic_materials", + "communication_services", + "consumer_cyclical", + "consumer_defensive", + "energy", + "financial_services", + "healthcare", + "industrials", + "real_estate", + "technology", + "utilities" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "industry", + "type": "str | None", + "description": "Filter by industry.", + "default": null, + "optional": true, + "choices": [ + "advertising_agencies", + "aerospace_defense", + "agricultural_inputs", + "airlines", + "airports_air_services", + "aluminum", + "apparel_manufacturing", + "apparel_retail", + "asset_management", + "auto_components", + "auto_manufacturers", + "auto_parts", + "auto_truck_dealerships", + "automobiles", + "banks", + "biotechnology", + "broadcasting", + "building_materials", + "building_products", + "building_products_equipment", + "business_equipment_supplies", + "capital_markets", + "chemicals", + "coking_coal", + "commercial_services", + "communication_equipment", + "computer_hardware", + "confectioners", + "construction_engineering", + "construction_materials", + "consulting_services", + "consumer_durables", + "consumer_electronics", + "consumer_services", + "copper", + "credit_services", + "department_stores", + "diagnostics_research", + "discount_stores", + "diversified_financials", + "education_training_services", + "electrical_equipment", + "electrical_equipment_parts", + "electronic_components", + "electronic_gaming_multimedia", + "electronics_computer_distribution", + "energy_services", + "engineering_construction", + "entertainment", + "farm_heavy_construction_machinery", + "farm_products", + "financial_conglomerates", + "financial_data_stock_exchanges", + "food_distribution", + "footwear_accessories", + "furnishings_fixtures_appliances", + "gambling", + "gold", + "grocery_stores", + "health_information_services", + "healthcare_plans", + "home_builders", + "home_improvement_retail", + "household_products", + "household_personal_products", + "industrial_conglomerates", + "industrial_distribution", + "information_technology_services", + "infrastructure_operations", + "insurance", + "integrated_freight_logistics", + "internet_content_information", + "internet_retail", + "leisure", + "lodging", + "lumber_wood_production", + "luxury_goods", + "machinery", + "marine_shipping", + "media", + "medical_care_facilities", + "medical_devices", + "medical_distribution", + "medical_instruments_supplies", + "metal_fabrication", + "mortgage_finance", + "oil_gas_drilling", + "oil_gas_e_p", + "oil_gas_equipment_services", + "oil_gas_integrated", + "oil_gas_midstream", + "oil_gas_producers", + "oil_gas_refining_marketing", + "other_industrial_metals_mining", + "other_precious_metals_mining", + "packaged_foods", + "packaging_containers", + "paper_forestry", + "paper_paper_products", + "personal_services", + "pharmaceuticals", + "pharmaceutical_retailers", + "pollution_treatment_controls", + "precious_metals", + "publishing", + "railroads", + "real_estate", + "recreational_vehicles", + "refiners_pipelines", + "rental_leasing_services", + "residential_construction", + "resorts_casinos", + "restaurants", + "retailing", + "scientific_technical_instruments", + "security_protection_services", + "semiconductor_equipment_materials", + "semiconductors", + "shell_companies", + "silver", + "software_and_services", + "solar", + "specialty_business_services", + "specialty_chemicals", + "specialty_industrial_machinery", + "specialty_retail", + "staffing_employment_services", + "steel", + "technology_hardware", + "telecom_services", + "textiles_apparel", + "textile_manufacturing", + "thermal_coal", + "tobacco", + "tools_accessories", + "traders_distributors", + "transportation", + "transportation_infrastructure", + "travel_services", + "trucking", + "uranium", + "utilities", + "waste_management" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "mktcap_min", + "type": "int | None", + "description": "Filter by market cap greater than this value. Default is 500M.", + "default": 500000000, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "mktcap_max", + "type": "int | None", + "description": "Filter by market cap less than this value.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "price_min", + "type": "float | None", + "description": "Filter by price greater than this value. Default is, 5", + "default": 5, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "price_max", + "type": "float | None", + "description": "Filter by price less than this value.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "volume_min", + "type": "int | None", + "description": "Filter by volume greater than this value. Default is, 10K", + "default": 10000, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "volume_max", + "type": "int | None", + "description": "Filter by volume less than this value.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "beta_min", + "type": "float | None", + "description": "Filter by a beta greater than this value.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "beta_max", + "type": "float | None", + "description": "Filter by a beta less than this value.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "limit", + "type": "int | None", + "description": "Limit the number of results returned. Default is, 200. Set to, 0, for all results.", + "default": 200, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityScreener", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Last price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "change", + "type": "float", + "description": "Change in price.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "percent_change", + "type": "float", + "description": "Percent change.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-widget_config": { + "renderFn": "cellOnClick", + "renderFnParams": { + "actionType": "groupBy", + "groupBy": { + "paramName": "symbol", + "valueField": "symbol" + } + } + } + } + }, + { + "name": "price_history", + "type": "list[float] | None", + "description": "Recent daily closing prices, rendered as a sparkline.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-widget_config": { + "headerName": "Price (1M)", + "width": 180, + "sparkline": { + "type": "line", + "options": { + "stroke": "#2563eb", + "strokeWidth": 2, + "pointsOfInterest": { + "maximum": { + "fill": "#22c55e", + "stroke": "#16a34a", + "size": 5 + }, + "minimum": { + "fill": "#ef4444", + "stroke": "#dc2626", + "size": 5 + } + } + } + } + } + } + }, + { + "name": "open", + "type": "float | None", + "description": "Open price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "high", + "type": "float | None", + "description": "High price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "low", + "type": "float | None", + "description": "Low price for the day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "previous_close", + "type": "float | None", + "description": "Previous close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma50", + "type": "float | None", + "description": "50-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ma200", + "type": "float | None", + "description": "200-day moving average.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "52-week high.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "year_low", + "type": "float | None", + "description": "52-week low.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "market_cap", + "type": "float | None", + "description": "Market Cap.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "shares_outstanding", + "type": "float | None", + "description": "Shares outstanding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "book_value", + "type": "float | None", + "description": "Book value per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Price to book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "eps_ttm", + "type": "float | None", + "description": "Earnings per share over the trailing twelve months.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "eps_forward", + "type": "float | None", + "description": "Forward earnings per share.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "pe_forward", + "type": "float | None", + "description": "Forward price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "Trailing twelve month dividend yield.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "frontend_multiply": 100 + } + }, + { + "name": "exchange", + "type": "str | None", + "description": "Exchange where the stock is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_timezone", + "type": "str | None", + "description": "Timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_date", + "type": "datetime | None", + "description": "Most recent earnings date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency of the price data.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityScreener", + "openapi_extra": { + "model": "EquityScreener" + } + }, + "/equity/profile": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get general information about a company. This includes company name, industry, sector and price data.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "EquityInfo", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Common name of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "cik", + "type": "str | None", + "description": "Central Index Key (CIK) for the requested entity.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "cusip", + "type": "str | None", + "description": "CUSIP identifier for the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "isin", + "type": "str | None", + "description": "International Securities Identification Number.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "lei", + "type": "str | None", + "description": "Legal Entity Identifier assigned to the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "legal_name", + "type": "str | None", + "description": "Official legal name of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "stock_exchange", + "type": "str | None", + "description": "Stock exchange where the company is traded.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sic", + "type": "int | None", + "description": "Standard Industrial Classification code for the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "short_description", + "type": "str | None", + "description": "Short description of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "long_description", + "type": "str | None", + "description": "Long description of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ceo", + "type": "str | None", + "description": "Chief Executive Officer of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "company_url", + "type": "str | None", + "description": "URL of the company's website.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "business_address", + "type": "str | None", + "description": "Address of the company's headquarters.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "mailing_address", + "type": "str | None", + "description": "Mailing address of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "business_phone_no", + "type": "str | None", + "description": "Phone number of the company's headquarters.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "hq_address1", + "type": "str | None", + "description": "Address of the company's headquarters.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "hq_address2", + "type": "str | None", + "description": "Address of the company's headquarters.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "hq_address_city", + "type": "str | None", + "description": "City of the company's headquarters.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "hq_address_postal_code", + "type": "str | None", + "description": "Zip code of the company's headquarters.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "hq_state", + "type": "str | None", + "description": "State of the company's headquarters.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "hq_country", + "type": "str | None", + "description": "Country of the company's headquarters.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "inc_state", + "type": "str | None", + "description": "State in which the company is incorporated.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "inc_country", + "type": "str | None", + "description": "Country in which the company is incorporated.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "employees", + "type": "int | None", + "description": "Number of employees working for the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "entity_legal_form", + "type": "str | None", + "description": "Legal form of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "entity_status", + "type": "str | None", + "description": "Status of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "latest_filing_date", + "type": "date | None", + "description": "Date of the company's latest filing.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "irs_number", + "type": "str | None", + "description": "IRS number assigned to the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sector", + "type": "str | None", + "description": "Sector in which the company operates.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "industry_category", + "type": "str | None", + "description": "Category of industry in which the company operates.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "industry_group", + "type": "str | None", + "description": "Group of industry in which the company operates.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "template", + "type": "str | None", + "description": "Template used to standardize the company's financial statements.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "standardized_active", + "type": "bool | None", + "description": "Whether the company is active or not.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "first_fundamental_date", + "type": "date | None", + "description": "Date of the company's first fundamental.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "last_fundamental_date", + "type": "date | None", + "description": "Date of the company's last fundamental.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "first_stock_price_date", + "type": "date | None", + "description": "Date of the company's first stock price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "last_stock_price_date", + "type": "date | None", + "description": "Date of the company's last stock price.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "exchange_timezone", + "type": "str | None", + "description": "The timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "issue_type", + "type": "str | None", + "description": "The issuance type of the asset.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "The currency in which the asset is traded.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "market_cap", + "type": "int | None", + "description": "The market capitalization of the asset.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "shares_outstanding", + "type": "int | None", + "description": "The number of listed shares outstanding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "shares_float", + "type": "int | None", + "description": "The number of shares in the public float.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "shares_implied_outstanding", + "type": "int | None", + "description": "Implied shares outstanding of common equityassuming the conversion of all convertible subsidiary equity into common.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "shares_short", + "type": "int | None", + "description": "The reported number of shares short.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "The dividend yield of the asset, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "beta", + "type": "float | None", + "description": "The beta of the asset relative to the broad market.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "EquityInfo", + "openapi_extra": { + "model": "EquityInfo" + } + }, + "/oecd/list_topic_choices": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Return [{label, value}] for every OECD topic (for dropdowns).\n\n Returns\n -------\n list\n\n Examples\n --------\n >>> from openbb import obb\n >>> # Get OECD topic choices for UI dropdowns.\n >>> obb.oecd.list_topic_choices()", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get OECD topic choices for UI dropdowns.\nobb.oecd.list_topic_choices()\n```\n\n", + "parameters": { + "standard": [] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "exclude": true + }, + "model": "" + } + }, + "/oecd/list_subtopic_choices": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Return [{label, value}] for subtopics within a given topic (for dropdowns).", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get subtopic choices for a given topic.\nobb.oecd.list_subtopic_choices(topic='ECO')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "topic", + "type": "str", + "description": "Topic ID to get subtopics for (e.g. 'ECO').", + "default": null, + "optional": true + } + ] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "exclude": true + }, + "model": "" + } + }, + "/oecd/list_dataflows": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "list all available OECD dataflows, optionally filtered by topic and subtopic.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# list all OECD dataflows.\nobb.oecd.list_dataflows()\n# Filter dataflows by topic.\nobb.oecd.list_dataflows(topic='HEA')\n# list all OECD dataflows.\ndataflows = obb.oecd.utils.list_dataflows()\nprint(dataflows.results)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "topic", + "type": "str", + "description": "Filter dataflows by topic ID (e.g. 'ECO', 'HEA', 'ENV'). Use list_topics() to see all available topics.", + "default": null, + "optional": true + }, + { + "name": "subtopic", + "type": "str", + "description": "Filter dataflows by subtopic ID within the selected topic.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "Any", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "name": "OECD Dataflows", + "description": "All available OECD dataflows, optionally filtered by topic.", + "params": [ + { + "paramName": "topic", + "label": "Topic", + "value": null, + "description": "Filter by topic. Leave blank to show all.", + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/list_topic_choices", + "style": { + "popupWidth": 500 + }, + "optional": true + }, + { + "paramName": "subtopic", + "label": "Subtopic", + "value": null, + "description": "Filter by subtopic (requires a topic to be selected).", + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/list_subtopic_choices", + "optionsParams": { + "topic": "$topic" + }, + "style": { + "popupWidth": 500 + }, + "optional": true + } + ], + "gridData": { + "w": 20, + "h": 15 + }, + "refetchInterval": false, + "source": [ + "OECD" + ], + "category": "OECD Utilities", + "subCategory": "Metadata" + }, + "model": "" + } + }, + "/oecd/list_dataflow_choices": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Return [{label, value}] for every OECD dataflow (for dropdowns).\n\n Returns\n -------\n list\n\n Examples\n --------\n >>> from openbb import obb\n >>> # Get OECD dataflow choices for UI dropdowns.\n >>> obb.oecd.list_dataflow_choices()", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get OECD dataflow choices for UI dropdowns.\nobb.oecd.list_dataflow_choices()\n```\n\n", + "parameters": { + "standard": [] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "exclude": true + }, + "model": "" + } + }, + "/oecd/list_topics": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "list all OECD topic categories with dataflow counts.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# list all OECD topics.\nobb.oecd.list_topics()\n# Search topics.\nobb.oecd.list_topics(query='health')\n# Browse OECD topics and subtopics.\ntopics = obb.oecd.utils.list_topics()\nprint(topics.results)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "query", + "type": "str", + "description": "Filter rows by topic or subtopic name.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "Any", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "name": "OECD Topics", + "description": "All OECD topic categories with dataflow counts.", + "params": [ + { + "paramName": "query", + "label": "Search", + "value": null, + "description": "Filter by topic or subtopic name.", + "optional": true + } + ], + "gridData": { + "w": 30, + "h": 20 + }, + "refetchInterval": false, + "source": [ + "OECD" + ], + "category": "OECD Utilities", + "subCategory": "Metadata" + }, + "model": "" + } + }, + "/oecd/get_dataflow_parameters": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Dataflow parameters and possible dimension values.\n\nReturns an OBBject with either a JSON dict or markdown string under results.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get parameters for the 'DF_PRICES_ALL' dataflow in markdown.\nobb.oecd.get_dataflow_parameters(dataflow_id='DF_PRICES_ALL', output_format='markdown')\n# Get parameters for the 'DF_CLI' dataflow as JSON.\nobb.oecd.get_dataflow_parameters(dataflow_id='DF_CLI', output_format='json')\n# Inspect dimensions of the 'DF_QNA' dataflow.\nparams = obb.oecd.utils.get_dataflow_parameters('DF_QNA')\nprint(params.results)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "dataflow_id", + "type": "str", + "description": "The OECD dataflow ID. Use list_dataflows() to see available dataflows.", + "default": null, + "optional": false + }, + { + "name": "output_format", + "type": "Literal['json', 'markdown']", + "description": "", + "default": "json", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "Any", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "name": "OECD Dataflow Parameters", + "type": "markdown", + "params": [ + { + "paramName": "dataflow_id", + "label": "Dataflow", + "value": "DF_PRICES_ALL", + "description": "The OECD dataflow to inspect.", + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/list_dataflow_choices", + "style": { + "popupWidth": 700 + } + }, + { + "paramName": "output_format", + "value": "markdown", + "show": false + } + ], + "data": { + "dataKey": "results" + }, + "source": [ + "OECD" + ], + "category": "OECD Utilities", + "subCategory": "Metadata" + }, + "model": "" + } + }, + "/oecd/list_tables": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "list all OECD tables with keyword search, topic, subtopic, and dataflow ID filtering.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# list all OECD tables.\nobb.oecd.list_tables()\n# Search for GDP tables.\nobb.oecd.list_tables(query='GDP')\n# Filter by topic.\nobb.oecd.list_tables(topic='HEA')\n# Find a specific table by dataflow ID.\nobb.oecd.list_tables(dataflow_id='DF_PRICES_ALL')\n# Search OECD tables.\ntables = obb.oecd.utils.list_tables(query='prices')\nprint(tables.results)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "query", + "type": "str", + "description": "Keyword search. Space-separated terms are AND-ed; use | for OR within a word.", + "default": null, + "optional": true + }, + { + "name": "topic", + "type": "str", + "description": "Filter by topic ID (e.g. 'ECO', 'HEA'). Use list_topics() to see all topics.", + "default": null, + "optional": true + }, + { + "name": "subtopic", + "type": "str", + "description": "Filter by subtopic ID within the selected topic.", + "default": null, + "optional": true + }, + { + "name": "dataflow_id", + "type": "str", + "description": "Filter by exact or partial dataflow (table) ID (e.g. 'DF_PRICES_ALL').", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "Any", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "name": "OECD Tables", + "description": "Searchable map of all OECD tables, optionally filtered by topic.", + "params": [ + { + "paramName": "topic", + "label": "Topic", + "value": null, + "description": "Filter by topic. Leave blank to show all.", + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/list_topic_choices", + "style": { + "popupWidth": 700 + }, + "optional": true + }, + { + "paramName": "subtopic", + "label": "Subtopic", + "value": null, + "description": "Filter by subtopic (requires a topic to be selected).", + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/list_subtopic_choices", + "optionsParams": { + "topic": "$topic" + }, + "style": { + "popupWidth": 500 + }, + "optional": true + }, + { + "paramName": "query", + "label": "Search", + "value": null, + "description": "Keyword search across name, topic, and dataflow ID.", + "optional": true + }, + { + "paramName": "dataflow_id", + "label": "Dataflow ID", + "value": null, + "description": "Filter by exact or partial dataflow ID (e.g. 'DF_PRICES_ALL').", + "optional": true + } + ], + "gridData": { + "w": 40, + "h": 20 + }, + "refetchInterval": false, + "source": [ + "OECD" + ], + "category": "OECD Utilities", + "subCategory": "Metadata" + }, + "model": "" + } + }, + "/oecd/get_table_detail": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Full dimension and indicator breakdown for a single OECD table.\n\nReturns a markdown document with:\n- Table name and description\n- Each dimension as a collapsible section with allowed values\n- Table groups (TABLE_IDENTIFIER), if present\n- Indicator count and hierarchy summary", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get full detail for the DF_PRICES_ALL table.\nobb.oecd.get_table_detail(table_id='DF_PRICES_ALL')\n# Inspect a national accounts table.\nobb.oecd.get_table_detail(table_id='DF_T725R_Q')\n# Describe a table.\ndetail = obb.oecd.utils.get_table_detail(table_id='DF_QNA')\nprint(detail.results)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "table_id", + "type": "str", + "description": "The OECD dataflow (table) ID. Use list_tables() to find IDs.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "Any", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "name": "OECD Table Detail", + "description": "Full dimension breakdown for a single OECD table, including indicator hierarchy.", + "type": "markdown", + "params": [ + { + "paramName": "table_id", + "label": "Table", + "value": "DF_PRICES_ALL", + "description": "The OECD table (dataflow) to inspect.", + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/list_table_choices", + "style": { + "popupWidth": 950 + } + } + ], + "data": { + "dataKey": "results" + }, + "gridData": { + "w": 40, + "h": 25 + }, + "refetchInterval": false, + "source": [ + "OECD" + ], + "category": "OECD Utilities", + "subCategory": "Metadata" + }, + "model": "" + } + }, + "/oecd/list_table_choices": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Return [{label, value}] for OECD tables (for dropdowns), optionally filtered by topic.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get table choices for dropdowns.\nobb.oecd.list_table_choices()\n```\n\n", + "parameters": { + "standard": [ + { + "name": "topic", + "type": "str", + "description": "Filter choices by topic ID (e.g. 'ECO'). Leave blank for all.", + "default": null, + "optional": true + } + ] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "exclude": true + }, + "model": "" + } + }, + "/oecd/presentation_table_choices": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get presentation table choices for OECD data retrieval.\n\n Progressive cascading selector using the OECD metadata taxonomy.\n All data is discovered dynamically from the OECD SDMX metadata \u2014\n topics, subtopics, dataflows, and their TABLE_IDENTIFIER dimension\n values are resolved at runtime.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get the topic choices (step 0).\nobb.oecd.presentation_table_choices()\n# Get subtopic choices for the 'ECO' topic (step 1).\nobb.oecd.presentation_table_choices(topic='ECO')\n# Get table choices for topic and subtopic (step 2).\nobb.oecd.presentation_table_choices(topic='ECO', subtopic='ECO.EO')\n# Get country choices for a specific table (step 3).\nobb.oecd.presentation_table_choices(topic='ECO', subtopic='ECO.EO', table='DF_QNA::T0101')\n# Get frequency choices (step 4).\nobb.oecd.presentation_table_choices(topic='ECO', subtopic='ECO.EO', table='DF_QNA::T0101', country='USA')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "topic", + "type": "str", + "description": "OECD topic code (e.g. 'ECO'). Omit to list all topics.", + "default": null, + "optional": true + }, + { + "name": "subtopic", + "type": "str", + "description": "OECD subtopic code (e.g. 'ECO.EO'). Enter a topic to see choices. Pure UI convenience \u2014 not required for API / Python usage.", + "default": null, + "optional": true + }, + { + "name": "table", + "type": "str", + "description": "", + "default": null, + "optional": true + }, + { + "name": "country", + "type": "str", + "description": "Country code. Enter a topic and table to see choices.", + "default": null, + "optional": true + }, + { + "name": "frequency", + "type": "str", + "description": "Frequency code. Enter topic, table, and country to see choices.", + "default": null, + "optional": true + } + ] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "exclude": true + }, + "model": "" + } + }, + "/oecd/presentation_table": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get a formatted presentation table from the OECD database.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get quarterly GDP output table for the United States.\nobb.oecd.presentation_table(topic='ECO', table='DF_QNA::T0101', country='USA', frequency='Q', limit=4)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "topic", + "type": "str", + "description": "The OECD topic code. UI navigation aid \u2014 not required when table is provided directly.", + "default": null, + "optional": true + }, + { + "name": "subtopic", + "type": "str", + "description": "The OECD subtopic code (e.g. 'ECO.EO'). UI navigation aid \u2014 not required when table is provided directly.", + "default": null, + "optional": true + }, + { + "name": "table", + "type": "str", + "description": "The OECD presentation table key. Accepts 'DATAFLOW::TABLE_ID' or bare 'DATAFLOW_ID'. See presentation_table_choices() for options.", + "default": null, + "optional": true + }, + { + "name": "country", + "type": "str", + "description": "Country code to filter the data. Enter multiple codes by joining on '+'. See presentation_table_choices() for options.", + "default": null, + "optional": true + }, + { + "name": "counterpart", + "type": "str", + "description": "Counterpart area code for bilateral data (e.g. 'W' for World, 'USA'). When omitted, auto-selected (defaults to World aggregate).", + "default": null, + "optional": true + }, + { + "name": "frequency", + "type": "str", + "description": "The data frequency. See presentation_table_choices() for options. Typical values are 'A' (annual), 'Q' (quarter), 'M' (month).", + "default": null, + "optional": true + }, + { + "name": "unit_measure", + "type": "str", + "description": "Unit of measure code. E.g. 'XDC' (national currency), 'USD_EXC' (US dollars), 'PS' (persons), 'PT_B1GQ' (% of GDP). When omitted, auto-selected via availability.", + "default": null, + "optional": true + }, + { + "name": "adjustment", + "type": "str", + "description": "Seasonal adjustment code. 'Y' = seasonally adjusted, 'N' = not adjusted. When omitted, prefers 'Y' where available, falls back to 'N'.", + "default": null, + "optional": true + }, + { + "name": "transformation", + "type": "str", + "description": "Data transformation code. E.g. 'N' (none), 'GY' (growth year-on-year), 'IX' (index). When omitted, auto-selected via availability.", + "default": null, + "optional": true + }, + { + "name": "dimension_values", + "type": "list[str] | str", + "description": "Dimension selection for filtering. Format: 'DIM_ID1:VAL1+VAL2.'", + "default": null, + "optional": true + }, + { + "name": "start_date", + "type": "str", + "description": "Earliest date to include (ISO format, e.g. '2020-01-01'). Filters out obsolete rows that stopped reporting before this date.", + "default": null, + "optional": true + }, + { + "name": "end_date", + "type": "str", + "description": "Latest date to include (ISO format, e.g. '2025-12-31').", + "default": null, + "optional": true + }, + { + "name": "limit", + "type": "int", + "description": "Maximum number of records to retrieve per series.", + "default": 1, + "optional": true + } + ] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "params": [ + { + "paramName": "topic", + "label": "Topic", + "value": null, + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/presentation_table_choices", + "description": "The OECD topic." + }, + { + "paramName": "subtopic", + "label": "Subtopic", + "value": null, + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/presentation_table_choices", + "optionsParams": { + "topic": "$topic" + }, + "style": { + "popupWidth": 500 + }, + "description": "Filter by subtopic. UI convenience only.", + "show": true + }, + { + "paramName": "table", + "label": "Table", + "type": "endpoint", + "value": null, + "optionsEndpoint": "/api/v1/oecd/presentation_table_choices", + "optionsParams": { + "topic": "$topic", + "subtopic": "$subtopic" + }, + "style": { + "popupWidth": 950 + }, + "description": "The OECD presentation table (DATAFLOW::TABLE_ID)." + }, + { + "paramName": "country", + "label": "Country", + "description": "Country or region for the table.", + "type": "endpoint", + "multiSelect": true, + "optionsEndpoint": "/api/v1/oecd/presentation_table_choices", + "optionsParams": { + "topic": "$topic", + "subtopic": "$subtopic", + "table": "$table" + } + }, + { + "paramName": "counterpart", + "label": "Counterpart", + "type": "endpoint", + "multiSelect": true, + "optionsEndpoint": "/api/v1/oecd/presentation_table_dim_choices", + "optionsParams": { + "table": "$table", + "country": "$country", + "frequency": "$frequency", + "dimension": "counterpart_area" + }, + "style": { + "popupWidth": 400 + }, + "description": "Counterpart area for bilateral data. Leave blank for auto-selection (World).", + "optional": true + }, + { + "paramName": "frequency", + "label": "Frequency", + "type": "endpoint", + "optionsEndpoint": "/api/v1/oecd/presentation_table_choices", + "optionsParams": { + "topic": "$topic", + "subtopic": "$subtopic", + "table": "$table", + "country": "$country" + }, + "description": "The data frequency." + }, + { + "paramName": "unit_measure", + "label": "Unit Measure", + "type": "endpoint", + "value": null, + "optionsEndpoint": "/api/v1/oecd/presentation_table_dim_choices", + "optionsParams": { + "table": "$table", + "country": "$country", + "frequency": "$frequency", + "dimension": "unit_measure" + }, + "style": { + "popupWidth": 400 + }, + "description": "Unit of measure. Leave blank for auto-selection." + }, + { + "paramName": "adjustment", + "label": "Adjustment", + "type": "endpoint", + "value": null, + "optionsEndpoint": "/api/v1/oecd/presentation_table_dim_choices", + "optionsParams": { + "table": "$table", + "country": "$country", + "frequency": "$frequency", + "dimension": "adjustment" + }, + "description": "Seasonal adjustment type. Leave blank for auto-selection.", + "optional": true + }, + { + "paramName": "transformation", + "label": "Transformation", + "type": "endpoint", + "value": null, + "optionsEndpoint": "/api/v1/oecd/presentation_table_dim_choices", + "optionsParams": { + "table": "$table", + "country": "$country", + "frequency": "$frequency", + "dimension": "transformation" + }, + "description": "Data transformation. Leave blank for auto-selection.", + "optional": true + }, + { + "paramName": "dimension_values", + "label": "Dimension Values", + "type": "text", + "value": null, + "description": "Dimension selection for filtering. Format: 'DIM_ID1:VAL1+VAL2.' See the Metadata tab for available dimensions and values.", + "multiple": true, + "multiSelect": false + }, + { + "paramName": "start_date", + "label": "Start Date", + "type": "date", + "value": null, + "description": "Earliest date to include (ISO format). Filters out obsolete data.", + "optional": true + }, + { + "paramName": "end_date", + "label": "End Date", + "type": "date", + "value": null, + "description": "Latest date to include (ISO format).", + "optional": true + }, + { + "paramName": "limit", + "label": "Limit", + "value": 5, + "description": "Most recent N records to retrieve per series.", + "type": "number" + } + ], + "runButton": true, + "refetchInterval": false, + "name": "OECD Presentation Table", + "description": "Presentation tables from the OECD database.", + "source": [ + "OECD" + ], + "category": "OECD Utilities", + "subCategory": "Presentation Tables" + }, + "model": "" + } + }, + "/quantitative/normality": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get normality statistics for a data series.\n\nRuns the kurtosis, skewness, Jarque-Bera, Shapiro-Wilk, and Kolmogorov-Smirnov\ntests, each assessing whether the sample is drawn from a normal distribution.\nReturns the test statistic and p-value for every test.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.normality(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "NormalityData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "kurtosis_statistic", + "type": "float", + "description": "Kurtosis test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kurtosis_p_value", + "type": "float", + "description": "p-value of the kurtosis test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "skewness_statistic", + "type": "float", + "description": "Skewness test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "skewness_p_value", + "type": "float", + "description": "p-value of the skewness test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "jarque_bera_statistic", + "type": "float", + "description": "Jarque-Bera test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "jarque_bera_p_value", + "type": "float", + "description": "p-value of the Jarque-Bera test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "shapiro_wilk_statistic", + "type": "float", + "description": "Shapiro-Wilk test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "shapiro_wilk_p_value", + "type": "float", + "description": "p-value of the Shapiro-Wilk test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kolmogorov_smirnov_statistic", + "type": "float", + "description": "Kolmogorov-Smirnov test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kolmogorov_smirnov_p_value", + "type": "float", + "description": "p-value of the Kolmogorov-Smirnov test.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/capm": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get Capital Asset Pricing Model (CAPM) risk measures for an asset.\n\nRegresses the asset's excess return on the excess market return using the\nFama-French factors. Returns the market risk (beta), the systematic risk\n(R-squared), and the idiosyncratic risk (1 - R-squared).", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.capm(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "CapmData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "market_risk", + "type": "float", + "description": "Beta - the asset's sensitivity to market returns.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "systematic_risk", + "type": "float", + "description": "Share of variance explained by the market (R-squared).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "idiosyncratic_risk", + "type": "float", + "description": "Share of variance not explained by the market (1 - R-squared).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/unitroot_test": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get unit root test results for a data series.\n\nApplies the Augmented Dickey-Fuller (ADF) test, whose null hypothesis is the\npresence of a unit root, and the Kwiatkowski-Phillips-Schmidt-Shin (KPSS) test,\nwhose null hypothesis is stationarity. Together they characterize whether the\nseries is stationary.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.unitroot_test(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "fuller_reg", + "type": "Literal['c', 'ct', 'ctt', 'n']", + "description": "Constant/trend terms for the ADF test: 'c' constant, 'ct' constant and trend, 'ctt' constant, linear and quadratic trend, 'n' no constant or trend.", + "default": "c", + "optional": true + }, + { + "name": "kpss_reg", + "type": "Literal['c', 'ct']", + "description": "Null hypothesis for the KPSS test: 'c' level-stationary, 'ct' trend-stationary.", + "default": "c", + "optional": true + }, + { + "name": "maxlag", + "type": "Annotated[int, Ge(ge=0)]", + "description": "Maximum lag for the ADF test. If None, a data-dependent default is used.", + "default": null, + "optional": true + }, + { + "name": "autolag", + "type": "None", + "description": "Lag-selection method for the ADF test: 'AIC', 'BIC', or 't-stat'; None uses maxlag directly without selection.", + "default": "AIC", + "optional": true + }, + { + "name": "nlags", + "type": "Annotated[int, Ge(ge=0)]", + "description": "Lags for the KPSS variance estimator: 'auto', 'legacy', or an explicit non-negative integer.", + "default": "auto", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "UnitRootData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "adf_statistic", + "type": "float", + "description": "Augmented Dickey-Fuller test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_p_value", + "type": "float", + "description": "p-value of the ADF test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_nlags", + "type": "int", + "description": "Number of lags used in the ADF test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_nobs", + "type": "int", + "description": "Number of observations used in the ADF test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_icbest", + "type": "float", + "description": "Best information criterion value of the ADF test (None when autolag is disabled).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "kpss_statistic", + "type": "float", + "description": "KPSS test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kpss_p_value", + "type": "float", + "description": "p-value of the KPSS test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kpss_nlags", + "type": "int", + "description": "Number of lags used in the KPSS test.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kpss_p_value_interpolated", + "type": "bool", + "description": "False when the KPSS p-value is clamped to a table boundary (the true p-value is more extreme than reported).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/summary": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get descriptive summary statistics for a data series.\n\nComputes the count, central tendency, dispersion, and quartile distribution of\nthe target column. Provides a concise profile of the data for initial\nexploration and reporting.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.summary(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "SummaryData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "count", + "type": "int", + "description": "Number of observations.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "mean", + "type": "float", + "description": "Arithmetic mean.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "std", + "type": "float", + "description": "Standard deviation.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "var", + "type": "float", + "description": "Variance.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "min", + "type": "float", + "description": "Minimum value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p25", + "type": "float", + "description": "25th percentile.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p50", + "type": "float", + "description": "50th percentile (median).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p75", + "type": "float", + "description": "75th percentile.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "max", + "type": "float", + "description": "Maximum value.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/rolling/skew": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the rolling skew of a target column over a moving window.\n\nSkew measures the asymmetry of a distribution about its mean. Positive skew\nindicates a longer right tail, while negative skew indicates a longer left tail.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.rolling.skew(target='close', window=20, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "window", + "type": "int", + "description": "Number of observations in each rolling window.", + "default": 21, + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column.", + "default": "date", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RollingSkewData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "skew", + "type": "float", + "description": "Rolling skew value.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/rolling/variance": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the rolling variance of a target column over a moving window.\n\nVariance measures the dispersion of a set of data points around their mean. It is\na key metric for assessing the volatility and stability of a time series.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.rolling.variance(target='close', window=20, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "window", + "type": "int", + "description": "Number of observations in each rolling window.", + "default": 21, + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column.", + "default": "date", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RollingVarianceData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "variance", + "type": "float", + "description": "Rolling variance value.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/rolling/stdev": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the rolling standard deviation of a target column over a moving window.\n\nStandard deviation measures the amount of variation or dispersion of a set of\nvalues. It is the square root of the variance and is widely used to assess risk.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.rolling.stdev(target='close', window=20, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "window", + "type": "int", + "description": "Number of observations in each rolling window.", + "default": 21, + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column.", + "default": "date", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RollingStdevData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "stdev", + "type": "float", + "description": "Rolling standard deviation value.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/rolling/kurtosis": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the rolling kurtosis of a target column over a moving window.\n\nKurtosis measures the tailedness of a distribution. High kurtosis indicates heavy\ntails and a higher risk of extreme outcomes, while low kurtosis indicates the\nopposite.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.rolling.kurtosis(target='close', window=20, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "window", + "type": "int", + "description": "Number of observations in each rolling window.", + "default": 21, + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column.", + "default": "date", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RollingKurtosisData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kurtosis", + "type": "float", + "description": "Rolling kurtosis value.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/rolling/mean": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the rolling mean of a target column over a moving window.\n\nThe rolling mean is a simple moving average that smooths short-term fluctuations\nand highlights longer-term trends or cycles in a time series.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.rolling.mean(target='close', window=20, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "window", + "type": "int", + "description": "Number of observations in each rolling window.", + "default": 21, + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column.", + "default": "date", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RollingMeanData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "mean", + "type": "float", + "description": "Rolling mean value.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/rolling/quantile": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the rolling quantile of a target column over a moving window.\n\nQuantiles divide the range of a distribution into intervals with equal\nprobabilities. This command returns the rolling median alongside the requested\nquantile for analyzing trends, outliers, and risk.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.rolling.quantile(target='close', window=20, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "window", + "type": "int", + "description": "Number of observations in each rolling window.", + "default": 21, + "optional": true + }, + { + "name": "quantile_pct", + "type": "float", + "description": "Quantile to compute, between 0 and 1.", + "default": 0.5, + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column.", + "default": "date", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RollingQuantileData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "median", + "type": "float", + "description": "Rolling median value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "quantile", + "type": "float", + "description": "Rolling quantile value.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/rolling/factors": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Refit factor OLS on a rolling window and emit per-factor betas through time.\n\nFor each window end at stride `step`, runs OLS of the target on the factor\nmatrix using the trailing `window` observations and records each factor's\ncoefficient and t-statistic. Suitable for visualizing time-varying factor\nexposures and detecting regime shifts that named-window decompositions hide.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Roll a 1-year window stepping monthly across a multi-factor regression of `close` on the supplied factor matrix.\nobb.quantitative.rolling.factors(target='close', window=252, step=21, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}, {'date': '2023-03-03', 'open': 111.8, 'high': 121.97, 'low': 101.64, 'close': 116.89, 'volume': 10163.93}, {'date': '2023-03-04', 'open': 111.77, 'high': 121.94, 'low': 101.61, 'close': 116.85, 'volume': 10161.29}, {'date': '2023-03-05', 'open': 111.75, 'high': 121.9, 'low': 101.59, 'close': 116.83, 'volume': 10158.73}, {'date': '2023-03-06', 'open': 111.72, 'high': 121.88, 'low': 101.56, 'close': 116.8, 'volume': 10156.25}, {'date': '2023-03-07', 'open': 111.69, 'high': 121.85, 'low': 101.54, 'close': 116.77, 'volume': 10153.85}, {'date': '2023-03-08', 'open': 111.67, 'high': 121.82, 'low': 101.52, 'close': 116.74, 'volume': 10151.52}, {'date': '2023-03-09', 'open': 111.64, 'high': 121.79, 'low': 101.49, 'close': 116.72, 'volume': 10149.25}, {'date': '2023-03-10', 'open': 111.62, 'high': 121.76, 'low': 101.47, 'close': 116.69, 'volume': 10147.06}, {'date': '2023-03-11', 'open': 111.59, 'high': 121.74, 'low': 101.45, 'close': 116.67, 'volume': 10144.93}, {'date': '2023-03-12', 'open': 111.57, 'high': 121.71, 'low': 101.43, 'close': 116.64, 'volume': 10142.86}, {'date': '2023-03-13', 'open': 111.55, 'high': 121.69, 'low': 101.41, 'close': 116.62, 'volume': 10140.85}, {'date': '2023-03-14', 'open': 111.53, 'high': 121.67, 'low': 101.39, 'close': 116.6, 'volume': 10138.89}, {'date': '2023-03-15', 'open': 111.51, 'high': 121.64, 'low': 101.37, 'close': 116.58, 'volume': 10136.99}, {'date': '2023-03-16', 'open': 111.49, 'high': 121.62, 'low': 101.35, 'close': 116.55, 'volume': 10135.14}, {'date': '2023-03-17', 'open': 111.47, 'high': 121.6, 'low': 101.33, 'close': 116.53, 'volume': 10133.33}, {'date': '2023-03-18', 'open': 111.45, 'high': 121.58, 'low': 101.32, 'close': 116.51, 'volume': 10131.58}, {'date': '2023-03-19', 'open': 111.43, 'high': 121.56, 'low': 101.3, 'close': 116.49, 'volume': 10129.87}, {'date': '2023-03-20', 'open': 111.41, 'high': 121.54, 'low': 101.28, 'close': 116.47, 'volume': 10128.21}, {'date': '2023-03-21', 'open': 111.39, 'high': 121.52, 'low': 101.27, 'close': 116.46, 'volume': 10126.58}, {'date': '2023-03-22', 'open': 111.38, 'high': 121.5, 'low': 101.25, 'close': 116.44, 'volume': 10125.0}, {'date': '2023-03-23', 'open': 111.36, 'high': 121.48, 'low': 101.23, 'close': 116.42, 'volume': 10123.46}, {'date': '2023-03-24', 'open': 111.34, 'high': 121.46, 'low': 101.22, 'close': 116.4, 'volume': 10121.95}, {'date': '2023-03-25', 'open': 111.33, 'high': 121.45, 'low': 101.2, 'close': 116.39, 'volume': 10120.48}, {'date': '2023-03-26', 'open': 111.31, 'high': 121.43, 'low': 101.19, 'close': 116.37, 'volume': 10119.05}, {'date': '2023-03-27', 'open': 111.29, 'high': 121.41, 'low': 101.18, 'close': 116.35, 'volume': 10117.65}, {'date': '2023-03-28', 'open': 111.28, 'high': 121.4, 'low': 101.16, 'close': 116.34, 'volume': 10116.28}, {'date': '2023-03-29', 'open': 111.26, 'high': 121.38, 'low': 101.15, 'close': 116.32, 'volume': 10114.94}, {'date': '2023-03-30', 'open': 111.25, 'high': 121.36, 'low': 101.14, 'close': 116.31, 'volume': 10113.64}, {'date': '2023-03-31', 'open': 111.24, 'high': 121.35, 'low': 101.12, 'close': 116.29, 'volume': 10112.36}, {'date': '2023-04-01', 'open': 111.22, 'high': 121.33, 'low': 101.11, 'close': 116.28, 'volume': 10111.11}, {'date': '2023-04-02', 'open': 111.21, 'high': 121.32, 'low': 101.1, 'close': 116.26, 'volume': 10109.89}, {'date': '2023-04-03', 'open': 111.2, 'high': 121.3, 'low': 101.09, 'close': 116.25, 'volume': 10108.7}, {'date': '2023-04-04', 'open': 111.18, 'high': 121.29, 'low': 101.08, 'close': 116.24, 'volume': 10107.53}, {'date': '2023-04-05', 'open': 111.17, 'high': 121.28, 'low': 101.06, 'close': 116.22, 'volume': 10106.38}, {'date': '2023-04-06', 'open': 111.16, 'high': 121.26, 'low': 101.05, 'close': 116.21, 'volume': 10105.26}, {'date': '2023-04-07', 'open': 111.15, 'high': 121.25, 'low': 101.04, 'close': 116.2, 'volume': 10104.17}, {'date': '2023-04-08', 'open': 111.13, 'high': 121.24, 'low': 101.03, 'close': 116.19, 'volume': 10103.09}, {'date': '2023-04-09', 'open': 111.12, 'high': 121.22, 'low': 101.02, 'close': 116.17, 'volume': 10102.04}, {'date': '2023-04-10', 'open': 111.11, 'high': 121.21, 'low': 101.01, 'close': 116.16, 'volume': 10101.01}, {'date': '2023-04-11', 'open': 111.1, 'high': 121.2, 'low': 101.0, 'close': 116.15, 'volume': 10100.0}, {'date': '2023-04-12', 'open': 111.09, 'high': 121.19, 'low': 100.99, 'close': 116.14, 'volume': 10099.01}, {'date': '2023-04-13', 'open': 111.08, 'high': 121.18, 'low': 100.98, 'close': 116.13, 'volume': 10098.04}, {'date': '2023-04-14', 'open': 111.07, 'high': 121.17, 'low': 100.97, 'close': 116.12, 'volume': 10097.09}, {'date': '2023-04-15', 'open': 111.06, 'high': 121.15, 'low': 100.96, 'close': 116.11, 'volume': 10096.15}, {'date': '2023-04-16', 'open': 111.05, 'high': 121.14, 'low': 100.95, 'close': 116.1, 'volume': 10095.24}, {'date': '2023-04-17', 'open': 111.04, 'high': 121.13, 'low': 100.94, 'close': 116.08, 'volume': 10094.34}, {'date': '2023-04-18', 'open': 111.03, 'high': 121.12, 'low': 100.93, 'close': 116.07, 'volume': 10093.46}, {'date': '2023-04-19', 'open': 111.02, 'high': 121.11, 'low': 100.93, 'close': 116.06, 'volume': 10092.59}, {'date': '2023-04-20', 'open': 111.01, 'high': 121.1, 'low': 100.92, 'close': 116.06, 'volume': 10091.74}, {'date': '2023-04-21', 'open': 111.0, 'high': 121.09, 'low': 100.91, 'close': 116.05, 'volume': 10090.91}, {'date': '2023-04-22', 'open': 110.99, 'high': 121.08, 'low': 100.9, 'close': 116.04, 'volume': 10090.09}, {'date': '2023-04-23', 'open': 110.98, 'high': 121.07, 'low': 100.89, 'close': 116.03, 'volume': 10089.29}, {'date': '2023-04-24', 'open': 110.97, 'high': 121.06, 'low': 100.88, 'close': 116.02, 'volume': 10088.5}, {'date': '2023-04-25', 'open': 110.96, 'high': 121.05, 'low': 100.88, 'close': 116.01, 'volume': 10087.72}, {'date': '2023-04-26', 'open': 110.96, 'high': 121.04, 'low': 100.87, 'close': 116.0, 'volume': 10086.96}, {'date': '2023-04-27', 'open': 110.95, 'high': 121.03, 'low': 100.86, 'close': 115.99, 'volume': 10086.21}, {'date': '2023-04-28', 'open': 110.94, 'high': 121.03, 'low': 100.85, 'close': 115.98, 'volume': 10085.47}, {'date': '2023-04-29', 'open': 110.93, 'high': 121.02, 'low': 100.85, 'close': 115.97, 'volume': 10084.75}, {'date': '2023-04-30', 'open': 110.92, 'high': 121.01, 'low': 100.84, 'close': 115.97, 'volume': 10084.03}, {'date': '2023-05-01', 'open': 110.92, 'high': 121.0, 'low': 100.83, 'close': 115.96, 'volume': 10083.33}, {'date': '2023-05-02', 'open': 110.91, 'high': 120.99, 'low': 100.83, 'close': 115.95, 'volume': 10082.64}, {'date': '2023-05-03', 'open': 110.9, 'high': 120.98, 'low': 100.82, 'close': 115.94, 'volume': 10081.97}, {'date': '2023-05-04', 'open': 110.89, 'high': 120.98, 'low': 100.81, 'close': 115.93, 'volume': 10081.3}, {'date': '2023-05-05', 'open': 110.89, 'high': 120.97, 'low': 100.81, 'close': 115.93, 'volume': 10080.65}, {'date': '2023-05-06', 'open': 110.88, 'high': 120.96, 'low': 100.8, 'close': 115.92, 'volume': 10080.0}, {'date': '2023-05-07', 'open': 110.87, 'high': 120.95, 'low': 100.79, 'close': 115.91, 'volume': 10079.37}, {'date': '2023-05-08', 'open': 110.87, 'high': 120.94, 'low': 100.79, 'close': 115.91, 'volume': 10078.74}, {'date': '2023-05-09', 'open': 110.86, 'high': 120.94, 'low': 100.78, 'close': 115.9, 'volume': 10078.12}, {'date': '2023-05-10', 'open': 110.85, 'high': 120.93, 'low': 100.78, 'close': 115.89, 'volume': 10077.52}, {'date': '2023-05-11', 'open': 110.85, 'high': 120.92, 'low': 100.77, 'close': 115.88, 'volume': 10076.92}, {'date': '2023-05-12', 'open': 110.84, 'high': 120.92, 'low': 100.76, 'close': 115.88, 'volume': 10076.34}, {'date': '2023-05-13', 'open': 110.83, 'high': 120.91, 'low': 100.76, 'close': 115.87, 'volume': 10075.76}, {'date': '2023-05-14', 'open': 110.83, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10075.19}, {'date': '2023-05-15', 'open': 110.82, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10074.63}, {'date': '2023-05-16', 'open': 110.81, 'high': 120.89, 'low': 100.74, 'close': 115.85, 'volume': 10074.07}, {'date': '2023-05-17', 'open': 110.81, 'high': 120.88, 'low': 100.74, 'close': 115.85, 'volume': 10073.53}, {'date': '2023-05-18', 'open': 110.8, 'high': 120.88, 'low': 100.73, 'close': 115.84, 'volume': 10072.99}, {'date': '2023-05-19', 'open': 110.8, 'high': 120.87, 'low': 100.72, 'close': 115.83, 'volume': 10072.46}, {'date': '2023-05-20', 'open': 110.79, 'high': 120.86, 'low': 100.72, 'close': 115.83, 'volume': 10071.94}, {'date': '2023-05-21', 'open': 110.79, 'high': 120.86, 'low': 100.71, 'close': 115.82, 'volume': 10071.43}, {'date': '2023-05-22', 'open': 110.78, 'high': 120.85, 'low': 100.71, 'close': 115.82, 'volume': 10070.92}, {'date': '2023-05-23', 'open': 110.77, 'high': 120.85, 'low': 100.7, 'close': 115.81, 'volume': 10070.42}, {'date': '2023-05-24', 'open': 110.77, 'high': 120.84, 'low': 100.7, 'close': 115.8, 'volume': 10069.93}, {'date': '2023-05-25', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.8, 'volume': 10069.44}, {'date': '2023-05-26', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.79, 'volume': 10068.97}, {'date': '2023-05-27', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.79, 'volume': 10068.49}, {'date': '2023-05-28', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.78, 'volume': 10068.03}, {'date': '2023-05-29', 'open': 110.74, 'high': 120.81, 'low': 100.68, 'close': 115.78, 'volume': 10067.57}, {'date': '2023-05-30', 'open': 110.74, 'high': 120.81, 'low': 100.67, 'close': 115.77, 'volume': 10067.11}, {'date': '2023-05-31', 'open': 110.73, 'high': 120.8, 'low': 100.67, 'close': 115.77, 'volume': 10066.67}, {'date': '2023-06-01', 'open': 110.73, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10066.23}, {'date': '2023-06-02', 'open': 110.72, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10065.79}, {'date': '2023-06-03', 'open': 110.72, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10065.36}, {'date': '2023-06-04', 'open': 110.71, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10064.94}, {'date': '2023-06-05', 'open': 110.71, 'high': 120.77, 'low': 100.65, 'close': 115.74, 'volume': 10064.52}, {'date': '2023-06-06', 'open': 110.71, 'high': 120.77, 'low': 100.64, 'close': 115.74, 'volume': 10064.1}, {'date': '2023-06-07', 'open': 110.7, 'high': 120.76, 'low': 100.64, 'close': 115.73, 'volume': 10063.69}, {'date': '2023-06-08', 'open': 110.7, 'high': 120.76, 'low': 100.63, 'close': 115.73, 'volume': 10063.29}, {'date': '2023-06-09', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.89}, {'date': '2023-06-10', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.5}, {'date': '2023-06-11', 'open': 110.68, 'high': 120.75, 'low': 100.62, 'close': 115.71, 'volume': 10062.11}, {'date': '2023-06-12', 'open': 110.68, 'high': 120.74, 'low': 100.62, 'close': 115.71, 'volume': 10061.73}, {'date': '2023-06-13', 'open': 110.67, 'high': 120.74, 'low': 100.61, 'close': 115.71, 'volume': 10061.35}, {'date': '2023-06-14', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.98}, {'date': '2023-06-15', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.61}, {'date': '2023-06-16', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10060.24}, {'date': '2023-06-17', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10059.88}, {'date': '2023-06-18', 'open': 110.65, 'high': 120.71, 'low': 100.6, 'close': 115.68, 'volume': 10059.52}, {'date': '2023-06-19', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10059.17}, {'date': '2023-06-20', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10058.82}, {'date': '2023-06-21', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.48}, {'date': '2023-06-22', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.14}, {'date': '2023-06-23', 'open': 110.64, 'high': 120.69, 'low': 100.58, 'close': 115.66, 'volume': 10057.8}, {'date': '2023-06-24', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.47}, {'date': '2023-06-25', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.14}, {'date': '2023-06-26', 'open': 110.62, 'high': 120.68, 'low': 100.57, 'close': 115.65, 'volume': 10056.82}, {'date': '2023-06-27', 'open': 110.62, 'high': 120.68, 'low': 100.56, 'close': 115.65, 'volume': 10056.5}, {'date': '2023-06-28', 'open': 110.62, 'high': 120.67, 'low': 100.56, 'close': 115.65, 'volume': 10056.18}, {'date': '2023-06-29', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.87}, {'date': '2023-06-30', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.56}, {'date': '2023-07-01', 'open': 110.61, 'high': 120.66, 'low': 100.55, 'close': 115.64, 'volume': 10055.25}, {'date': '2023-07-02', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.95}, {'date': '2023-07-03', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.64}, {'date': '2023-07-04', 'open': 110.6, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.35}, {'date': '2023-07-05', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.05}, {'date': '2023-07-06', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10053.76}, {'date': '2023-07-07', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.48}, {'date': '2023-07-08', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.19}, {'date': '2023-07-09', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.91}, {'date': '2023-07-10', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.63}, {'date': '2023-07-11', 'open': 110.58, 'high': 120.63, 'low': 100.52, 'close': 115.6, 'volume': 10052.36}, {'date': '2023-07-12', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10052.08}, {'date': '2023-07-13', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10051.81}, {'date': '2023-07-14', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.59, 'volume': 10051.55}, {'date': '2023-07-15', 'open': 110.56, 'high': 120.62, 'low': 100.51, 'close': 115.59, 'volume': 10051.28}, {'date': '2023-07-16', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.59, 'volume': 10051.02}, {'date': '2023-07-17', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.76}, {'date': '2023-07-18', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.51}, {'date': '2023-07-19', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.58, 'volume': 10050.25}, {'date': '2023-07-20', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10050.0}, {'date': '2023-07-21', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10049.75}, {'date': '2023-07-22', 'open': 110.54, 'high': 120.59, 'low': 100.5, 'close': 115.57, 'volume': 10049.5}, {'date': '2023-07-23', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.57, 'volume': 10049.26}, {'date': '2023-07-24', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10049.02}, {'date': '2023-07-25', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10048.78}, {'date': '2023-07-26', 'open': 110.53, 'high': 120.58, 'low': 100.49, 'close': 115.56, 'volume': 10048.54}, {'date': '2023-07-27', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.56, 'volume': 10048.31}, {'date': '2023-07-28', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.55, 'volume': 10048.08}, {'date': '2023-07-29', 'open': 110.53, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.85}, {'date': '2023-07-30', 'open': 110.52, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.62}, {'date': '2023-07-31', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.55, 'volume': 10047.39}, {'date': '2023-08-01', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.54, 'volume': 10047.17}, {'date': '2023-08-02', 'open': 110.52, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.95}, {'date': '2023-08-03', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.73}, {'date': '2023-08-04', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.53, 'volume': 10046.51}, {'date': '2023-08-05', 'open': 110.51, 'high': 120.56, 'low': 100.46, 'close': 115.53, 'volume': 10046.3}, {'date': '2023-08-06', 'open': 110.51, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10046.08}, {'date': '2023-08-07', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.87}, {'date': '2023-08-08', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.66}, {'date': '2023-08-09', 'open': 110.5, 'high': 120.55, 'low': 100.45, 'close': 115.52, 'volume': 10045.45}, {'date': '2023-08-10', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.25}, {'date': '2023-08-11', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.05}, {'date': '2023-08-12', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10044.84}, {'date': '2023-08-13', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.51, 'volume': 10044.64}, {'date': '2023-08-14', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.44}, {'date': '2023-08-15', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.25}, {'date': '2023-08-16', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.05}, {'date': '2023-08-17', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.5, 'volume': 10043.86}, {'date': '2023-08-18', 'open': 110.48, 'high': 120.52, 'low': 100.44, 'close': 115.5, 'volume': 10043.67}, {'date': '2023-08-19', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.48}, {'date': '2023-08-20', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.29}, {'date': '2023-08-21', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.1}, {'date': '2023-08-22', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.49, 'volume': 10042.92}, {'date': '2023-08-23', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.74}, {'date': '2023-08-24', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.55}, {'date': '2023-08-25', 'open': 110.47, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.37}, {'date': '2023-08-26', 'open': 110.46, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.19}, {'date': '2023-08-27', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10042.02}, {'date': '2023-08-28', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.84}, {'date': '2023-08-29', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.67}, {'date': '2023-08-30', 'open': 110.46, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.49}, {'date': '2023-08-31', 'open': 110.45, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.32}, {'date': '2023-09-01', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10041.15}, {'date': '2023-09-02', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.98}, {'date': '2023-09-03', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.82}, {'date': '2023-09-04', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.65}, {'date': '2023-09-05', 'open': 110.45, 'high': 120.49, 'low': 100.4, 'close': 115.47, 'volume': 10040.49}, {'date': '2023-09-06', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.32}, {'date': '2023-09-07', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.16}, {'date': '2023-09-08', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.0}, {'date': '2023-09-09', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.84}, {'date': '2023-09-10', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.68}, {'date': '2023-09-11', 'open': 110.43, 'high': 120.47, 'low': 100.4, 'close': 115.45, 'volume': 10039.53}, {'date': '2023-09-12', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.37}, {'date': '2023-09-13', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.22}, {'date': '2023-09-14', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.06}, {'date': '2023-09-15', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.91}, {'date': '2023-09-16', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.76}, {'date': '2023-09-17', 'open': 110.42, 'high': 120.46, 'low': 100.39, 'close': 115.44, 'volume': 10038.61}, {'date': '2023-09-18', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.46}, {'date': '2023-09-19', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.31}, {'date': '2023-09-20', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.17}, {'date': '2023-09-21', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.02}, {'date': '2023-09-22', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.44, 'volume': 10037.88}, {'date': '2023-09-23', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.74}, {'date': '2023-09-24', 'open': 110.41, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.59}, {'date': '2023-09-25', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.45}, {'date': '2023-09-26', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.31}, {'date': '2023-09-27', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.17}, {'date': '2023-09-28', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.43, 'volume': 10037.04}, {'date': '2023-09-29', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.9}, {'date': '2023-09-30', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.76}, {'date': '2023-10-01', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.63}, {'date': '2023-10-02', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.5}, {'date': '2023-10-03', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.36}, {'date': '2023-10-04', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.23}, {'date': '2023-10-05', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.1}, {'date': '2023-10-06', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.97}, {'date': '2023-10-07', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.84}, {'date': '2023-10-08', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.71}, {'date': '2023-10-09', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.59}, {'date': '2023-10-10', 'open': 110.39, 'high': 120.43, 'low': 100.35, 'close': 115.41, 'volume': 10035.46}, {'date': '2023-10-11', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.41, 'volume': 10035.34}, {'date': '2023-10-12', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.21}, {'date': '2023-10-13', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.09}, {'date': '2023-10-14', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.97}, {'date': '2023-10-15', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.84}, {'date': '2023-10-16', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.72}, {'date': '2023-10-17', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.6}, {'date': '2023-10-18', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.48}, {'date': '2023-10-19', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.36}, {'date': '2023-10-20', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.25}, {'date': '2023-10-21', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.13}, {'date': '2023-10-22', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.01}, {'date': '2023-10-23', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.9}, {'date': '2023-10-24', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.78}, {'date': '2023-10-25', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.67}, {'date': '2023-10-26', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.56}, {'date': '2023-10-27', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.44}, {'date': '2023-10-28', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.33}, {'date': '2023-10-29', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.22}, {'date': '2023-10-30', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.11}, {'date': '2023-10-31', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.0}, {'date': '2023-11-01', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.89}, {'date': '2023-11-02', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.79}, {'date': '2023-11-03', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.68}, {'date': '2023-11-04', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.37, 'volume': 10032.57}, {'date': '2023-11-05', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.47}, {'date': '2023-11-06', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.36}, {'date': '2023-11-07', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.26}, {'date': '2023-11-08', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.15}, {'date': '2023-11-09', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10032.05}, {'date': '2023-11-10', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.95}, {'date': '2023-11-11', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.85}, {'date': '2023-11-12', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.75}, {'date': '2023-11-13', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.65}, {'date': '2023-11-14', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.55}, {'date': '2023-11-15', 'open': 110.35, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.45}, {'date': '2023-11-16', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.35}, {'date': '2023-11-17', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.25}, {'date': '2023-11-18', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.15}, {'date': '2023-11-19', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.06}, {'date': '2023-11-20', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10030.96}, {'date': '2023-11-21', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.86}, {'date': '2023-11-22', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.77}, {'date': '2023-11-23', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.67}, {'date': '2023-11-24', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.58}, {'date': '2023-11-25', 'open': 110.34, 'high': 120.37, 'low': 100.3, 'close': 115.35, 'volume': 10030.49}, {'date': '2023-11-26', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.4}, {'date': '2023-11-27', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.3}, {'date': '2023-11-28', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.21}, {'date': '2023-11-29', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.12}, {'date': '2023-11-30', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.03}, {'date': '2023-12-01', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.94}, {'date': '2023-12-02', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.85}, {'date': '2023-12-03', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.76}, {'date': '2023-12-04', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.67}, {'date': '2023-12-05', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.59}, {'date': '2023-12-06', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.5}, {'date': '2023-12-07', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.41}, {'date': '2023-12-08', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.33}, {'date': '2023-12-09', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.24}, {'date': '2023-12-10', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.15}, {'date': '2023-12-11', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10029.07}, {'date': '2023-12-12', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.99}, {'date': '2023-12-13', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.9}, {'date': '2023-12-14', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.82}, {'date': '2023-12-15', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.74}, {'date': '2023-12-16', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.65}, {'date': '2023-12-17', 'open': 110.31, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.57}, {'date': '2023-12-18', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.49}, {'date': '2023-12-19', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.41}, {'date': '2023-12-20', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.33}, {'date': '2023-12-21', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.25}, {'date': '2023-12-22', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.17}, {'date': '2023-12-23', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.09}, {'date': '2023-12-24', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.01}, {'date': '2023-12-25', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10027.93}, {'date': '2023-12-26', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.86}, {'date': '2023-12-27', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.78}, {'date': '2023-12-28', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.7}, {'date': '2023-12-29', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.62}, {'date': '2023-12-30', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.55}, {'date': '2023-12-31', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.47}, {'date': '2024-01-01', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.4}, {'date': '2024-01-02', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.32}, {'date': '2024-01-03', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.25}, {'date': '2024-01-04', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.17}, {'date': '2024-01-05', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.1}, {'date': '2024-01-06', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10027.03}, {'date': '2024-01-07', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.95}, {'date': '2024-01-08', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.88}, {'date': '2024-01-09', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.81}, {'date': '2024-01-10', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.74}, {'date': '2024-01-11', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.67}, {'date': '2024-01-12', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.6}, {'date': '2024-01-13', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.53}, {'date': '2024-01-14', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.46}, {'date': '2024-01-15', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.39}, {'date': '2024-01-16', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.32}, {'date': '2024-01-17', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.25}, {'date': '2024-01-18', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.18}, {'date': '2024-01-19', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.11}, {'date': '2024-01-20', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.04}, {'date': '2024-01-21', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.97}, {'date': '2024-01-22', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.91}, {'date': '2024-01-23', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.84}, {'date': '2024-01-24', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.77}, {'date': '2024-01-25', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.71}, {'date': '2024-01-26', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.64}, {'date': '2024-01-27', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.58}, {'date': '2024-01-28', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.51}, {'date': '2024-01-29', 'open': 110.28, 'high': 120.31, 'low': 100.25, 'close': 115.29, 'volume': 10025.45}, {'date': '2024-01-30', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.38}, {'date': '2024-01-31', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.32}, {'date': '2024-02-01', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.25}, {'date': '2024-02-02', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.19}, {'date': '2024-02-03', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.13}, {'date': '2024-02-04', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.06}, {'date': '2024-02-05', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.0}, {'date': '2024-02-06', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.94}, {'date': '2024-02-07', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.88}, {'date': '2024-02-08', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.81}, {'date': '2024-02-09', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.75}, {'date': '2024-02-10', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.69}, {'date': '2024-02-11', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.63}, {'date': '2024-02-12', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.57}, {'date': '2024-02-13', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.51}, {'date': '2024-02-14', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.45}, {'date': '2024-02-15', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.39}, {'date': '2024-02-16', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.33}, {'date': '2024-02-17', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.27}, {'date': '2024-02-18', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.21}, {'date': '2024-02-19', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.15}, {'date': '2024-02-20', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.1}, {'date': '2024-02-21', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.04}, {'date': '2024-02-22', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.98}, {'date': '2024-02-23', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.92}, {'date': '2024-02-24', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.87}, {'date': '2024-02-25', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.81}, {'date': '2024-02-26', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.75}, {'date': '2024-02-27', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.7}, {'date': '2024-02-28', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.64}, {'date': '2024-02-29', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.58}, {'date': '2024-03-01', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.53}, {'date': '2024-03-02', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.47}, {'date': '2024-03-03', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.42}, {'date': '2024-03-04', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.36}, {'date': '2024-03-05', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.31}, {'date': '2024-03-06', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.26}, {'date': '2024-03-07', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.2}, {'date': '2024-03-08', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.15}, {'date': '2024-03-09', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.09}, {'date': '2024-03-10', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10023.04}, {'date': '2024-03-11', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.99}, {'date': '2024-03-12', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.94}, {'date': '2024-03-13', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.88}, {'date': '2024-03-14', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.83}, {'date': '2024-03-15', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.78}, {'date': '2024-03-16', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.73}, {'date': '2024-03-17', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.68}, {'date': '2024-03-18', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.62}, {'date': '2024-03-19', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.57}, {'date': '2024-03-20', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.52}, {'date': '2024-03-21', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.47}, {'date': '2024-03-22', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.42}, {'date': '2024-03-23', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.37}, {'date': '2024-03-24', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.32}, {'date': '2024-03-25', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.27}, {'date': '2024-03-26', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.22}, {'date': '2024-03-27', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.17}, {'date': '2024-03-28', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.12}, {'date': '2024-03-29', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.08}, {'date': '2024-03-30', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.03}, {'date': '2024-03-31', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.98}, {'date': '2024-04-01', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.93}, {'date': '2024-04-02', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.88}, {'date': '2024-04-03', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.83}, {'date': '2024-04-04', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.79}, {'date': '2024-04-05', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.74}, {'date': '2024-04-06', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.69}, {'date': '2024-04-07', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.65}, {'date': '2024-04-08', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.6}, {'date': '2024-04-09', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.55}, {'date': '2024-04-10', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.51}, {'date': '2024-04-11', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.46}, {'date': '2024-04-12', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.41}, {'date': '2024-04-13', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.37}, {'date': '2024-04-14', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.32}, {'date': '2024-04-15', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.24, 'volume': 10021.28}, {'date': '2024-04-16', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.23}, {'date': '2024-04-17', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.19}, {'date': '2024-04-18', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.14}, {'date': '2024-04-19', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.1}, {'date': '2024-04-20', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.05}, {'date': '2024-04-21', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.01}, {'date': '2024-04-22', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.96}, {'date': '2024-04-23', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.92}, {'date': '2024-04-24', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.88}, {'date': '2024-04-25', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.83}, {'date': '2024-04-26', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.79}, {'date': '2024-04-27', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.75}, {'date': '2024-04-28', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.7}, {'date': '2024-04-29', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.66}, {'date': '2024-04-30', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.62}, {'date': '2024-05-01', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.58}, {'date': '2024-05-02', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.53}, {'date': '2024-05-03', 'open': 110.23, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.49}, {'date': '2024-05-04', 'open': 110.22, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.45}, {'date': '2024-05-05', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.41}, {'date': '2024-05-06', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.37}, {'date': '2024-05-07', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.33}, {'date': '2024-05-08', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.28}, {'date': '2024-05-09', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.24}, {'date': '2024-05-10', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.2}, {'date': '2024-05-11', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.16}, {'date': '2024-05-12', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.12}, {'date': '2024-05-13', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.08}, {'date': '2024-05-14', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.04}, {'date': '2024-05-15', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.0}, {'date': '2024-05-16', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.96}, {'date': '2024-05-17', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.92}, {'date': '2024-05-18', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.88}, {'date': '2024-05-19', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.84}, {'date': '2024-05-20', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.8}, {'date': '2024-05-21', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.76}, {'date': '2024-05-22', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.72}, {'date': '2024-05-23', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.69}, {'date': '2024-05-24', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.65}, {'date': '2024-05-25', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.61}, {'date': '2024-05-26', 'open': 110.22, 'high': 120.23, 'low': 100.2, 'close': 115.23, 'volume': 10019.57}, {'date': '2024-05-27', 'open': 110.21, 'high': 120.23, 'low': 100.2, 'close': 115.22, 'volume': 10019.53}, {'date': '2024-05-28', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.49}, {'date': '2024-05-29', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.46}, {'date': '2024-05-30', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.42}, {'date': '2024-05-31', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.38}, {'date': '2024-06-01', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.34}, {'date': '2024-06-02', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.31}, {'date': '2024-06-03', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.27}, {'date': '2024-06-04', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.23}, {'date': '2024-06-05', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.19}, {'date': '2024-06-06', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.16}, {'date': '2024-06-07', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.12}, {'date': '2024-06-08', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.08}, {'date': '2024-06-09', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.05}, {'date': '2024-06-10', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.01}, {'date': '2024-06-11', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.98}, {'date': '2024-06-12', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.94}, {'date': '2024-06-13', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.9}, {'date': '2024-06-14', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.87}, {'date': '2024-06-15', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.83}, {'date': '2024-06-16', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.8}, {'date': '2024-06-17', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.76}, {'date': '2024-06-18', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.22, 'volume': 10018.73}, {'date': '2024-06-19', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.69}, {'date': '2024-06-20', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.66}, {'date': '2024-06-21', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.62}, {'date': '2024-06-22', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.59}, {'date': '2024-06-23', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.55}, {'date': '2024-06-24', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.52}, {'date': '2024-06-25', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.48}, {'date': '2024-06-26', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.45}, {'date': '2024-06-27', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.42}, {'date': '2024-06-28', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.38}, {'date': '2024-06-29', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.35}, {'date': '2024-06-30', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.32}, {'date': '2024-07-01', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.28}, {'date': '2024-07-02', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.25}, {'date': '2024-07-03', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.21}, {'date': '2024-07-04', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.18}, {'date': '2024-07-05', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.15}, {'date': '2024-07-06', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.12}, {'date': '2024-07-07', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.08}, {'date': '2024-07-08', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.05}, {'date': '2024-07-09', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.02}, {'date': '2024-07-10', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.99}, {'date': '2024-07-11', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.95}, {'date': '2024-07-12', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.92}, {'date': '2024-07-13', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.89}, {'date': '2024-07-14', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.86}, {'date': '2024-07-15', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.83}, {'date': '2024-07-16', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.79}, {'date': '2024-07-17', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.76}, {'date': '2024-07-18', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.73}, {'date': '2024-07-19', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.7}, {'date': '2024-07-20', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.67}, {'date': '2024-07-21', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.64}, {'date': '2024-07-22', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.61}, {'date': '2024-07-23', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.57}, {'date': '2024-07-24', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.54}, {'date': '2024-07-25', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.51}, {'date': '2024-07-26', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.48}, {'date': '2024-07-27', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.45}, {'date': '2024-07-28', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.42}, {'date': '2024-07-29', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.39}, {'date': '2024-07-30', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.36}, {'date': '2024-07-31', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.33}, {'date': '2024-08-01', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.3}, {'date': '2024-08-02', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.27}, {'date': '2024-08-03', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.24}, {'date': '2024-08-04', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.21}, {'date': '2024-08-05', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.18}, {'date': '2024-08-06', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.15}, {'date': '2024-08-07', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.12}, {'date': '2024-08-08', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.09}, {'date': '2024-08-09', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.06}, {'date': '2024-08-10', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.04}, {'date': '2024-08-11', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.01}, {'date': '2024-08-12', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10016.98}, {'date': '2024-08-13', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.95}, {'date': '2024-08-14', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.92}, {'date': '2024-08-15', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.89}, {'date': '2024-08-16', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.86}, {'date': '2024-08-17', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.84}, {'date': '2024-08-18', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.81}, {'date': '2024-08-19', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.78}, {'date': '2024-08-20', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.75}, {'date': '2024-08-21', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.72}, {'date': '2024-08-22', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.69}, {'date': '2024-08-23', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.67}, {'date': '2024-08-24', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.64}, {'date': '2024-08-25', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.61}, {'date': '2024-08-26', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.58}, {'date': '2024-08-27', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.56}, {'date': '2024-08-28', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.53}, {'date': '2024-08-29', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.5}, {'date': '2024-08-30', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.47}, {'date': '2024-08-31', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.45}, {'date': '2024-09-01', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.42}, {'date': '2024-09-02', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.39}, {'date': '2024-09-03', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.37}, {'date': '2024-09-04', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.34}, {'date': '2024-09-05', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.31}, {'date': '2024-09-06', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.29}, {'date': '2024-09-07', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.26}, {'date': '2024-09-08', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.23}, {'date': '2024-09-09', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.21}, {'date': '2024-09-10', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.18}, {'date': '2024-09-11', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.16}, {'date': '2024-09-12', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.13}, {'date': '2024-09-13', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.1}, {'date': '2024-09-14', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.08}, {'date': '2024-09-15', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.05}, {'date': '2024-09-16', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.03}, {'date': '2024-09-17', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.0}, {'date': '2024-09-18', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.97}, {'date': '2024-09-19', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.95}, {'date': '2024-09-20', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.92}, {'date': '2024-09-21', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.9}, {'date': '2024-09-22', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.87}, {'date': '2024-09-23', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.85}, {'date': '2024-09-24', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.82}, {'date': '2024-09-25', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.8}, {'date': '2024-09-26', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.77}, {'date': '2024-09-27', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.75}, {'date': '2024-09-28', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.72}, {'date': '2024-09-29', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.7}, {'date': '2024-09-30', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.67}, {'date': '2024-10-01', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.65}, {'date': '2024-10-02', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.62}, {'date': '2024-10-03', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.6}, {'date': '2024-10-04', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.58}, {'date': '2024-10-05', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.55}, {'date': '2024-10-06', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.53}, {'date': '2024-10-07', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.5}, {'date': '2024-10-08', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.48}, {'date': '2024-10-09', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.46}, {'date': '2024-10-10', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.43}, {'date': '2024-10-11', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.41}, {'date': '2024-10-12', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.38}, {'date': '2024-10-13', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.36}, {'date': '2024-10-14', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.34}, {'date': '2024-10-15', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.31}, {'date': '2024-10-16', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.29}, {'date': '2024-10-17', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.27}, {'date': '2024-10-18', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.24}, {'date': '2024-10-19', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.22}, {'date': '2024-10-20', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.2}, {'date': '2024-10-21', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.17}, {'date': '2024-10-22', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.15}, {'date': '2024-10-23', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.13}, {'date': '2024-10-24', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.11}, {'date': '2024-10-25', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.08}, {'date': '2024-10-26', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.06}, {'date': '2024-10-27', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.04}, {'date': '2024-10-28', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.02}, {'date': '2024-10-29', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.99}, {'date': '2024-10-30', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.97}, {'date': '2024-10-31', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.95}, {'date': '2024-11-01', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.93}, {'date': '2024-11-02', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.9}, {'date': '2024-11-03', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.88}, {'date': '2024-11-04', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.86}, {'date': '2024-11-05', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.84}, {'date': '2024-11-06', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.81}, {'date': '2024-11-07', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.79}, {'date': '2024-11-08', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.77}, {'date': '2024-11-09', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.75}, {'date': '2024-11-10', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.73}, {'date': '2024-11-11', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.71}, {'date': '2024-11-12', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.68}, {'date': '2024-11-13', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.66}, {'date': '2024-11-14', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.64}, {'date': '2024-11-15', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.62}, {'date': '2024-11-16', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.6}, {'date': '2024-11-17', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.58}, {'date': '2024-11-18', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.56}, {'date': '2024-11-19', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.53}, {'date': '2024-11-20', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.51}, {'date': '2024-11-21', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.49}, {'date': '2024-11-22', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.47}, {'date': '2024-11-23', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.45}, {'date': '2024-11-24', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.43}, {'date': '2024-11-25', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.41}, {'date': '2024-11-26', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.39}, {'date': '2024-11-27', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.37}, {'date': '2024-11-28', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.35}, {'date': '2024-11-29', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.33}, {'date': '2024-11-30', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.31}, {'date': '2024-12-01', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.29}, {'date': '2024-12-02', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.27}, {'date': '2024-12-03', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.25}, {'date': '2024-12-04', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.22}, {'date': '2024-12-05', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.2}, {'date': '2024-12-06', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.18}, {'date': '2024-12-07', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.16}, {'date': '2024-12-08', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.14}, {'date': '2024-12-09', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.12}, {'date': '2024-12-10', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.1}, {'date': '2024-12-11', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.08}, {'date': '2024-12-12', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.06}, {'date': '2024-12-13', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.04}, {'date': '2024-12-14', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.03}, {'date': '2024-12-15', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.01}, {'date': '2024-12-16', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.99}, {'date': '2024-12-17', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.97}, {'date': '2024-12-18', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.95}, {'date': '2024-12-19', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.93}, {'date': '2024-12-20', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.91}, {'date': '2024-12-21', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.89}, {'date': '2024-12-22', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.87}, {'date': '2024-12-23', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.85}, {'date': '2024-12-24', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.83}, {'date': '2024-12-25', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.81}, {'date': '2024-12-26', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.79}, {'date': '2024-12-27', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.77}, {'date': '2024-12-28', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.76}, {'date': '2024-12-29', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.74}, {'date': '2024-12-30', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.72}, {'date': '2024-12-31', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.7}, {'date': '2025-01-01', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.68}, {'date': '2025-01-02', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.66}, {'date': '2025-01-03', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.64}, {'date': '2025-01-04', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.62}, {'date': '2025-01-05', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.61}, {'date': '2025-01-06', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.59}, {'date': '2025-01-07', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.57}, {'date': '2025-01-08', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.55}, {'date': '2025-01-09', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.53}, {'date': '2025-01-10', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.51}, {'date': '2025-01-11', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.16, 'volume': 10013.5}, {'date': '2025-01-12', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.48}, {'date': '2025-01-13', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.46}, {'date': '2025-01-14', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.44}, {'date': '2025-01-15', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.42}, {'date': '2025-01-16', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.4}, {'date': '2025-01-17', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.39}, {'date': '2025-01-18', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.37}, {'date': '2025-01-19', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.35}, {'date': '2025-01-20', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.33}, {'date': '2025-01-21', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.32}, {'date': '2025-01-22', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.3}, {'date': '2025-01-23', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.28}, {'date': '2025-01-24', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.26}, {'date': '2025-01-25', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.25}, {'date': '2025-01-26', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.23}, {'date': '2025-01-27', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.21}, {'date': '2025-01-28', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.19}, {'date': '2025-01-29', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.18}, {'date': '2025-01-30', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.16}, {'date': '2025-01-31', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.14}, {'date': '2025-02-01', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.12}, {'date': '2025-02-02', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.11}, {'date': '2025-02-03', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.09}, {'date': '2025-02-04', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.07}, {'date': '2025-02-05', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.05}, {'date': '2025-02-06', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.04}, {'date': '2025-02-07', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.02}, {'date': '2025-02-08', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.0}, {'date': '2025-02-09', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.99}, {'date': '2025-02-10', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.97}, {'date': '2025-02-11', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.95}, {'date': '2025-02-12', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.94}, {'date': '2025-02-13', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.92}, {'date': '2025-02-14', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.9}, {'date': '2025-02-15', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.89}, {'date': '2025-02-16', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.87}, {'date': '2025-02-17', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.85}, {'date': '2025-02-18', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.84}, {'date': '2025-02-19', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.82}, {'date': '2025-02-20', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.8}, {'date': '2025-02-21', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.79}, {'date': '2025-02-22', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.77}, {'date': '2025-02-23', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.76}, {'date': '2025-02-24', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.74}, {'date': '2025-02-25', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.72}, {'date': '2025-02-26', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.71}, {'date': '2025-02-27', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.69}, {'date': '2025-02-28', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.67}, {'date': '2025-03-01', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.66}, {'date': '2025-03-02', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.64}, {'date': '2025-03-03', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.63}, {'date': '2025-03-04', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.61}, {'date': '2025-03-05', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.59}, {'date': '2025-03-06', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.58}, {'date': '2025-03-07', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.56}, {'date': '2025-03-08', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.55}, {'date': '2025-03-09', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.53}, {'date': '2025-03-10', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.52}, {'date': '2025-03-11', 'open': 110.14, 'high': 120.15, 'low': 100.12, 'close': 115.14, 'volume': 10012.5}], factors_data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}, {'date': '2023-03-03', 'open': 111.8, 'high': 121.97, 'low': 101.64, 'close': 116.89, 'volume': 10163.93}, {'date': '2023-03-04', 'open': 111.77, 'high': 121.94, 'low': 101.61, 'close': 116.85, 'volume': 10161.29}, {'date': '2023-03-05', 'open': 111.75, 'high': 121.9, 'low': 101.59, 'close': 116.83, 'volume': 10158.73}, {'date': '2023-03-06', 'open': 111.72, 'high': 121.88, 'low': 101.56, 'close': 116.8, 'volume': 10156.25}, {'date': '2023-03-07', 'open': 111.69, 'high': 121.85, 'low': 101.54, 'close': 116.77, 'volume': 10153.85}, {'date': '2023-03-08', 'open': 111.67, 'high': 121.82, 'low': 101.52, 'close': 116.74, 'volume': 10151.52}, {'date': '2023-03-09', 'open': 111.64, 'high': 121.79, 'low': 101.49, 'close': 116.72, 'volume': 10149.25}, {'date': '2023-03-10', 'open': 111.62, 'high': 121.76, 'low': 101.47, 'close': 116.69, 'volume': 10147.06}, {'date': '2023-03-11', 'open': 111.59, 'high': 121.74, 'low': 101.45, 'close': 116.67, 'volume': 10144.93}, {'date': '2023-03-12', 'open': 111.57, 'high': 121.71, 'low': 101.43, 'close': 116.64, 'volume': 10142.86}, {'date': '2023-03-13', 'open': 111.55, 'high': 121.69, 'low': 101.41, 'close': 116.62, 'volume': 10140.85}, {'date': '2023-03-14', 'open': 111.53, 'high': 121.67, 'low': 101.39, 'close': 116.6, 'volume': 10138.89}, {'date': '2023-03-15', 'open': 111.51, 'high': 121.64, 'low': 101.37, 'close': 116.58, 'volume': 10136.99}, {'date': '2023-03-16', 'open': 111.49, 'high': 121.62, 'low': 101.35, 'close': 116.55, 'volume': 10135.14}, {'date': '2023-03-17', 'open': 111.47, 'high': 121.6, 'low': 101.33, 'close': 116.53, 'volume': 10133.33}, {'date': '2023-03-18', 'open': 111.45, 'high': 121.58, 'low': 101.32, 'close': 116.51, 'volume': 10131.58}, {'date': '2023-03-19', 'open': 111.43, 'high': 121.56, 'low': 101.3, 'close': 116.49, 'volume': 10129.87}, {'date': '2023-03-20', 'open': 111.41, 'high': 121.54, 'low': 101.28, 'close': 116.47, 'volume': 10128.21}, {'date': '2023-03-21', 'open': 111.39, 'high': 121.52, 'low': 101.27, 'close': 116.46, 'volume': 10126.58}, {'date': '2023-03-22', 'open': 111.38, 'high': 121.5, 'low': 101.25, 'close': 116.44, 'volume': 10125.0}, {'date': '2023-03-23', 'open': 111.36, 'high': 121.48, 'low': 101.23, 'close': 116.42, 'volume': 10123.46}, {'date': '2023-03-24', 'open': 111.34, 'high': 121.46, 'low': 101.22, 'close': 116.4, 'volume': 10121.95}, {'date': '2023-03-25', 'open': 111.33, 'high': 121.45, 'low': 101.2, 'close': 116.39, 'volume': 10120.48}, {'date': '2023-03-26', 'open': 111.31, 'high': 121.43, 'low': 101.19, 'close': 116.37, 'volume': 10119.05}, {'date': '2023-03-27', 'open': 111.29, 'high': 121.41, 'low': 101.18, 'close': 116.35, 'volume': 10117.65}, {'date': '2023-03-28', 'open': 111.28, 'high': 121.4, 'low': 101.16, 'close': 116.34, 'volume': 10116.28}, {'date': '2023-03-29', 'open': 111.26, 'high': 121.38, 'low': 101.15, 'close': 116.32, 'volume': 10114.94}, {'date': '2023-03-30', 'open': 111.25, 'high': 121.36, 'low': 101.14, 'close': 116.31, 'volume': 10113.64}, {'date': '2023-03-31', 'open': 111.24, 'high': 121.35, 'low': 101.12, 'close': 116.29, 'volume': 10112.36}, {'date': '2023-04-01', 'open': 111.22, 'high': 121.33, 'low': 101.11, 'close': 116.28, 'volume': 10111.11}, {'date': '2023-04-02', 'open': 111.21, 'high': 121.32, 'low': 101.1, 'close': 116.26, 'volume': 10109.89}, {'date': '2023-04-03', 'open': 111.2, 'high': 121.3, 'low': 101.09, 'close': 116.25, 'volume': 10108.7}, {'date': '2023-04-04', 'open': 111.18, 'high': 121.29, 'low': 101.08, 'close': 116.24, 'volume': 10107.53}, {'date': '2023-04-05', 'open': 111.17, 'high': 121.28, 'low': 101.06, 'close': 116.22, 'volume': 10106.38}, {'date': '2023-04-06', 'open': 111.16, 'high': 121.26, 'low': 101.05, 'close': 116.21, 'volume': 10105.26}, {'date': '2023-04-07', 'open': 111.15, 'high': 121.25, 'low': 101.04, 'close': 116.2, 'volume': 10104.17}, {'date': '2023-04-08', 'open': 111.13, 'high': 121.24, 'low': 101.03, 'close': 116.19, 'volume': 10103.09}, {'date': '2023-04-09', 'open': 111.12, 'high': 121.22, 'low': 101.02, 'close': 116.17, 'volume': 10102.04}, {'date': '2023-04-10', 'open': 111.11, 'high': 121.21, 'low': 101.01, 'close': 116.16, 'volume': 10101.01}, {'date': '2023-04-11', 'open': 111.1, 'high': 121.2, 'low': 101.0, 'close': 116.15, 'volume': 10100.0}, {'date': '2023-04-12', 'open': 111.09, 'high': 121.19, 'low': 100.99, 'close': 116.14, 'volume': 10099.01}, {'date': '2023-04-13', 'open': 111.08, 'high': 121.18, 'low': 100.98, 'close': 116.13, 'volume': 10098.04}, {'date': '2023-04-14', 'open': 111.07, 'high': 121.17, 'low': 100.97, 'close': 116.12, 'volume': 10097.09}, {'date': '2023-04-15', 'open': 111.06, 'high': 121.15, 'low': 100.96, 'close': 116.11, 'volume': 10096.15}, {'date': '2023-04-16', 'open': 111.05, 'high': 121.14, 'low': 100.95, 'close': 116.1, 'volume': 10095.24}, {'date': '2023-04-17', 'open': 111.04, 'high': 121.13, 'low': 100.94, 'close': 116.08, 'volume': 10094.34}, {'date': '2023-04-18', 'open': 111.03, 'high': 121.12, 'low': 100.93, 'close': 116.07, 'volume': 10093.46}, {'date': '2023-04-19', 'open': 111.02, 'high': 121.11, 'low': 100.93, 'close': 116.06, 'volume': 10092.59}, {'date': '2023-04-20', 'open': 111.01, 'high': 121.1, 'low': 100.92, 'close': 116.06, 'volume': 10091.74}, {'date': '2023-04-21', 'open': 111.0, 'high': 121.09, 'low': 100.91, 'close': 116.05, 'volume': 10090.91}, {'date': '2023-04-22', 'open': 110.99, 'high': 121.08, 'low': 100.9, 'close': 116.04, 'volume': 10090.09}, {'date': '2023-04-23', 'open': 110.98, 'high': 121.07, 'low': 100.89, 'close': 116.03, 'volume': 10089.29}, {'date': '2023-04-24', 'open': 110.97, 'high': 121.06, 'low': 100.88, 'close': 116.02, 'volume': 10088.5}, {'date': '2023-04-25', 'open': 110.96, 'high': 121.05, 'low': 100.88, 'close': 116.01, 'volume': 10087.72}, {'date': '2023-04-26', 'open': 110.96, 'high': 121.04, 'low': 100.87, 'close': 116.0, 'volume': 10086.96}, {'date': '2023-04-27', 'open': 110.95, 'high': 121.03, 'low': 100.86, 'close': 115.99, 'volume': 10086.21}, {'date': '2023-04-28', 'open': 110.94, 'high': 121.03, 'low': 100.85, 'close': 115.98, 'volume': 10085.47}, {'date': '2023-04-29', 'open': 110.93, 'high': 121.02, 'low': 100.85, 'close': 115.97, 'volume': 10084.75}, {'date': '2023-04-30', 'open': 110.92, 'high': 121.01, 'low': 100.84, 'close': 115.97, 'volume': 10084.03}, {'date': '2023-05-01', 'open': 110.92, 'high': 121.0, 'low': 100.83, 'close': 115.96, 'volume': 10083.33}, {'date': '2023-05-02', 'open': 110.91, 'high': 120.99, 'low': 100.83, 'close': 115.95, 'volume': 10082.64}, {'date': '2023-05-03', 'open': 110.9, 'high': 120.98, 'low': 100.82, 'close': 115.94, 'volume': 10081.97}, {'date': '2023-05-04', 'open': 110.89, 'high': 120.98, 'low': 100.81, 'close': 115.93, 'volume': 10081.3}, {'date': '2023-05-05', 'open': 110.89, 'high': 120.97, 'low': 100.81, 'close': 115.93, 'volume': 10080.65}, {'date': '2023-05-06', 'open': 110.88, 'high': 120.96, 'low': 100.8, 'close': 115.92, 'volume': 10080.0}, {'date': '2023-05-07', 'open': 110.87, 'high': 120.95, 'low': 100.79, 'close': 115.91, 'volume': 10079.37}, {'date': '2023-05-08', 'open': 110.87, 'high': 120.94, 'low': 100.79, 'close': 115.91, 'volume': 10078.74}, {'date': '2023-05-09', 'open': 110.86, 'high': 120.94, 'low': 100.78, 'close': 115.9, 'volume': 10078.12}, {'date': '2023-05-10', 'open': 110.85, 'high': 120.93, 'low': 100.78, 'close': 115.89, 'volume': 10077.52}, {'date': '2023-05-11', 'open': 110.85, 'high': 120.92, 'low': 100.77, 'close': 115.88, 'volume': 10076.92}, {'date': '2023-05-12', 'open': 110.84, 'high': 120.92, 'low': 100.76, 'close': 115.88, 'volume': 10076.34}, {'date': '2023-05-13', 'open': 110.83, 'high': 120.91, 'low': 100.76, 'close': 115.87, 'volume': 10075.76}, {'date': '2023-05-14', 'open': 110.83, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10075.19}, {'date': '2023-05-15', 'open': 110.82, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10074.63}, {'date': '2023-05-16', 'open': 110.81, 'high': 120.89, 'low': 100.74, 'close': 115.85, 'volume': 10074.07}, {'date': '2023-05-17', 'open': 110.81, 'high': 120.88, 'low': 100.74, 'close': 115.85, 'volume': 10073.53}, {'date': '2023-05-18', 'open': 110.8, 'high': 120.88, 'low': 100.73, 'close': 115.84, 'volume': 10072.99}, {'date': '2023-05-19', 'open': 110.8, 'high': 120.87, 'low': 100.72, 'close': 115.83, 'volume': 10072.46}, {'date': '2023-05-20', 'open': 110.79, 'high': 120.86, 'low': 100.72, 'close': 115.83, 'volume': 10071.94}, {'date': '2023-05-21', 'open': 110.79, 'high': 120.86, 'low': 100.71, 'close': 115.82, 'volume': 10071.43}, {'date': '2023-05-22', 'open': 110.78, 'high': 120.85, 'low': 100.71, 'close': 115.82, 'volume': 10070.92}, {'date': '2023-05-23', 'open': 110.77, 'high': 120.85, 'low': 100.7, 'close': 115.81, 'volume': 10070.42}, {'date': '2023-05-24', 'open': 110.77, 'high': 120.84, 'low': 100.7, 'close': 115.8, 'volume': 10069.93}, {'date': '2023-05-25', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.8, 'volume': 10069.44}, {'date': '2023-05-26', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.79, 'volume': 10068.97}, {'date': '2023-05-27', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.79, 'volume': 10068.49}, {'date': '2023-05-28', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.78, 'volume': 10068.03}, {'date': '2023-05-29', 'open': 110.74, 'high': 120.81, 'low': 100.68, 'close': 115.78, 'volume': 10067.57}, {'date': '2023-05-30', 'open': 110.74, 'high': 120.81, 'low': 100.67, 'close': 115.77, 'volume': 10067.11}, {'date': '2023-05-31', 'open': 110.73, 'high': 120.8, 'low': 100.67, 'close': 115.77, 'volume': 10066.67}, {'date': '2023-06-01', 'open': 110.73, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10066.23}, {'date': '2023-06-02', 'open': 110.72, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10065.79}, {'date': '2023-06-03', 'open': 110.72, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10065.36}, {'date': '2023-06-04', 'open': 110.71, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10064.94}, {'date': '2023-06-05', 'open': 110.71, 'high': 120.77, 'low': 100.65, 'close': 115.74, 'volume': 10064.52}, {'date': '2023-06-06', 'open': 110.71, 'high': 120.77, 'low': 100.64, 'close': 115.74, 'volume': 10064.1}, {'date': '2023-06-07', 'open': 110.7, 'high': 120.76, 'low': 100.64, 'close': 115.73, 'volume': 10063.69}, {'date': '2023-06-08', 'open': 110.7, 'high': 120.76, 'low': 100.63, 'close': 115.73, 'volume': 10063.29}, {'date': '2023-06-09', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.89}, {'date': '2023-06-10', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.5}, {'date': '2023-06-11', 'open': 110.68, 'high': 120.75, 'low': 100.62, 'close': 115.71, 'volume': 10062.11}, {'date': '2023-06-12', 'open': 110.68, 'high': 120.74, 'low': 100.62, 'close': 115.71, 'volume': 10061.73}, {'date': '2023-06-13', 'open': 110.67, 'high': 120.74, 'low': 100.61, 'close': 115.71, 'volume': 10061.35}, {'date': '2023-06-14', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.98}, {'date': '2023-06-15', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.61}, {'date': '2023-06-16', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10060.24}, {'date': '2023-06-17', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10059.88}, {'date': '2023-06-18', 'open': 110.65, 'high': 120.71, 'low': 100.6, 'close': 115.68, 'volume': 10059.52}, {'date': '2023-06-19', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10059.17}, {'date': '2023-06-20', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10058.82}, {'date': '2023-06-21', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.48}, {'date': '2023-06-22', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.14}, {'date': '2023-06-23', 'open': 110.64, 'high': 120.69, 'low': 100.58, 'close': 115.66, 'volume': 10057.8}, {'date': '2023-06-24', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.47}, {'date': '2023-06-25', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.14}, {'date': '2023-06-26', 'open': 110.62, 'high': 120.68, 'low': 100.57, 'close': 115.65, 'volume': 10056.82}, {'date': '2023-06-27', 'open': 110.62, 'high': 120.68, 'low': 100.56, 'close': 115.65, 'volume': 10056.5}, {'date': '2023-06-28', 'open': 110.62, 'high': 120.67, 'low': 100.56, 'close': 115.65, 'volume': 10056.18}, {'date': '2023-06-29', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.87}, {'date': '2023-06-30', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.56}, {'date': '2023-07-01', 'open': 110.61, 'high': 120.66, 'low': 100.55, 'close': 115.64, 'volume': 10055.25}, {'date': '2023-07-02', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.95}, {'date': '2023-07-03', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.64}, {'date': '2023-07-04', 'open': 110.6, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.35}, {'date': '2023-07-05', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.05}, {'date': '2023-07-06', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10053.76}, {'date': '2023-07-07', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.48}, {'date': '2023-07-08', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.19}, {'date': '2023-07-09', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.91}, {'date': '2023-07-10', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.63}, {'date': '2023-07-11', 'open': 110.58, 'high': 120.63, 'low': 100.52, 'close': 115.6, 'volume': 10052.36}, {'date': '2023-07-12', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10052.08}, {'date': '2023-07-13', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10051.81}, {'date': '2023-07-14', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.59, 'volume': 10051.55}, {'date': '2023-07-15', 'open': 110.56, 'high': 120.62, 'low': 100.51, 'close': 115.59, 'volume': 10051.28}, {'date': '2023-07-16', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.59, 'volume': 10051.02}, {'date': '2023-07-17', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.76}, {'date': '2023-07-18', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.51}, {'date': '2023-07-19', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.58, 'volume': 10050.25}, {'date': '2023-07-20', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10050.0}, {'date': '2023-07-21', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10049.75}, {'date': '2023-07-22', 'open': 110.54, 'high': 120.59, 'low': 100.5, 'close': 115.57, 'volume': 10049.5}, {'date': '2023-07-23', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.57, 'volume': 10049.26}, {'date': '2023-07-24', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10049.02}, {'date': '2023-07-25', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10048.78}, {'date': '2023-07-26', 'open': 110.53, 'high': 120.58, 'low': 100.49, 'close': 115.56, 'volume': 10048.54}, {'date': '2023-07-27', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.56, 'volume': 10048.31}, {'date': '2023-07-28', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.55, 'volume': 10048.08}, {'date': '2023-07-29', 'open': 110.53, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.85}, {'date': '2023-07-30', 'open': 110.52, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.62}, {'date': '2023-07-31', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.55, 'volume': 10047.39}, {'date': '2023-08-01', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.54, 'volume': 10047.17}, {'date': '2023-08-02', 'open': 110.52, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.95}, {'date': '2023-08-03', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.73}, {'date': '2023-08-04', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.53, 'volume': 10046.51}, {'date': '2023-08-05', 'open': 110.51, 'high': 120.56, 'low': 100.46, 'close': 115.53, 'volume': 10046.3}, {'date': '2023-08-06', 'open': 110.51, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10046.08}, {'date': '2023-08-07', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.87}, {'date': '2023-08-08', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.66}, {'date': '2023-08-09', 'open': 110.5, 'high': 120.55, 'low': 100.45, 'close': 115.52, 'volume': 10045.45}, {'date': '2023-08-10', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.25}, {'date': '2023-08-11', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.05}, {'date': '2023-08-12', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10044.84}, {'date': '2023-08-13', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.51, 'volume': 10044.64}, {'date': '2023-08-14', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.44}, {'date': '2023-08-15', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.25}, {'date': '2023-08-16', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.05}, {'date': '2023-08-17', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.5, 'volume': 10043.86}, {'date': '2023-08-18', 'open': 110.48, 'high': 120.52, 'low': 100.44, 'close': 115.5, 'volume': 10043.67}, {'date': '2023-08-19', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.48}, {'date': '2023-08-20', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.29}, {'date': '2023-08-21', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.1}, {'date': '2023-08-22', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.49, 'volume': 10042.92}, {'date': '2023-08-23', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.74}, {'date': '2023-08-24', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.55}, {'date': '2023-08-25', 'open': 110.47, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.37}, {'date': '2023-08-26', 'open': 110.46, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.19}, {'date': '2023-08-27', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10042.02}, {'date': '2023-08-28', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.84}, {'date': '2023-08-29', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.67}, {'date': '2023-08-30', 'open': 110.46, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.49}, {'date': '2023-08-31', 'open': 110.45, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.32}, {'date': '2023-09-01', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10041.15}, {'date': '2023-09-02', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.98}, {'date': '2023-09-03', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.82}, {'date': '2023-09-04', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.65}, {'date': '2023-09-05', 'open': 110.45, 'high': 120.49, 'low': 100.4, 'close': 115.47, 'volume': 10040.49}, {'date': '2023-09-06', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.32}, {'date': '2023-09-07', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.16}, {'date': '2023-09-08', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.0}, {'date': '2023-09-09', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.84}, {'date': '2023-09-10', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.68}, {'date': '2023-09-11', 'open': 110.43, 'high': 120.47, 'low': 100.4, 'close': 115.45, 'volume': 10039.53}, {'date': '2023-09-12', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.37}, {'date': '2023-09-13', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.22}, {'date': '2023-09-14', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.06}, {'date': '2023-09-15', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.91}, {'date': '2023-09-16', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.76}, {'date': '2023-09-17', 'open': 110.42, 'high': 120.46, 'low': 100.39, 'close': 115.44, 'volume': 10038.61}, {'date': '2023-09-18', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.46}, {'date': '2023-09-19', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.31}, {'date': '2023-09-20', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.17}, {'date': '2023-09-21', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.02}, {'date': '2023-09-22', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.44, 'volume': 10037.88}, {'date': '2023-09-23', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.74}, {'date': '2023-09-24', 'open': 110.41, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.59}, {'date': '2023-09-25', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.45}, {'date': '2023-09-26', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.31}, {'date': '2023-09-27', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.17}, {'date': '2023-09-28', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.43, 'volume': 10037.04}, {'date': '2023-09-29', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.9}, {'date': '2023-09-30', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.76}, {'date': '2023-10-01', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.63}, {'date': '2023-10-02', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.5}, {'date': '2023-10-03', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.36}, {'date': '2023-10-04', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.23}, {'date': '2023-10-05', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.1}, {'date': '2023-10-06', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.97}, {'date': '2023-10-07', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.84}, {'date': '2023-10-08', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.71}, {'date': '2023-10-09', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.59}, {'date': '2023-10-10', 'open': 110.39, 'high': 120.43, 'low': 100.35, 'close': 115.41, 'volume': 10035.46}, {'date': '2023-10-11', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.41, 'volume': 10035.34}, {'date': '2023-10-12', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.21}, {'date': '2023-10-13', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.09}, {'date': '2023-10-14', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.97}, {'date': '2023-10-15', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.84}, {'date': '2023-10-16', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.72}, {'date': '2023-10-17', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.6}, {'date': '2023-10-18', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.48}, {'date': '2023-10-19', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.36}, {'date': '2023-10-20', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.25}, {'date': '2023-10-21', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.13}, {'date': '2023-10-22', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.01}, {'date': '2023-10-23', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.9}, {'date': '2023-10-24', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.78}, {'date': '2023-10-25', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.67}, {'date': '2023-10-26', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.56}, {'date': '2023-10-27', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.44}, {'date': '2023-10-28', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.33}, {'date': '2023-10-29', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.22}, {'date': '2023-10-30', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.11}, {'date': '2023-10-31', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.0}, {'date': '2023-11-01', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.89}, {'date': '2023-11-02', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.79}, {'date': '2023-11-03', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.68}, {'date': '2023-11-04', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.37, 'volume': 10032.57}, {'date': '2023-11-05', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.47}, {'date': '2023-11-06', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.36}, {'date': '2023-11-07', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.26}, {'date': '2023-11-08', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.15}, {'date': '2023-11-09', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10032.05}, {'date': '2023-11-10', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.95}, {'date': '2023-11-11', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.85}, {'date': '2023-11-12', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.75}, {'date': '2023-11-13', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.65}, {'date': '2023-11-14', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.55}, {'date': '2023-11-15', 'open': 110.35, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.45}, {'date': '2023-11-16', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.35}, {'date': '2023-11-17', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.25}, {'date': '2023-11-18', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.15}, {'date': '2023-11-19', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.06}, {'date': '2023-11-20', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10030.96}, {'date': '2023-11-21', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.86}, {'date': '2023-11-22', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.77}, {'date': '2023-11-23', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.67}, {'date': '2023-11-24', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.58}, {'date': '2023-11-25', 'open': 110.34, 'high': 120.37, 'low': 100.3, 'close': 115.35, 'volume': 10030.49}, {'date': '2023-11-26', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.4}, {'date': '2023-11-27', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.3}, {'date': '2023-11-28', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.21}, {'date': '2023-11-29', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.12}, {'date': '2023-11-30', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.03}, {'date': '2023-12-01', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.94}, {'date': '2023-12-02', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.85}, {'date': '2023-12-03', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.76}, {'date': '2023-12-04', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.67}, {'date': '2023-12-05', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.59}, {'date': '2023-12-06', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.5}, {'date': '2023-12-07', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.41}, {'date': '2023-12-08', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.33}, {'date': '2023-12-09', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.24}, {'date': '2023-12-10', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.15}, {'date': '2023-12-11', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10029.07}, {'date': '2023-12-12', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.99}, {'date': '2023-12-13', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.9}, {'date': '2023-12-14', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.82}, {'date': '2023-12-15', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.74}, {'date': '2023-12-16', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.65}, {'date': '2023-12-17', 'open': 110.31, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.57}, {'date': '2023-12-18', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.49}, {'date': '2023-12-19', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.41}, {'date': '2023-12-20', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.33}, {'date': '2023-12-21', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.25}, {'date': '2023-12-22', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.17}, {'date': '2023-12-23', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.09}, {'date': '2023-12-24', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.01}, {'date': '2023-12-25', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10027.93}, {'date': '2023-12-26', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.86}, {'date': '2023-12-27', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.78}, {'date': '2023-12-28', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.7}, {'date': '2023-12-29', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.62}, {'date': '2023-12-30', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.55}, {'date': '2023-12-31', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.47}, {'date': '2024-01-01', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.4}, {'date': '2024-01-02', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.32}, {'date': '2024-01-03', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.25}, {'date': '2024-01-04', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.17}, {'date': '2024-01-05', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.1}, {'date': '2024-01-06', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10027.03}, {'date': '2024-01-07', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.95}, {'date': '2024-01-08', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.88}, {'date': '2024-01-09', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.81}, {'date': '2024-01-10', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.74}, {'date': '2024-01-11', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.67}, {'date': '2024-01-12', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.6}, {'date': '2024-01-13', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.53}, {'date': '2024-01-14', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.46}, {'date': '2024-01-15', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.39}, {'date': '2024-01-16', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.32}, {'date': '2024-01-17', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.25}, {'date': '2024-01-18', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.18}, {'date': '2024-01-19', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.11}, {'date': '2024-01-20', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.04}, {'date': '2024-01-21', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.97}, {'date': '2024-01-22', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.91}, {'date': '2024-01-23', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.84}, {'date': '2024-01-24', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.77}, {'date': '2024-01-25', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.71}, {'date': '2024-01-26', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.64}, {'date': '2024-01-27', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.58}, {'date': '2024-01-28', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.51}, {'date': '2024-01-29', 'open': 110.28, 'high': 120.31, 'low': 100.25, 'close': 115.29, 'volume': 10025.45}, {'date': '2024-01-30', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.38}, {'date': '2024-01-31', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.32}, {'date': '2024-02-01', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.25}, {'date': '2024-02-02', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.19}, {'date': '2024-02-03', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.13}, {'date': '2024-02-04', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.06}, {'date': '2024-02-05', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.0}, {'date': '2024-02-06', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.94}, {'date': '2024-02-07', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.88}, {'date': '2024-02-08', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.81}, {'date': '2024-02-09', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.75}, {'date': '2024-02-10', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.69}, {'date': '2024-02-11', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.63}, {'date': '2024-02-12', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.57}, {'date': '2024-02-13', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.51}, {'date': '2024-02-14', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.45}, {'date': '2024-02-15', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.39}, {'date': '2024-02-16', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.33}, {'date': '2024-02-17', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.27}, {'date': '2024-02-18', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.21}, {'date': '2024-02-19', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.15}, {'date': '2024-02-20', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.1}, {'date': '2024-02-21', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.04}, {'date': '2024-02-22', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.98}, {'date': '2024-02-23', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.92}, {'date': '2024-02-24', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.87}, {'date': '2024-02-25', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.81}, {'date': '2024-02-26', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.75}, {'date': '2024-02-27', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.7}, {'date': '2024-02-28', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.64}, {'date': '2024-02-29', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.58}, {'date': '2024-03-01', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.53}, {'date': '2024-03-02', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.47}, {'date': '2024-03-03', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.42}, {'date': '2024-03-04', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.36}, {'date': '2024-03-05', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.31}, {'date': '2024-03-06', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.26}, {'date': '2024-03-07', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.2}, {'date': '2024-03-08', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.15}, {'date': '2024-03-09', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.09}, {'date': '2024-03-10', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10023.04}, {'date': '2024-03-11', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.99}, {'date': '2024-03-12', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.94}, {'date': '2024-03-13', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.88}, {'date': '2024-03-14', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.83}, {'date': '2024-03-15', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.78}, {'date': '2024-03-16', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.73}, {'date': '2024-03-17', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.68}, {'date': '2024-03-18', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.62}, {'date': '2024-03-19', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.57}, {'date': '2024-03-20', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.52}, {'date': '2024-03-21', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.47}, {'date': '2024-03-22', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.42}, {'date': '2024-03-23', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.37}, {'date': '2024-03-24', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.32}, {'date': '2024-03-25', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.27}, {'date': '2024-03-26', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.22}, {'date': '2024-03-27', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.17}, {'date': '2024-03-28', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.12}, {'date': '2024-03-29', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.08}, {'date': '2024-03-30', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.03}, {'date': '2024-03-31', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.98}, {'date': '2024-04-01', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.93}, {'date': '2024-04-02', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.88}, {'date': '2024-04-03', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.83}, {'date': '2024-04-04', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.79}, {'date': '2024-04-05', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.74}, {'date': '2024-04-06', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.69}, {'date': '2024-04-07', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.65}, {'date': '2024-04-08', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.6}, {'date': '2024-04-09', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.55}, {'date': '2024-04-10', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.51}, {'date': '2024-04-11', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.46}, {'date': '2024-04-12', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.41}, {'date': '2024-04-13', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.37}, {'date': '2024-04-14', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.32}, {'date': '2024-04-15', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.24, 'volume': 10021.28}, {'date': '2024-04-16', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.23}, {'date': '2024-04-17', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.19}, {'date': '2024-04-18', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.14}, {'date': '2024-04-19', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.1}, {'date': '2024-04-20', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.05}, {'date': '2024-04-21', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.01}, {'date': '2024-04-22', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.96}, {'date': '2024-04-23', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.92}, {'date': '2024-04-24', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.88}, {'date': '2024-04-25', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.83}, {'date': '2024-04-26', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.79}, {'date': '2024-04-27', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.75}, {'date': '2024-04-28', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.7}, {'date': '2024-04-29', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.66}, {'date': '2024-04-30', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.62}, {'date': '2024-05-01', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.58}, {'date': '2024-05-02', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.53}, {'date': '2024-05-03', 'open': 110.23, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.49}, {'date': '2024-05-04', 'open': 110.22, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.45}, {'date': '2024-05-05', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.41}, {'date': '2024-05-06', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.37}, {'date': '2024-05-07', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.33}, {'date': '2024-05-08', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.28}, {'date': '2024-05-09', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.24}, {'date': '2024-05-10', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.2}, {'date': '2024-05-11', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.16}, {'date': '2024-05-12', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.12}, {'date': '2024-05-13', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.08}, {'date': '2024-05-14', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.04}, {'date': '2024-05-15', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.0}, {'date': '2024-05-16', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.96}, {'date': '2024-05-17', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.92}, {'date': '2024-05-18', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.88}, {'date': '2024-05-19', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.84}, {'date': '2024-05-20', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.8}, {'date': '2024-05-21', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.76}, {'date': '2024-05-22', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.72}, {'date': '2024-05-23', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.69}, {'date': '2024-05-24', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.65}, {'date': '2024-05-25', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.61}, {'date': '2024-05-26', 'open': 110.22, 'high': 120.23, 'low': 100.2, 'close': 115.23, 'volume': 10019.57}, {'date': '2024-05-27', 'open': 110.21, 'high': 120.23, 'low': 100.2, 'close': 115.22, 'volume': 10019.53}, {'date': '2024-05-28', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.49}, {'date': '2024-05-29', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.46}, {'date': '2024-05-30', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.42}, {'date': '2024-05-31', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.38}, {'date': '2024-06-01', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.34}, {'date': '2024-06-02', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.31}, {'date': '2024-06-03', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.27}, {'date': '2024-06-04', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.23}, {'date': '2024-06-05', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.19}, {'date': '2024-06-06', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.16}, {'date': '2024-06-07', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.12}, {'date': '2024-06-08', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.08}, {'date': '2024-06-09', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.05}, {'date': '2024-06-10', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.01}, {'date': '2024-06-11', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.98}, {'date': '2024-06-12', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.94}, {'date': '2024-06-13', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.9}, {'date': '2024-06-14', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.87}, {'date': '2024-06-15', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.83}, {'date': '2024-06-16', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.8}, {'date': '2024-06-17', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.76}, {'date': '2024-06-18', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.22, 'volume': 10018.73}, {'date': '2024-06-19', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.69}, {'date': '2024-06-20', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.66}, {'date': '2024-06-21', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.62}, {'date': '2024-06-22', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.59}, {'date': '2024-06-23', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.55}, {'date': '2024-06-24', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.52}, {'date': '2024-06-25', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.48}, {'date': '2024-06-26', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.45}, {'date': '2024-06-27', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.42}, {'date': '2024-06-28', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.38}, {'date': '2024-06-29', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.35}, {'date': '2024-06-30', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.32}, {'date': '2024-07-01', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.28}, {'date': '2024-07-02', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.25}, {'date': '2024-07-03', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.21}, {'date': '2024-07-04', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.18}, {'date': '2024-07-05', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.15}, {'date': '2024-07-06', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.12}, {'date': '2024-07-07', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.08}, {'date': '2024-07-08', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.05}, {'date': '2024-07-09', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.02}, {'date': '2024-07-10', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.99}, {'date': '2024-07-11', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.95}, {'date': '2024-07-12', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.92}, {'date': '2024-07-13', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.89}, {'date': '2024-07-14', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.86}, {'date': '2024-07-15', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.83}, {'date': '2024-07-16', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.79}, {'date': '2024-07-17', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.76}, {'date': '2024-07-18', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.73}, {'date': '2024-07-19', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.7}, {'date': '2024-07-20', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.67}, {'date': '2024-07-21', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.64}, {'date': '2024-07-22', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.61}, {'date': '2024-07-23', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.57}, {'date': '2024-07-24', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.54}, {'date': '2024-07-25', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.51}, {'date': '2024-07-26', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.48}, {'date': '2024-07-27', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.45}, {'date': '2024-07-28', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.42}, {'date': '2024-07-29', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.39}, {'date': '2024-07-30', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.36}, {'date': '2024-07-31', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.33}, {'date': '2024-08-01', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.3}, {'date': '2024-08-02', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.27}, {'date': '2024-08-03', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.24}, {'date': '2024-08-04', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.21}, {'date': '2024-08-05', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.18}, {'date': '2024-08-06', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.15}, {'date': '2024-08-07', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.12}, {'date': '2024-08-08', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.09}, {'date': '2024-08-09', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.06}, {'date': '2024-08-10', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.04}, {'date': '2024-08-11', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.01}, {'date': '2024-08-12', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10016.98}, {'date': '2024-08-13', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.95}, {'date': '2024-08-14', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.92}, {'date': '2024-08-15', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.89}, {'date': '2024-08-16', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.86}, {'date': '2024-08-17', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.84}, {'date': '2024-08-18', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.81}, {'date': '2024-08-19', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.78}, {'date': '2024-08-20', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.75}, {'date': '2024-08-21', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.72}, {'date': '2024-08-22', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.69}, {'date': '2024-08-23', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.67}, {'date': '2024-08-24', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.64}, {'date': '2024-08-25', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.61}, {'date': '2024-08-26', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.58}, {'date': '2024-08-27', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.56}, {'date': '2024-08-28', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.53}, {'date': '2024-08-29', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.5}, {'date': '2024-08-30', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.47}, {'date': '2024-08-31', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.45}, {'date': '2024-09-01', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.42}, {'date': '2024-09-02', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.39}, {'date': '2024-09-03', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.37}, {'date': '2024-09-04', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.34}, {'date': '2024-09-05', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.31}, {'date': '2024-09-06', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.29}, {'date': '2024-09-07', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.26}, {'date': '2024-09-08', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.23}, {'date': '2024-09-09', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.21}, {'date': '2024-09-10', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.18}, {'date': '2024-09-11', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.16}, {'date': '2024-09-12', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.13}, {'date': '2024-09-13', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.1}, {'date': '2024-09-14', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.08}, {'date': '2024-09-15', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.05}, {'date': '2024-09-16', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.03}, {'date': '2024-09-17', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.0}, {'date': '2024-09-18', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.97}, {'date': '2024-09-19', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.95}, {'date': '2024-09-20', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.92}, {'date': '2024-09-21', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.9}, {'date': '2024-09-22', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.87}, {'date': '2024-09-23', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.85}, {'date': '2024-09-24', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.82}, {'date': '2024-09-25', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.8}, {'date': '2024-09-26', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.77}, {'date': '2024-09-27', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.75}, {'date': '2024-09-28', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.72}, {'date': '2024-09-29', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.7}, {'date': '2024-09-30', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.67}, {'date': '2024-10-01', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.65}, {'date': '2024-10-02', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.62}, {'date': '2024-10-03', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.6}, {'date': '2024-10-04', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.58}, {'date': '2024-10-05', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.55}, {'date': '2024-10-06', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.53}, {'date': '2024-10-07', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.5}, {'date': '2024-10-08', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.48}, {'date': '2024-10-09', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.46}, {'date': '2024-10-10', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.43}, {'date': '2024-10-11', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.41}, {'date': '2024-10-12', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.38}, {'date': '2024-10-13', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.36}, {'date': '2024-10-14', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.34}, {'date': '2024-10-15', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.31}, {'date': '2024-10-16', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.29}, {'date': '2024-10-17', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.27}, {'date': '2024-10-18', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.24}, {'date': '2024-10-19', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.22}, {'date': '2024-10-20', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.2}, {'date': '2024-10-21', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.17}, {'date': '2024-10-22', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.15}, {'date': '2024-10-23', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.13}, {'date': '2024-10-24', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.11}, {'date': '2024-10-25', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.08}, {'date': '2024-10-26', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.06}, {'date': '2024-10-27', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.04}, {'date': '2024-10-28', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.02}, {'date': '2024-10-29', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.99}, {'date': '2024-10-30', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.97}, {'date': '2024-10-31', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.95}, {'date': '2024-11-01', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.93}, {'date': '2024-11-02', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.9}, {'date': '2024-11-03', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.88}, {'date': '2024-11-04', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.86}, {'date': '2024-11-05', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.84}, {'date': '2024-11-06', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.81}, {'date': '2024-11-07', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.79}, {'date': '2024-11-08', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.77}, {'date': '2024-11-09', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.75}, {'date': '2024-11-10', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.73}, {'date': '2024-11-11', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.71}, {'date': '2024-11-12', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.68}, {'date': '2024-11-13', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.66}, {'date': '2024-11-14', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.64}, {'date': '2024-11-15', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.62}, {'date': '2024-11-16', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.6}, {'date': '2024-11-17', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.58}, {'date': '2024-11-18', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.56}, {'date': '2024-11-19', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.53}, {'date': '2024-11-20', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.51}, {'date': '2024-11-21', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.49}, {'date': '2024-11-22', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.47}, {'date': '2024-11-23', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.45}, {'date': '2024-11-24', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.43}, {'date': '2024-11-25', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.41}, {'date': '2024-11-26', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.39}, {'date': '2024-11-27', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.37}, {'date': '2024-11-28', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.35}, {'date': '2024-11-29', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.33}, {'date': '2024-11-30', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.31}, {'date': '2024-12-01', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.29}, {'date': '2024-12-02', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.27}, {'date': '2024-12-03', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.25}, {'date': '2024-12-04', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.22}, {'date': '2024-12-05', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.2}, {'date': '2024-12-06', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.18}, {'date': '2024-12-07', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.16}, {'date': '2024-12-08', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.14}, {'date': '2024-12-09', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.12}, {'date': '2024-12-10', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.1}, {'date': '2024-12-11', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.08}, {'date': '2024-12-12', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.06}, {'date': '2024-12-13', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.04}, {'date': '2024-12-14', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.03}, {'date': '2024-12-15', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.01}, {'date': '2024-12-16', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.99}, {'date': '2024-12-17', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.97}, {'date': '2024-12-18', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.95}, {'date': '2024-12-19', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.93}, {'date': '2024-12-20', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.91}, {'date': '2024-12-21', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.89}, {'date': '2024-12-22', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.87}, {'date': '2024-12-23', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.85}, {'date': '2024-12-24', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.83}, {'date': '2024-12-25', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.81}, {'date': '2024-12-26', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.79}, {'date': '2024-12-27', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.77}, {'date': '2024-12-28', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.76}, {'date': '2024-12-29', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.74}, {'date': '2024-12-30', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.72}, {'date': '2024-12-31', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.7}, {'date': '2025-01-01', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.68}, {'date': '2025-01-02', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.66}, {'date': '2025-01-03', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.64}, {'date': '2025-01-04', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.62}, {'date': '2025-01-05', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.61}, {'date': '2025-01-06', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.59}, {'date': '2025-01-07', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.57}, {'date': '2025-01-08', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.55}, {'date': '2025-01-09', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.53}, {'date': '2025-01-10', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.51}, {'date': '2025-01-11', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.16, 'volume': 10013.5}, {'date': '2025-01-12', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.48}, {'date': '2025-01-13', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.46}, {'date': '2025-01-14', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.44}, {'date': '2025-01-15', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.42}, {'date': '2025-01-16', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.4}, {'date': '2025-01-17', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.39}, {'date': '2025-01-18', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.37}, {'date': '2025-01-19', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.35}, {'date': '2025-01-20', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.33}, {'date': '2025-01-21', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.32}, {'date': '2025-01-22', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.3}, {'date': '2025-01-23', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.28}, {'date': '2025-01-24', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.26}, {'date': '2025-01-25', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.25}, {'date': '2025-01-26', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.23}, {'date': '2025-01-27', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.21}, {'date': '2025-01-28', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.19}, {'date': '2025-01-29', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.18}, {'date': '2025-01-30', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.16}, {'date': '2025-01-31', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.14}, {'date': '2025-02-01', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.12}, {'date': '2025-02-02', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.11}, {'date': '2025-02-03', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.09}, {'date': '2025-02-04', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.07}, {'date': '2025-02-05', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.05}, {'date': '2025-02-06', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.04}, {'date': '2025-02-07', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.02}, {'date': '2025-02-08', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.0}, {'date': '2025-02-09', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.99}, {'date': '2025-02-10', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.97}, {'date': '2025-02-11', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.95}, {'date': '2025-02-12', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.94}, {'date': '2025-02-13', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.92}, {'date': '2025-02-14', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.9}, {'date': '2025-02-15', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.89}, {'date': '2025-02-16', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.87}, {'date': '2025-02-17', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.85}, {'date': '2025-02-18', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.84}, {'date': '2025-02-19', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.82}, {'date': '2025-02-20', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.8}, {'date': '2025-02-21', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.79}, {'date': '2025-02-22', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.77}, {'date': '2025-02-23', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.76}, {'date': '2025-02-24', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.74}, {'date': '2025-02-25', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.72}, {'date': '2025-02-26', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.71}, {'date': '2025-02-27', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.69}, {'date': '2025-02-28', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.67}, {'date': '2025-03-01', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.66}, {'date': '2025-03-02', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.64}, {'date': '2025-03-03', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.63}, {'date': '2025-03-04', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.61}, {'date': '2025-03-05', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.59}, {'date': '2025-03-06', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.58}, {'date': '2025-03-07', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.56}, {'date': '2025-03-08', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.55}, {'date': '2025-03-09', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.53}, {'date': '2025-03-10', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.52}, {'date': '2025-03-11', 'open': 110.14, 'high': 120.15, 'low': 100.12, 'close': 115.14, 'volume': 10012.5}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Target time series (index column plus the target column).", + "default": null, + "optional": false + }, + { + "name": "factors_data", + "type": "list[Data]", + "description": "Factor matrix (index column plus one column per factor).", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column in `data` to regress on the factor matrix.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column shared by `data` and `factors_data`.", + "default": "date", + "optional": true + }, + { + "name": "risk_free_column", + "type": "str", + "description": "Optional name of the risk-free rate column in `factors_data`. When provided, the target is regressed in excess form and the column is dropped from the regressors.", + "default": null, + "optional": true + }, + { + "name": "window", + "type": "int", + "description": "Number of observations in each rolling regression window.", + "default": 252, + "optional": true + }, + { + "name": "step", + "type": "int", + "description": "Stride between successive windows in observations. A value of 1 refits at every observation; larger values reduce cost on long histories.", + "default": 21, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RollingFactorsData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "End date of the rolling window.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "factor", + "type": "str", + "description": "Factor name (or 'const' for the regression intercept).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "OLS coefficient estimate for the factor at this window end.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "t_statistic", + "type": "float", + "description": "t-statistic for the coefficient at this window end.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/stats/skew": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute the skewness of a target column.\n\nSkewness measures the degree of asymmetry of a distribution around its mean.\nPositive skewness indicates a longer right tail, while negative skewness indicates\na longer left tail.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.stats.skew(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "StatsSkewData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "skew", + "type": "float", + "description": "Skewness of the distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/stats/variance": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute the variance of a target column.\n\nVariance measures the dispersion of a set of data points around their mean. It is a\nkey metric for assessing the volatility and stability of a time series.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.stats.variance(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "StatsVarianceData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "variance", + "type": "float", + "description": "Variance of the distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/stats/stdev": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute the standard deviation of a target column.\n\nStandard deviation measures the amount of variation or dispersion of a set of\nvalues and is the square root of the variance. It is widely used to assess risk\nand volatility.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.stats.stdev(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "StatsStdevData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "stdev", + "type": "float", + "description": "Standard deviation of the distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/stats/kurtosis": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute the kurtosis of a target column.\n\nKurtosis measures the tailedness of a distribution. High kurtosis indicates heavy\ntails and a higher risk of extreme outcomes, while low kurtosis indicates lighter\ntails.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.stats.kurtosis(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "StatsKurtosisData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "kurtosis", + "type": "float", + "description": "Kurtosis of the distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/stats/mean": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute the arithmetic mean of a target column.\n\nThe mean is the average of a set of values and is widely used to summarize the\ncentral tendency of a time series.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.stats.mean(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "StatsMeanData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "mean", + "type": "float", + "description": "Arithmetic mean of the distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/stats/quantile": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute the quantile of a target column at a specified percentage.\n\nQuantiles are points dividing the range of a distribution into intervals with equal\nprobabilities. The median (0.5 quantile) is returned alongside the requested\nquantile.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.stats.quantile(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "quantile_pct", + "type": "float", + "description": "Quantile to compute, between 0 and 1.", + "default": 0.5, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "StatsQuantileData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "median", + "type": "float", + "description": "Median (0.5 quantile) of the distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "quantile", + "type": "float", + "description": "Value at the requested quantile.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/performance/omega_ratio": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Omega ratio across a range of return thresholds.\n\nThe Omega ratio measures the probability-weighted gains above a threshold relative\nto the losses below it. The ratio is evaluated at 50 thresholds spanning the\nrequested range, giving a profile of risk and reward rather than a single number.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.performance.omega_ratio(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "threshold_start", + "type": "float", + "description": "Lower bound of the return-threshold range.", + "default": 0.0, + "optional": true + }, + { + "name": "threshold_end", + "type": "float", + "description": "Upper bound of the return-threshold range.", + "default": 1.5, + "optional": true + }, + { + "name": "bins", + "type": "int", + "description": "Number of evenly spaced thresholds to evaluate.", + "default": 50, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[OmegaRatioData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "threshold", + "type": "float", + "description": "Return threshold the ratio is evaluated at.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "omega", + "type": "float", + "description": "Omega ratio at the threshold.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/performance/sharpe_ratio": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the rolling Sharpe ratio of a return series.\n\nThe Sharpe ratio measures the excess return earned per unit of total volatility.\nIt is computed over a rolling window so the result tracks how risk-adjusted\nperformance evolves over time.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.performance.sharpe_ratio(target='close', window=20, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "rfr", + "type": "float", + "description": "Risk-free rate, as a decimal fraction.", + "default": 0.0, + "optional": true + }, + { + "name": "window", + "type": "int", + "description": "Number of observations in each rolling window.", + "default": 252, + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column.", + "default": "date", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[SharpeRatioData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "sharpe_ratio", + "type": "float", + "description": "Rolling Sharpe ratio.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/performance/sortino_ratio": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the rolling Sortino ratio of a return series.\n\nThe Sortino ratio refines the Sharpe ratio by penalizing only downside volatility\nmeasured against a minimum acceptable return. When adjusted, the ratio is scaled by\n1/sqrt(2) so it can be compared directly with the Sharpe ratio.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.performance.sortino_ratio(target='close', window=20, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input dataset.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column to analyze.", + "default": null, + "optional": false + }, + { + "name": "target_return", + "type": "float", + "description": "Minimum acceptable return, as a decimal fraction.", + "default": 0.0, + "optional": true + }, + { + "name": "window", + "type": "int", + "description": "Number of observations in each rolling window.", + "default": 252, + "optional": true + }, + { + "name": "adjusted", + "type": "bool", + "description": "When true, scale the ratio by 1/sqrt(2) for comparability with the Sharpe ratio.", + "default": false, + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column.", + "default": "date", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[SortinoRatioData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "sortino_ratio", + "type": "float", + "description": "Rolling Sortino ratio.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/factors": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Regress a target return series on a factor matrix across named periods.\n\nAligns `data` and `factors_data` on their shared index, optionally subtracts a\nrisk-free column from the target, then runs an OLS regression of the target on\nthe factor columns (plus an intercept) for each named look-back period. Returns\none row per (`period`, `factor`) with the OLS coefficient, p-value, 95%\nconfidence interval, and the period's R-squared.\n\nDesigned to be paired with any factor provider; for Fama-French data, pipe the\n`openbb-famafrench` provider's output into `factors_data`.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.factors(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}, {'date': '2023-03-03', 'open': 111.8, 'high': 121.97, 'low': 101.64, 'close': 116.89, 'volume': 10163.93}, {'date': '2023-03-04', 'open': 111.77, 'high': 121.94, 'low': 101.61, 'close': 116.85, 'volume': 10161.29}, {'date': '2023-03-05', 'open': 111.75, 'high': 121.9, 'low': 101.59, 'close': 116.83, 'volume': 10158.73}, {'date': '2023-03-06', 'open': 111.72, 'high': 121.88, 'low': 101.56, 'close': 116.8, 'volume': 10156.25}, {'date': '2023-03-07', 'open': 111.69, 'high': 121.85, 'low': 101.54, 'close': 116.77, 'volume': 10153.85}, {'date': '2023-03-08', 'open': 111.67, 'high': 121.82, 'low': 101.52, 'close': 116.74, 'volume': 10151.52}, {'date': '2023-03-09', 'open': 111.64, 'high': 121.79, 'low': 101.49, 'close': 116.72, 'volume': 10149.25}, {'date': '2023-03-10', 'open': 111.62, 'high': 121.76, 'low': 101.47, 'close': 116.69, 'volume': 10147.06}, {'date': '2023-03-11', 'open': 111.59, 'high': 121.74, 'low': 101.45, 'close': 116.67, 'volume': 10144.93}, {'date': '2023-03-12', 'open': 111.57, 'high': 121.71, 'low': 101.43, 'close': 116.64, 'volume': 10142.86}, {'date': '2023-03-13', 'open': 111.55, 'high': 121.69, 'low': 101.41, 'close': 116.62, 'volume': 10140.85}, {'date': '2023-03-14', 'open': 111.53, 'high': 121.67, 'low': 101.39, 'close': 116.6, 'volume': 10138.89}, {'date': '2023-03-15', 'open': 111.51, 'high': 121.64, 'low': 101.37, 'close': 116.58, 'volume': 10136.99}, {'date': '2023-03-16', 'open': 111.49, 'high': 121.62, 'low': 101.35, 'close': 116.55, 'volume': 10135.14}, {'date': '2023-03-17', 'open': 111.47, 'high': 121.6, 'low': 101.33, 'close': 116.53, 'volume': 10133.33}, {'date': '2023-03-18', 'open': 111.45, 'high': 121.58, 'low': 101.32, 'close': 116.51, 'volume': 10131.58}, {'date': '2023-03-19', 'open': 111.43, 'high': 121.56, 'low': 101.3, 'close': 116.49, 'volume': 10129.87}, {'date': '2023-03-20', 'open': 111.41, 'high': 121.54, 'low': 101.28, 'close': 116.47, 'volume': 10128.21}, {'date': '2023-03-21', 'open': 111.39, 'high': 121.52, 'low': 101.27, 'close': 116.46, 'volume': 10126.58}, {'date': '2023-03-22', 'open': 111.38, 'high': 121.5, 'low': 101.25, 'close': 116.44, 'volume': 10125.0}, {'date': '2023-03-23', 'open': 111.36, 'high': 121.48, 'low': 101.23, 'close': 116.42, 'volume': 10123.46}, {'date': '2023-03-24', 'open': 111.34, 'high': 121.46, 'low': 101.22, 'close': 116.4, 'volume': 10121.95}, {'date': '2023-03-25', 'open': 111.33, 'high': 121.45, 'low': 101.2, 'close': 116.39, 'volume': 10120.48}, {'date': '2023-03-26', 'open': 111.31, 'high': 121.43, 'low': 101.19, 'close': 116.37, 'volume': 10119.05}, {'date': '2023-03-27', 'open': 111.29, 'high': 121.41, 'low': 101.18, 'close': 116.35, 'volume': 10117.65}, {'date': '2023-03-28', 'open': 111.28, 'high': 121.4, 'low': 101.16, 'close': 116.34, 'volume': 10116.28}, {'date': '2023-03-29', 'open': 111.26, 'high': 121.38, 'low': 101.15, 'close': 116.32, 'volume': 10114.94}, {'date': '2023-03-30', 'open': 111.25, 'high': 121.36, 'low': 101.14, 'close': 116.31, 'volume': 10113.64}, {'date': '2023-03-31', 'open': 111.24, 'high': 121.35, 'low': 101.12, 'close': 116.29, 'volume': 10112.36}, {'date': '2023-04-01', 'open': 111.22, 'high': 121.33, 'low': 101.11, 'close': 116.28, 'volume': 10111.11}, {'date': '2023-04-02', 'open': 111.21, 'high': 121.32, 'low': 101.1, 'close': 116.26, 'volume': 10109.89}, {'date': '2023-04-03', 'open': 111.2, 'high': 121.3, 'low': 101.09, 'close': 116.25, 'volume': 10108.7}, {'date': '2023-04-04', 'open': 111.18, 'high': 121.29, 'low': 101.08, 'close': 116.24, 'volume': 10107.53}, {'date': '2023-04-05', 'open': 111.17, 'high': 121.28, 'low': 101.06, 'close': 116.22, 'volume': 10106.38}, {'date': '2023-04-06', 'open': 111.16, 'high': 121.26, 'low': 101.05, 'close': 116.21, 'volume': 10105.26}, {'date': '2023-04-07', 'open': 111.15, 'high': 121.25, 'low': 101.04, 'close': 116.2, 'volume': 10104.17}, {'date': '2023-04-08', 'open': 111.13, 'high': 121.24, 'low': 101.03, 'close': 116.19, 'volume': 10103.09}, {'date': '2023-04-09', 'open': 111.12, 'high': 121.22, 'low': 101.02, 'close': 116.17, 'volume': 10102.04}, {'date': '2023-04-10', 'open': 111.11, 'high': 121.21, 'low': 101.01, 'close': 116.16, 'volume': 10101.01}, {'date': '2023-04-11', 'open': 111.1, 'high': 121.2, 'low': 101.0, 'close': 116.15, 'volume': 10100.0}, {'date': '2023-04-12', 'open': 111.09, 'high': 121.19, 'low': 100.99, 'close': 116.14, 'volume': 10099.01}, {'date': '2023-04-13', 'open': 111.08, 'high': 121.18, 'low': 100.98, 'close': 116.13, 'volume': 10098.04}, {'date': '2023-04-14', 'open': 111.07, 'high': 121.17, 'low': 100.97, 'close': 116.12, 'volume': 10097.09}, {'date': '2023-04-15', 'open': 111.06, 'high': 121.15, 'low': 100.96, 'close': 116.11, 'volume': 10096.15}, {'date': '2023-04-16', 'open': 111.05, 'high': 121.14, 'low': 100.95, 'close': 116.1, 'volume': 10095.24}, {'date': '2023-04-17', 'open': 111.04, 'high': 121.13, 'low': 100.94, 'close': 116.08, 'volume': 10094.34}, {'date': '2023-04-18', 'open': 111.03, 'high': 121.12, 'low': 100.93, 'close': 116.07, 'volume': 10093.46}, {'date': '2023-04-19', 'open': 111.02, 'high': 121.11, 'low': 100.93, 'close': 116.06, 'volume': 10092.59}, {'date': '2023-04-20', 'open': 111.01, 'high': 121.1, 'low': 100.92, 'close': 116.06, 'volume': 10091.74}, {'date': '2023-04-21', 'open': 111.0, 'high': 121.09, 'low': 100.91, 'close': 116.05, 'volume': 10090.91}, {'date': '2023-04-22', 'open': 110.99, 'high': 121.08, 'low': 100.9, 'close': 116.04, 'volume': 10090.09}, {'date': '2023-04-23', 'open': 110.98, 'high': 121.07, 'low': 100.89, 'close': 116.03, 'volume': 10089.29}, {'date': '2023-04-24', 'open': 110.97, 'high': 121.06, 'low': 100.88, 'close': 116.02, 'volume': 10088.5}, {'date': '2023-04-25', 'open': 110.96, 'high': 121.05, 'low': 100.88, 'close': 116.01, 'volume': 10087.72}, {'date': '2023-04-26', 'open': 110.96, 'high': 121.04, 'low': 100.87, 'close': 116.0, 'volume': 10086.96}, {'date': '2023-04-27', 'open': 110.95, 'high': 121.03, 'low': 100.86, 'close': 115.99, 'volume': 10086.21}, {'date': '2023-04-28', 'open': 110.94, 'high': 121.03, 'low': 100.85, 'close': 115.98, 'volume': 10085.47}, {'date': '2023-04-29', 'open': 110.93, 'high': 121.02, 'low': 100.85, 'close': 115.97, 'volume': 10084.75}, {'date': '2023-04-30', 'open': 110.92, 'high': 121.01, 'low': 100.84, 'close': 115.97, 'volume': 10084.03}, {'date': '2023-05-01', 'open': 110.92, 'high': 121.0, 'low': 100.83, 'close': 115.96, 'volume': 10083.33}, {'date': '2023-05-02', 'open': 110.91, 'high': 120.99, 'low': 100.83, 'close': 115.95, 'volume': 10082.64}, {'date': '2023-05-03', 'open': 110.9, 'high': 120.98, 'low': 100.82, 'close': 115.94, 'volume': 10081.97}, {'date': '2023-05-04', 'open': 110.89, 'high': 120.98, 'low': 100.81, 'close': 115.93, 'volume': 10081.3}, {'date': '2023-05-05', 'open': 110.89, 'high': 120.97, 'low': 100.81, 'close': 115.93, 'volume': 10080.65}, {'date': '2023-05-06', 'open': 110.88, 'high': 120.96, 'low': 100.8, 'close': 115.92, 'volume': 10080.0}, {'date': '2023-05-07', 'open': 110.87, 'high': 120.95, 'low': 100.79, 'close': 115.91, 'volume': 10079.37}, {'date': '2023-05-08', 'open': 110.87, 'high': 120.94, 'low': 100.79, 'close': 115.91, 'volume': 10078.74}, {'date': '2023-05-09', 'open': 110.86, 'high': 120.94, 'low': 100.78, 'close': 115.9, 'volume': 10078.12}, {'date': '2023-05-10', 'open': 110.85, 'high': 120.93, 'low': 100.78, 'close': 115.89, 'volume': 10077.52}, {'date': '2023-05-11', 'open': 110.85, 'high': 120.92, 'low': 100.77, 'close': 115.88, 'volume': 10076.92}, {'date': '2023-05-12', 'open': 110.84, 'high': 120.92, 'low': 100.76, 'close': 115.88, 'volume': 10076.34}, {'date': '2023-05-13', 'open': 110.83, 'high': 120.91, 'low': 100.76, 'close': 115.87, 'volume': 10075.76}, {'date': '2023-05-14', 'open': 110.83, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10075.19}, {'date': '2023-05-15', 'open': 110.82, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10074.63}, {'date': '2023-05-16', 'open': 110.81, 'high': 120.89, 'low': 100.74, 'close': 115.85, 'volume': 10074.07}, {'date': '2023-05-17', 'open': 110.81, 'high': 120.88, 'low': 100.74, 'close': 115.85, 'volume': 10073.53}, {'date': '2023-05-18', 'open': 110.8, 'high': 120.88, 'low': 100.73, 'close': 115.84, 'volume': 10072.99}, {'date': '2023-05-19', 'open': 110.8, 'high': 120.87, 'low': 100.72, 'close': 115.83, 'volume': 10072.46}, {'date': '2023-05-20', 'open': 110.79, 'high': 120.86, 'low': 100.72, 'close': 115.83, 'volume': 10071.94}, {'date': '2023-05-21', 'open': 110.79, 'high': 120.86, 'low': 100.71, 'close': 115.82, 'volume': 10071.43}, {'date': '2023-05-22', 'open': 110.78, 'high': 120.85, 'low': 100.71, 'close': 115.82, 'volume': 10070.92}, {'date': '2023-05-23', 'open': 110.77, 'high': 120.85, 'low': 100.7, 'close': 115.81, 'volume': 10070.42}, {'date': '2023-05-24', 'open': 110.77, 'high': 120.84, 'low': 100.7, 'close': 115.8, 'volume': 10069.93}, {'date': '2023-05-25', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.8, 'volume': 10069.44}, {'date': '2023-05-26', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.79, 'volume': 10068.97}, {'date': '2023-05-27', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.79, 'volume': 10068.49}, {'date': '2023-05-28', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.78, 'volume': 10068.03}, {'date': '2023-05-29', 'open': 110.74, 'high': 120.81, 'low': 100.68, 'close': 115.78, 'volume': 10067.57}, {'date': '2023-05-30', 'open': 110.74, 'high': 120.81, 'low': 100.67, 'close': 115.77, 'volume': 10067.11}, {'date': '2023-05-31', 'open': 110.73, 'high': 120.8, 'low': 100.67, 'close': 115.77, 'volume': 10066.67}, {'date': '2023-06-01', 'open': 110.73, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10066.23}, {'date': '2023-06-02', 'open': 110.72, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10065.79}, {'date': '2023-06-03', 'open': 110.72, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10065.36}, {'date': '2023-06-04', 'open': 110.71, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10064.94}, {'date': '2023-06-05', 'open': 110.71, 'high': 120.77, 'low': 100.65, 'close': 115.74, 'volume': 10064.52}, {'date': '2023-06-06', 'open': 110.71, 'high': 120.77, 'low': 100.64, 'close': 115.74, 'volume': 10064.1}, {'date': '2023-06-07', 'open': 110.7, 'high': 120.76, 'low': 100.64, 'close': 115.73, 'volume': 10063.69}, {'date': '2023-06-08', 'open': 110.7, 'high': 120.76, 'low': 100.63, 'close': 115.73, 'volume': 10063.29}, {'date': '2023-06-09', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.89}, {'date': '2023-06-10', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.5}, {'date': '2023-06-11', 'open': 110.68, 'high': 120.75, 'low': 100.62, 'close': 115.71, 'volume': 10062.11}, {'date': '2023-06-12', 'open': 110.68, 'high': 120.74, 'low': 100.62, 'close': 115.71, 'volume': 10061.73}, {'date': '2023-06-13', 'open': 110.67, 'high': 120.74, 'low': 100.61, 'close': 115.71, 'volume': 10061.35}, {'date': '2023-06-14', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.98}, {'date': '2023-06-15', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.61}, {'date': '2023-06-16', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10060.24}, {'date': '2023-06-17', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10059.88}, {'date': '2023-06-18', 'open': 110.65, 'high': 120.71, 'low': 100.6, 'close': 115.68, 'volume': 10059.52}, {'date': '2023-06-19', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10059.17}, {'date': '2023-06-20', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10058.82}, {'date': '2023-06-21', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.48}, {'date': '2023-06-22', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.14}, {'date': '2023-06-23', 'open': 110.64, 'high': 120.69, 'low': 100.58, 'close': 115.66, 'volume': 10057.8}, {'date': '2023-06-24', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.47}, {'date': '2023-06-25', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.14}, {'date': '2023-06-26', 'open': 110.62, 'high': 120.68, 'low': 100.57, 'close': 115.65, 'volume': 10056.82}, {'date': '2023-06-27', 'open': 110.62, 'high': 120.68, 'low': 100.56, 'close': 115.65, 'volume': 10056.5}, {'date': '2023-06-28', 'open': 110.62, 'high': 120.67, 'low': 100.56, 'close': 115.65, 'volume': 10056.18}, {'date': '2023-06-29', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.87}, {'date': '2023-06-30', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.56}, {'date': '2023-07-01', 'open': 110.61, 'high': 120.66, 'low': 100.55, 'close': 115.64, 'volume': 10055.25}, {'date': '2023-07-02', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.95}, {'date': '2023-07-03', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.64}, {'date': '2023-07-04', 'open': 110.6, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.35}, {'date': '2023-07-05', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.05}, {'date': '2023-07-06', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10053.76}, {'date': '2023-07-07', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.48}, {'date': '2023-07-08', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.19}, {'date': '2023-07-09', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.91}, {'date': '2023-07-10', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.63}, {'date': '2023-07-11', 'open': 110.58, 'high': 120.63, 'low': 100.52, 'close': 115.6, 'volume': 10052.36}, {'date': '2023-07-12', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10052.08}, {'date': '2023-07-13', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10051.81}, {'date': '2023-07-14', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.59, 'volume': 10051.55}, {'date': '2023-07-15', 'open': 110.56, 'high': 120.62, 'low': 100.51, 'close': 115.59, 'volume': 10051.28}, {'date': '2023-07-16', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.59, 'volume': 10051.02}, {'date': '2023-07-17', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.76}, {'date': '2023-07-18', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.51}, {'date': '2023-07-19', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.58, 'volume': 10050.25}, {'date': '2023-07-20', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10050.0}, {'date': '2023-07-21', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10049.75}, {'date': '2023-07-22', 'open': 110.54, 'high': 120.59, 'low': 100.5, 'close': 115.57, 'volume': 10049.5}, {'date': '2023-07-23', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.57, 'volume': 10049.26}, {'date': '2023-07-24', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10049.02}, {'date': '2023-07-25', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10048.78}, {'date': '2023-07-26', 'open': 110.53, 'high': 120.58, 'low': 100.49, 'close': 115.56, 'volume': 10048.54}, {'date': '2023-07-27', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.56, 'volume': 10048.31}, {'date': '2023-07-28', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.55, 'volume': 10048.08}, {'date': '2023-07-29', 'open': 110.53, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.85}, {'date': '2023-07-30', 'open': 110.52, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.62}, {'date': '2023-07-31', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.55, 'volume': 10047.39}, {'date': '2023-08-01', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.54, 'volume': 10047.17}, {'date': '2023-08-02', 'open': 110.52, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.95}, {'date': '2023-08-03', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.73}, {'date': '2023-08-04', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.53, 'volume': 10046.51}, {'date': '2023-08-05', 'open': 110.51, 'high': 120.56, 'low': 100.46, 'close': 115.53, 'volume': 10046.3}, {'date': '2023-08-06', 'open': 110.51, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10046.08}, {'date': '2023-08-07', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.87}, {'date': '2023-08-08', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.66}, {'date': '2023-08-09', 'open': 110.5, 'high': 120.55, 'low': 100.45, 'close': 115.52, 'volume': 10045.45}, {'date': '2023-08-10', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.25}, {'date': '2023-08-11', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.05}, {'date': '2023-08-12', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10044.84}, {'date': '2023-08-13', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.51, 'volume': 10044.64}, {'date': '2023-08-14', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.44}, {'date': '2023-08-15', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.25}, {'date': '2023-08-16', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.05}, {'date': '2023-08-17', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.5, 'volume': 10043.86}, {'date': '2023-08-18', 'open': 110.48, 'high': 120.52, 'low': 100.44, 'close': 115.5, 'volume': 10043.67}, {'date': '2023-08-19', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.48}, {'date': '2023-08-20', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.29}, {'date': '2023-08-21', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.1}, {'date': '2023-08-22', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.49, 'volume': 10042.92}, {'date': '2023-08-23', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.74}, {'date': '2023-08-24', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.55}, {'date': '2023-08-25', 'open': 110.47, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.37}, {'date': '2023-08-26', 'open': 110.46, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.19}, {'date': '2023-08-27', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10042.02}, {'date': '2023-08-28', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.84}, {'date': '2023-08-29', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.67}, {'date': '2023-08-30', 'open': 110.46, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.49}, {'date': '2023-08-31', 'open': 110.45, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.32}, {'date': '2023-09-01', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10041.15}, {'date': '2023-09-02', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.98}, {'date': '2023-09-03', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.82}, {'date': '2023-09-04', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.65}, {'date': '2023-09-05', 'open': 110.45, 'high': 120.49, 'low': 100.4, 'close': 115.47, 'volume': 10040.49}, {'date': '2023-09-06', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.32}, {'date': '2023-09-07', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.16}, {'date': '2023-09-08', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.0}, {'date': '2023-09-09', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.84}, {'date': '2023-09-10', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.68}, {'date': '2023-09-11', 'open': 110.43, 'high': 120.47, 'low': 100.4, 'close': 115.45, 'volume': 10039.53}, {'date': '2023-09-12', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.37}, {'date': '2023-09-13', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.22}, {'date': '2023-09-14', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.06}, {'date': '2023-09-15', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.91}, {'date': '2023-09-16', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.76}, {'date': '2023-09-17', 'open': 110.42, 'high': 120.46, 'low': 100.39, 'close': 115.44, 'volume': 10038.61}, {'date': '2023-09-18', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.46}, {'date': '2023-09-19', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.31}, {'date': '2023-09-20', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.17}, {'date': '2023-09-21', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.02}, {'date': '2023-09-22', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.44, 'volume': 10037.88}, {'date': '2023-09-23', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.74}, {'date': '2023-09-24', 'open': 110.41, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.59}, {'date': '2023-09-25', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.45}, {'date': '2023-09-26', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.31}, {'date': '2023-09-27', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.17}, {'date': '2023-09-28', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.43, 'volume': 10037.04}, {'date': '2023-09-29', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.9}, {'date': '2023-09-30', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.76}, {'date': '2023-10-01', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.63}, {'date': '2023-10-02', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.5}, {'date': '2023-10-03', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.36}, {'date': '2023-10-04', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.23}, {'date': '2023-10-05', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.1}, {'date': '2023-10-06', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.97}, {'date': '2023-10-07', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.84}, {'date': '2023-10-08', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.71}, {'date': '2023-10-09', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.59}, {'date': '2023-10-10', 'open': 110.39, 'high': 120.43, 'low': 100.35, 'close': 115.41, 'volume': 10035.46}, {'date': '2023-10-11', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.41, 'volume': 10035.34}, {'date': '2023-10-12', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.21}, {'date': '2023-10-13', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.09}, {'date': '2023-10-14', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.97}, {'date': '2023-10-15', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.84}, {'date': '2023-10-16', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.72}, {'date': '2023-10-17', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.6}, {'date': '2023-10-18', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.48}, {'date': '2023-10-19', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.36}, {'date': '2023-10-20', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.25}, {'date': '2023-10-21', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.13}, {'date': '2023-10-22', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.01}, {'date': '2023-10-23', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.9}, {'date': '2023-10-24', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.78}, {'date': '2023-10-25', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.67}, {'date': '2023-10-26', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.56}, {'date': '2023-10-27', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.44}, {'date': '2023-10-28', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.33}, {'date': '2023-10-29', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.22}, {'date': '2023-10-30', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.11}, {'date': '2023-10-31', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.0}, {'date': '2023-11-01', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.89}, {'date': '2023-11-02', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.79}, {'date': '2023-11-03', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.68}, {'date': '2023-11-04', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.37, 'volume': 10032.57}, {'date': '2023-11-05', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.47}, {'date': '2023-11-06', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.36}, {'date': '2023-11-07', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.26}, {'date': '2023-11-08', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.15}, {'date': '2023-11-09', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10032.05}, {'date': '2023-11-10', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.95}, {'date': '2023-11-11', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.85}, {'date': '2023-11-12', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.75}, {'date': '2023-11-13', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.65}, {'date': '2023-11-14', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.55}, {'date': '2023-11-15', 'open': 110.35, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.45}, {'date': '2023-11-16', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.35}, {'date': '2023-11-17', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.25}, {'date': '2023-11-18', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.15}, {'date': '2023-11-19', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.06}, {'date': '2023-11-20', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10030.96}, {'date': '2023-11-21', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.86}, {'date': '2023-11-22', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.77}, {'date': '2023-11-23', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.67}, {'date': '2023-11-24', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.58}, {'date': '2023-11-25', 'open': 110.34, 'high': 120.37, 'low': 100.3, 'close': 115.35, 'volume': 10030.49}, {'date': '2023-11-26', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.4}, {'date': '2023-11-27', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.3}, {'date': '2023-11-28', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.21}, {'date': '2023-11-29', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.12}, {'date': '2023-11-30', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.03}, {'date': '2023-12-01', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.94}, {'date': '2023-12-02', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.85}, {'date': '2023-12-03', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.76}, {'date': '2023-12-04', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.67}, {'date': '2023-12-05', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.59}, {'date': '2023-12-06', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.5}, {'date': '2023-12-07', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.41}, {'date': '2023-12-08', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.33}, {'date': '2023-12-09', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.24}, {'date': '2023-12-10', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.15}, {'date': '2023-12-11', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10029.07}, {'date': '2023-12-12', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.99}, {'date': '2023-12-13', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.9}, {'date': '2023-12-14', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.82}, {'date': '2023-12-15', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.74}, {'date': '2023-12-16', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.65}, {'date': '2023-12-17', 'open': 110.31, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.57}, {'date': '2023-12-18', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.49}, {'date': '2023-12-19', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.41}, {'date': '2023-12-20', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.33}, {'date': '2023-12-21', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.25}, {'date': '2023-12-22', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.17}, {'date': '2023-12-23', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.09}, {'date': '2023-12-24', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.01}, {'date': '2023-12-25', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10027.93}, {'date': '2023-12-26', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.86}, {'date': '2023-12-27', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.78}, {'date': '2023-12-28', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.7}, {'date': '2023-12-29', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.62}, {'date': '2023-12-30', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.55}, {'date': '2023-12-31', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.47}, {'date': '2024-01-01', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.4}, {'date': '2024-01-02', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.32}, {'date': '2024-01-03', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.25}, {'date': '2024-01-04', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.17}, {'date': '2024-01-05', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.1}, {'date': '2024-01-06', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10027.03}, {'date': '2024-01-07', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.95}, {'date': '2024-01-08', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.88}, {'date': '2024-01-09', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.81}, {'date': '2024-01-10', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.74}, {'date': '2024-01-11', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.67}, {'date': '2024-01-12', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.6}, {'date': '2024-01-13', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.53}, {'date': '2024-01-14', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.46}, {'date': '2024-01-15', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.39}, {'date': '2024-01-16', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.32}, {'date': '2024-01-17', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.25}, {'date': '2024-01-18', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.18}, {'date': '2024-01-19', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.11}, {'date': '2024-01-20', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.04}, {'date': '2024-01-21', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.97}, {'date': '2024-01-22', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.91}, {'date': '2024-01-23', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.84}, {'date': '2024-01-24', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.77}, {'date': '2024-01-25', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.71}, {'date': '2024-01-26', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.64}, {'date': '2024-01-27', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.58}, {'date': '2024-01-28', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.51}, {'date': '2024-01-29', 'open': 110.28, 'high': 120.31, 'low': 100.25, 'close': 115.29, 'volume': 10025.45}, {'date': '2024-01-30', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.38}, {'date': '2024-01-31', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.32}, {'date': '2024-02-01', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.25}, {'date': '2024-02-02', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.19}, {'date': '2024-02-03', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.13}, {'date': '2024-02-04', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.06}, {'date': '2024-02-05', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.0}, {'date': '2024-02-06', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.94}, {'date': '2024-02-07', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.88}, {'date': '2024-02-08', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.81}, {'date': '2024-02-09', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.75}, {'date': '2024-02-10', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.69}, {'date': '2024-02-11', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.63}, {'date': '2024-02-12', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.57}, {'date': '2024-02-13', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.51}, {'date': '2024-02-14', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.45}, {'date': '2024-02-15', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.39}, {'date': '2024-02-16', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.33}, {'date': '2024-02-17', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.27}, {'date': '2024-02-18', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.21}, {'date': '2024-02-19', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.15}, {'date': '2024-02-20', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.1}, {'date': '2024-02-21', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.04}, {'date': '2024-02-22', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.98}, {'date': '2024-02-23', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.92}, {'date': '2024-02-24', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.87}, {'date': '2024-02-25', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.81}, {'date': '2024-02-26', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.75}, {'date': '2024-02-27', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.7}, {'date': '2024-02-28', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.64}, {'date': '2024-02-29', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.58}, {'date': '2024-03-01', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.53}, {'date': '2024-03-02', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.47}, {'date': '2024-03-03', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.42}, {'date': '2024-03-04', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.36}, {'date': '2024-03-05', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.31}, {'date': '2024-03-06', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.26}, {'date': '2024-03-07', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.2}, {'date': '2024-03-08', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.15}, {'date': '2024-03-09', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.09}, {'date': '2024-03-10', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10023.04}, {'date': '2024-03-11', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.99}, {'date': '2024-03-12', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.94}, {'date': '2024-03-13', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.88}, {'date': '2024-03-14', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.83}, {'date': '2024-03-15', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.78}, {'date': '2024-03-16', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.73}, {'date': '2024-03-17', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.68}, {'date': '2024-03-18', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.62}, {'date': '2024-03-19', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.57}, {'date': '2024-03-20', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.52}, {'date': '2024-03-21', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.47}, {'date': '2024-03-22', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.42}, {'date': '2024-03-23', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.37}, {'date': '2024-03-24', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.32}, {'date': '2024-03-25', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.27}, {'date': '2024-03-26', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.22}, {'date': '2024-03-27', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.17}, {'date': '2024-03-28', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.12}, {'date': '2024-03-29', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.08}, {'date': '2024-03-30', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.03}, {'date': '2024-03-31', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.98}, {'date': '2024-04-01', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.93}, {'date': '2024-04-02', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.88}, {'date': '2024-04-03', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.83}, {'date': '2024-04-04', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.79}, {'date': '2024-04-05', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.74}, {'date': '2024-04-06', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.69}, {'date': '2024-04-07', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.65}, {'date': '2024-04-08', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.6}, {'date': '2024-04-09', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.55}, {'date': '2024-04-10', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.51}, {'date': '2024-04-11', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.46}, {'date': '2024-04-12', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.41}, {'date': '2024-04-13', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.37}, {'date': '2024-04-14', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.32}, {'date': '2024-04-15', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.24, 'volume': 10021.28}, {'date': '2024-04-16', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.23}, {'date': '2024-04-17', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.19}, {'date': '2024-04-18', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.14}, {'date': '2024-04-19', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.1}, {'date': '2024-04-20', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.05}, {'date': '2024-04-21', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.01}, {'date': '2024-04-22', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.96}, {'date': '2024-04-23', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.92}, {'date': '2024-04-24', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.88}, {'date': '2024-04-25', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.83}, {'date': '2024-04-26', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.79}, {'date': '2024-04-27', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.75}, {'date': '2024-04-28', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.7}, {'date': '2024-04-29', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.66}, {'date': '2024-04-30', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.62}, {'date': '2024-05-01', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.58}, {'date': '2024-05-02', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.53}, {'date': '2024-05-03', 'open': 110.23, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.49}, {'date': '2024-05-04', 'open': 110.22, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.45}, {'date': '2024-05-05', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.41}, {'date': '2024-05-06', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.37}, {'date': '2024-05-07', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.33}, {'date': '2024-05-08', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.28}, {'date': '2024-05-09', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.24}, {'date': '2024-05-10', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.2}, {'date': '2024-05-11', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.16}, {'date': '2024-05-12', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.12}, {'date': '2024-05-13', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.08}, {'date': '2024-05-14', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.04}, {'date': '2024-05-15', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.0}, {'date': '2024-05-16', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.96}, {'date': '2024-05-17', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.92}, {'date': '2024-05-18', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.88}, {'date': '2024-05-19', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.84}, {'date': '2024-05-20', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.8}, {'date': '2024-05-21', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.76}, {'date': '2024-05-22', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.72}, {'date': '2024-05-23', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.69}, {'date': '2024-05-24', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.65}, {'date': '2024-05-25', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.61}, {'date': '2024-05-26', 'open': 110.22, 'high': 120.23, 'low': 100.2, 'close': 115.23, 'volume': 10019.57}, {'date': '2024-05-27', 'open': 110.21, 'high': 120.23, 'low': 100.2, 'close': 115.22, 'volume': 10019.53}, {'date': '2024-05-28', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.49}, {'date': '2024-05-29', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.46}, {'date': '2024-05-30', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.42}, {'date': '2024-05-31', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.38}, {'date': '2024-06-01', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.34}, {'date': '2024-06-02', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.31}, {'date': '2024-06-03', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.27}, {'date': '2024-06-04', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.23}, {'date': '2024-06-05', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.19}, {'date': '2024-06-06', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.16}, {'date': '2024-06-07', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.12}, {'date': '2024-06-08', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.08}, {'date': '2024-06-09', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.05}, {'date': '2024-06-10', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.01}, {'date': '2024-06-11', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.98}, {'date': '2024-06-12', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.94}, {'date': '2024-06-13', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.9}, {'date': '2024-06-14', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.87}, {'date': '2024-06-15', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.83}, {'date': '2024-06-16', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.8}, {'date': '2024-06-17', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.76}, {'date': '2024-06-18', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.22, 'volume': 10018.73}, {'date': '2024-06-19', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.69}, {'date': '2024-06-20', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.66}, {'date': '2024-06-21', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.62}, {'date': '2024-06-22', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.59}, {'date': '2024-06-23', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.55}, {'date': '2024-06-24', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.52}, {'date': '2024-06-25', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.48}, {'date': '2024-06-26', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.45}, {'date': '2024-06-27', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.42}, {'date': '2024-06-28', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.38}, {'date': '2024-06-29', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.35}, {'date': '2024-06-30', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.32}, {'date': '2024-07-01', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.28}, {'date': '2024-07-02', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.25}, {'date': '2024-07-03', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.21}, {'date': '2024-07-04', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.18}, {'date': '2024-07-05', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.15}, {'date': '2024-07-06', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.12}, {'date': '2024-07-07', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.08}, {'date': '2024-07-08', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.05}, {'date': '2024-07-09', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.02}, {'date': '2024-07-10', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.99}, {'date': '2024-07-11', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.95}, {'date': '2024-07-12', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.92}, {'date': '2024-07-13', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.89}, {'date': '2024-07-14', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.86}, {'date': '2024-07-15', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.83}, {'date': '2024-07-16', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.79}, {'date': '2024-07-17', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.76}, {'date': '2024-07-18', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.73}, {'date': '2024-07-19', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.7}, {'date': '2024-07-20', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.67}, {'date': '2024-07-21', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.64}, {'date': '2024-07-22', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.61}, {'date': '2024-07-23', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.57}, {'date': '2024-07-24', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.54}, {'date': '2024-07-25', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.51}, {'date': '2024-07-26', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.48}, {'date': '2024-07-27', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.45}, {'date': '2024-07-28', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.42}, {'date': '2024-07-29', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.39}, {'date': '2024-07-30', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.36}, {'date': '2024-07-31', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.33}, {'date': '2024-08-01', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.3}, {'date': '2024-08-02', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.27}, {'date': '2024-08-03', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.24}, {'date': '2024-08-04', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.21}, {'date': '2024-08-05', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.18}, {'date': '2024-08-06', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.15}, {'date': '2024-08-07', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.12}, {'date': '2024-08-08', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.09}, {'date': '2024-08-09', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.06}, {'date': '2024-08-10', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.04}, {'date': '2024-08-11', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.01}, {'date': '2024-08-12', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10016.98}, {'date': '2024-08-13', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.95}, {'date': '2024-08-14', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.92}, {'date': '2024-08-15', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.89}, {'date': '2024-08-16', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.86}, {'date': '2024-08-17', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.84}, {'date': '2024-08-18', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.81}, {'date': '2024-08-19', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.78}, {'date': '2024-08-20', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.75}, {'date': '2024-08-21', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.72}, {'date': '2024-08-22', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.69}, {'date': '2024-08-23', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.67}, {'date': '2024-08-24', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.64}, {'date': '2024-08-25', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.61}, {'date': '2024-08-26', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.58}, {'date': '2024-08-27', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.56}, {'date': '2024-08-28', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.53}, {'date': '2024-08-29', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.5}, {'date': '2024-08-30', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.47}, {'date': '2024-08-31', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.45}, {'date': '2024-09-01', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.42}, {'date': '2024-09-02', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.39}, {'date': '2024-09-03', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.37}, {'date': '2024-09-04', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.34}, {'date': '2024-09-05', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.31}, {'date': '2024-09-06', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.29}, {'date': '2024-09-07', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.26}, {'date': '2024-09-08', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.23}, {'date': '2024-09-09', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.21}, {'date': '2024-09-10', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.18}, {'date': '2024-09-11', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.16}, {'date': '2024-09-12', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.13}, {'date': '2024-09-13', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.1}, {'date': '2024-09-14', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.08}, {'date': '2024-09-15', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.05}, {'date': '2024-09-16', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.03}, {'date': '2024-09-17', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.0}, {'date': '2024-09-18', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.97}, {'date': '2024-09-19', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.95}, {'date': '2024-09-20', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.92}, {'date': '2024-09-21', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.9}, {'date': '2024-09-22', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.87}, {'date': '2024-09-23', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.85}, {'date': '2024-09-24', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.82}, {'date': '2024-09-25', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.8}, {'date': '2024-09-26', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.77}, {'date': '2024-09-27', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.75}, {'date': '2024-09-28', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.72}, {'date': '2024-09-29', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.7}, {'date': '2024-09-30', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.67}, {'date': '2024-10-01', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.65}, {'date': '2024-10-02', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.62}, {'date': '2024-10-03', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.6}, {'date': '2024-10-04', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.58}, {'date': '2024-10-05', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.55}, {'date': '2024-10-06', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.53}, {'date': '2024-10-07', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.5}, {'date': '2024-10-08', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.48}, {'date': '2024-10-09', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.46}, {'date': '2024-10-10', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.43}, {'date': '2024-10-11', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.41}, {'date': '2024-10-12', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.38}, {'date': '2024-10-13', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.36}, {'date': '2024-10-14', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.34}, {'date': '2024-10-15', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.31}, {'date': '2024-10-16', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.29}, {'date': '2024-10-17', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.27}, {'date': '2024-10-18', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.24}, {'date': '2024-10-19', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.22}, {'date': '2024-10-20', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.2}, {'date': '2024-10-21', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.17}, {'date': '2024-10-22', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.15}, {'date': '2024-10-23', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.13}, {'date': '2024-10-24', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.11}, {'date': '2024-10-25', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.08}, {'date': '2024-10-26', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.06}, {'date': '2024-10-27', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.04}, {'date': '2024-10-28', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.02}, {'date': '2024-10-29', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.99}, {'date': '2024-10-30', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.97}, {'date': '2024-10-31', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.95}, {'date': '2024-11-01', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.93}, {'date': '2024-11-02', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.9}, {'date': '2024-11-03', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.88}, {'date': '2024-11-04', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.86}, {'date': '2024-11-05', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.84}, {'date': '2024-11-06', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.81}, {'date': '2024-11-07', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.79}, {'date': '2024-11-08', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.77}, {'date': '2024-11-09', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.75}, {'date': '2024-11-10', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.73}, {'date': '2024-11-11', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.71}, {'date': '2024-11-12', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.68}, {'date': '2024-11-13', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.66}, {'date': '2024-11-14', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.64}, {'date': '2024-11-15', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.62}, {'date': '2024-11-16', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.6}, {'date': '2024-11-17', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.58}, {'date': '2024-11-18', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.56}, {'date': '2024-11-19', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.53}, {'date': '2024-11-20', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.51}, {'date': '2024-11-21', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.49}, {'date': '2024-11-22', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.47}, {'date': '2024-11-23', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.45}, {'date': '2024-11-24', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.43}, {'date': '2024-11-25', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.41}, {'date': '2024-11-26', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.39}, {'date': '2024-11-27', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.37}, {'date': '2024-11-28', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.35}, {'date': '2024-11-29', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.33}, {'date': '2024-11-30', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.31}, {'date': '2024-12-01', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.29}, {'date': '2024-12-02', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.27}, {'date': '2024-12-03', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.25}, {'date': '2024-12-04', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.22}, {'date': '2024-12-05', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.2}, {'date': '2024-12-06', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.18}, {'date': '2024-12-07', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.16}, {'date': '2024-12-08', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.14}, {'date': '2024-12-09', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.12}, {'date': '2024-12-10', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.1}, {'date': '2024-12-11', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.08}, {'date': '2024-12-12', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.06}, {'date': '2024-12-13', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.04}, {'date': '2024-12-14', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.03}, {'date': '2024-12-15', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.01}, {'date': '2024-12-16', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.99}, {'date': '2024-12-17', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.97}, {'date': '2024-12-18', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.95}, {'date': '2024-12-19', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.93}, {'date': '2024-12-20', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.91}, {'date': '2024-12-21', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.89}, {'date': '2024-12-22', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.87}, {'date': '2024-12-23', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.85}, {'date': '2024-12-24', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.83}, {'date': '2024-12-25', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.81}, {'date': '2024-12-26', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.79}, {'date': '2024-12-27', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.77}, {'date': '2024-12-28', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.76}, {'date': '2024-12-29', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.74}, {'date': '2024-12-30', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.72}, {'date': '2024-12-31', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.7}, {'date': '2025-01-01', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.68}, {'date': '2025-01-02', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.66}, {'date': '2025-01-03', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.64}, {'date': '2025-01-04', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.62}, {'date': '2025-01-05', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.61}, {'date': '2025-01-06', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.59}, {'date': '2025-01-07', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.57}, {'date': '2025-01-08', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.55}, {'date': '2025-01-09', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.53}, {'date': '2025-01-10', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.51}, {'date': '2025-01-11', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.16, 'volume': 10013.5}, {'date': '2025-01-12', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.48}, {'date': '2025-01-13', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.46}, {'date': '2025-01-14', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.44}, {'date': '2025-01-15', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.42}, {'date': '2025-01-16', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.4}, {'date': '2025-01-17', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.39}, {'date': '2025-01-18', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.37}, {'date': '2025-01-19', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.35}, {'date': '2025-01-20', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.33}, {'date': '2025-01-21', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.32}, {'date': '2025-01-22', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.3}, {'date': '2025-01-23', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.28}, {'date': '2025-01-24', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.26}, {'date': '2025-01-25', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.25}, {'date': '2025-01-26', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.23}, {'date': '2025-01-27', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.21}, {'date': '2025-01-28', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.19}, {'date': '2025-01-29', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.18}, {'date': '2025-01-30', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.16}, {'date': '2025-01-31', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.14}, {'date': '2025-02-01', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.12}, {'date': '2025-02-02', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.11}, {'date': '2025-02-03', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.09}, {'date': '2025-02-04', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.07}, {'date': '2025-02-05', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.05}, {'date': '2025-02-06', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.04}, {'date': '2025-02-07', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.02}, {'date': '2025-02-08', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.0}, {'date': '2025-02-09', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.99}, {'date': '2025-02-10', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.97}, {'date': '2025-02-11', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.95}, {'date': '2025-02-12', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.94}, {'date': '2025-02-13', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.92}, {'date': '2025-02-14', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.9}, {'date': '2025-02-15', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.89}, {'date': '2025-02-16', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.87}, {'date': '2025-02-17', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.85}, {'date': '2025-02-18', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.84}, {'date': '2025-02-19', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.82}, {'date': '2025-02-20', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.8}, {'date': '2025-02-21', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.79}, {'date': '2025-02-22', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.77}, {'date': '2025-02-23', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.76}, {'date': '2025-02-24', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.74}, {'date': '2025-02-25', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.72}, {'date': '2025-02-26', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.71}, {'date': '2025-02-27', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.69}, {'date': '2025-02-28', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.67}, {'date': '2025-03-01', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.66}, {'date': '2025-03-02', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.64}, {'date': '2025-03-03', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.63}, {'date': '2025-03-04', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.61}, {'date': '2025-03-05', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.59}, {'date': '2025-03-06', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.58}, {'date': '2025-03-07', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.56}, {'date': '2025-03-08', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.55}, {'date': '2025-03-09', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.53}, {'date': '2025-03-10', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.52}, {'date': '2025-03-11', 'open': 110.14, 'high': 120.15, 'low': 100.12, 'close': 115.14, 'volume': 10012.5}], factors_data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}, {'date': '2023-03-03', 'open': 111.8, 'high': 121.97, 'low': 101.64, 'close': 116.89, 'volume': 10163.93}, {'date': '2023-03-04', 'open': 111.77, 'high': 121.94, 'low': 101.61, 'close': 116.85, 'volume': 10161.29}, {'date': '2023-03-05', 'open': 111.75, 'high': 121.9, 'low': 101.59, 'close': 116.83, 'volume': 10158.73}, {'date': '2023-03-06', 'open': 111.72, 'high': 121.88, 'low': 101.56, 'close': 116.8, 'volume': 10156.25}, {'date': '2023-03-07', 'open': 111.69, 'high': 121.85, 'low': 101.54, 'close': 116.77, 'volume': 10153.85}, {'date': '2023-03-08', 'open': 111.67, 'high': 121.82, 'low': 101.52, 'close': 116.74, 'volume': 10151.52}, {'date': '2023-03-09', 'open': 111.64, 'high': 121.79, 'low': 101.49, 'close': 116.72, 'volume': 10149.25}, {'date': '2023-03-10', 'open': 111.62, 'high': 121.76, 'low': 101.47, 'close': 116.69, 'volume': 10147.06}, {'date': '2023-03-11', 'open': 111.59, 'high': 121.74, 'low': 101.45, 'close': 116.67, 'volume': 10144.93}, {'date': '2023-03-12', 'open': 111.57, 'high': 121.71, 'low': 101.43, 'close': 116.64, 'volume': 10142.86}, {'date': '2023-03-13', 'open': 111.55, 'high': 121.69, 'low': 101.41, 'close': 116.62, 'volume': 10140.85}, {'date': '2023-03-14', 'open': 111.53, 'high': 121.67, 'low': 101.39, 'close': 116.6, 'volume': 10138.89}, {'date': '2023-03-15', 'open': 111.51, 'high': 121.64, 'low': 101.37, 'close': 116.58, 'volume': 10136.99}, {'date': '2023-03-16', 'open': 111.49, 'high': 121.62, 'low': 101.35, 'close': 116.55, 'volume': 10135.14}, {'date': '2023-03-17', 'open': 111.47, 'high': 121.6, 'low': 101.33, 'close': 116.53, 'volume': 10133.33}, {'date': '2023-03-18', 'open': 111.45, 'high': 121.58, 'low': 101.32, 'close': 116.51, 'volume': 10131.58}, {'date': '2023-03-19', 'open': 111.43, 'high': 121.56, 'low': 101.3, 'close': 116.49, 'volume': 10129.87}, {'date': '2023-03-20', 'open': 111.41, 'high': 121.54, 'low': 101.28, 'close': 116.47, 'volume': 10128.21}, {'date': '2023-03-21', 'open': 111.39, 'high': 121.52, 'low': 101.27, 'close': 116.46, 'volume': 10126.58}, {'date': '2023-03-22', 'open': 111.38, 'high': 121.5, 'low': 101.25, 'close': 116.44, 'volume': 10125.0}, {'date': '2023-03-23', 'open': 111.36, 'high': 121.48, 'low': 101.23, 'close': 116.42, 'volume': 10123.46}, {'date': '2023-03-24', 'open': 111.34, 'high': 121.46, 'low': 101.22, 'close': 116.4, 'volume': 10121.95}, {'date': '2023-03-25', 'open': 111.33, 'high': 121.45, 'low': 101.2, 'close': 116.39, 'volume': 10120.48}, {'date': '2023-03-26', 'open': 111.31, 'high': 121.43, 'low': 101.19, 'close': 116.37, 'volume': 10119.05}, {'date': '2023-03-27', 'open': 111.29, 'high': 121.41, 'low': 101.18, 'close': 116.35, 'volume': 10117.65}, {'date': '2023-03-28', 'open': 111.28, 'high': 121.4, 'low': 101.16, 'close': 116.34, 'volume': 10116.28}, {'date': '2023-03-29', 'open': 111.26, 'high': 121.38, 'low': 101.15, 'close': 116.32, 'volume': 10114.94}, {'date': '2023-03-30', 'open': 111.25, 'high': 121.36, 'low': 101.14, 'close': 116.31, 'volume': 10113.64}, {'date': '2023-03-31', 'open': 111.24, 'high': 121.35, 'low': 101.12, 'close': 116.29, 'volume': 10112.36}, {'date': '2023-04-01', 'open': 111.22, 'high': 121.33, 'low': 101.11, 'close': 116.28, 'volume': 10111.11}, {'date': '2023-04-02', 'open': 111.21, 'high': 121.32, 'low': 101.1, 'close': 116.26, 'volume': 10109.89}, {'date': '2023-04-03', 'open': 111.2, 'high': 121.3, 'low': 101.09, 'close': 116.25, 'volume': 10108.7}, {'date': '2023-04-04', 'open': 111.18, 'high': 121.29, 'low': 101.08, 'close': 116.24, 'volume': 10107.53}, {'date': '2023-04-05', 'open': 111.17, 'high': 121.28, 'low': 101.06, 'close': 116.22, 'volume': 10106.38}, {'date': '2023-04-06', 'open': 111.16, 'high': 121.26, 'low': 101.05, 'close': 116.21, 'volume': 10105.26}, {'date': '2023-04-07', 'open': 111.15, 'high': 121.25, 'low': 101.04, 'close': 116.2, 'volume': 10104.17}, {'date': '2023-04-08', 'open': 111.13, 'high': 121.24, 'low': 101.03, 'close': 116.19, 'volume': 10103.09}, {'date': '2023-04-09', 'open': 111.12, 'high': 121.22, 'low': 101.02, 'close': 116.17, 'volume': 10102.04}, {'date': '2023-04-10', 'open': 111.11, 'high': 121.21, 'low': 101.01, 'close': 116.16, 'volume': 10101.01}, {'date': '2023-04-11', 'open': 111.1, 'high': 121.2, 'low': 101.0, 'close': 116.15, 'volume': 10100.0}, {'date': '2023-04-12', 'open': 111.09, 'high': 121.19, 'low': 100.99, 'close': 116.14, 'volume': 10099.01}, {'date': '2023-04-13', 'open': 111.08, 'high': 121.18, 'low': 100.98, 'close': 116.13, 'volume': 10098.04}, {'date': '2023-04-14', 'open': 111.07, 'high': 121.17, 'low': 100.97, 'close': 116.12, 'volume': 10097.09}, {'date': '2023-04-15', 'open': 111.06, 'high': 121.15, 'low': 100.96, 'close': 116.11, 'volume': 10096.15}, {'date': '2023-04-16', 'open': 111.05, 'high': 121.14, 'low': 100.95, 'close': 116.1, 'volume': 10095.24}, {'date': '2023-04-17', 'open': 111.04, 'high': 121.13, 'low': 100.94, 'close': 116.08, 'volume': 10094.34}, {'date': '2023-04-18', 'open': 111.03, 'high': 121.12, 'low': 100.93, 'close': 116.07, 'volume': 10093.46}, {'date': '2023-04-19', 'open': 111.02, 'high': 121.11, 'low': 100.93, 'close': 116.06, 'volume': 10092.59}, {'date': '2023-04-20', 'open': 111.01, 'high': 121.1, 'low': 100.92, 'close': 116.06, 'volume': 10091.74}, {'date': '2023-04-21', 'open': 111.0, 'high': 121.09, 'low': 100.91, 'close': 116.05, 'volume': 10090.91}, {'date': '2023-04-22', 'open': 110.99, 'high': 121.08, 'low': 100.9, 'close': 116.04, 'volume': 10090.09}, {'date': '2023-04-23', 'open': 110.98, 'high': 121.07, 'low': 100.89, 'close': 116.03, 'volume': 10089.29}, {'date': '2023-04-24', 'open': 110.97, 'high': 121.06, 'low': 100.88, 'close': 116.02, 'volume': 10088.5}, {'date': '2023-04-25', 'open': 110.96, 'high': 121.05, 'low': 100.88, 'close': 116.01, 'volume': 10087.72}, {'date': '2023-04-26', 'open': 110.96, 'high': 121.04, 'low': 100.87, 'close': 116.0, 'volume': 10086.96}, {'date': '2023-04-27', 'open': 110.95, 'high': 121.03, 'low': 100.86, 'close': 115.99, 'volume': 10086.21}, {'date': '2023-04-28', 'open': 110.94, 'high': 121.03, 'low': 100.85, 'close': 115.98, 'volume': 10085.47}, {'date': '2023-04-29', 'open': 110.93, 'high': 121.02, 'low': 100.85, 'close': 115.97, 'volume': 10084.75}, {'date': '2023-04-30', 'open': 110.92, 'high': 121.01, 'low': 100.84, 'close': 115.97, 'volume': 10084.03}, {'date': '2023-05-01', 'open': 110.92, 'high': 121.0, 'low': 100.83, 'close': 115.96, 'volume': 10083.33}, {'date': '2023-05-02', 'open': 110.91, 'high': 120.99, 'low': 100.83, 'close': 115.95, 'volume': 10082.64}, {'date': '2023-05-03', 'open': 110.9, 'high': 120.98, 'low': 100.82, 'close': 115.94, 'volume': 10081.97}, {'date': '2023-05-04', 'open': 110.89, 'high': 120.98, 'low': 100.81, 'close': 115.93, 'volume': 10081.3}, {'date': '2023-05-05', 'open': 110.89, 'high': 120.97, 'low': 100.81, 'close': 115.93, 'volume': 10080.65}, {'date': '2023-05-06', 'open': 110.88, 'high': 120.96, 'low': 100.8, 'close': 115.92, 'volume': 10080.0}, {'date': '2023-05-07', 'open': 110.87, 'high': 120.95, 'low': 100.79, 'close': 115.91, 'volume': 10079.37}, {'date': '2023-05-08', 'open': 110.87, 'high': 120.94, 'low': 100.79, 'close': 115.91, 'volume': 10078.74}, {'date': '2023-05-09', 'open': 110.86, 'high': 120.94, 'low': 100.78, 'close': 115.9, 'volume': 10078.12}, {'date': '2023-05-10', 'open': 110.85, 'high': 120.93, 'low': 100.78, 'close': 115.89, 'volume': 10077.52}, {'date': '2023-05-11', 'open': 110.85, 'high': 120.92, 'low': 100.77, 'close': 115.88, 'volume': 10076.92}, {'date': '2023-05-12', 'open': 110.84, 'high': 120.92, 'low': 100.76, 'close': 115.88, 'volume': 10076.34}, {'date': '2023-05-13', 'open': 110.83, 'high': 120.91, 'low': 100.76, 'close': 115.87, 'volume': 10075.76}, {'date': '2023-05-14', 'open': 110.83, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10075.19}, {'date': '2023-05-15', 'open': 110.82, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10074.63}, {'date': '2023-05-16', 'open': 110.81, 'high': 120.89, 'low': 100.74, 'close': 115.85, 'volume': 10074.07}, {'date': '2023-05-17', 'open': 110.81, 'high': 120.88, 'low': 100.74, 'close': 115.85, 'volume': 10073.53}, {'date': '2023-05-18', 'open': 110.8, 'high': 120.88, 'low': 100.73, 'close': 115.84, 'volume': 10072.99}, {'date': '2023-05-19', 'open': 110.8, 'high': 120.87, 'low': 100.72, 'close': 115.83, 'volume': 10072.46}, {'date': '2023-05-20', 'open': 110.79, 'high': 120.86, 'low': 100.72, 'close': 115.83, 'volume': 10071.94}, {'date': '2023-05-21', 'open': 110.79, 'high': 120.86, 'low': 100.71, 'close': 115.82, 'volume': 10071.43}, {'date': '2023-05-22', 'open': 110.78, 'high': 120.85, 'low': 100.71, 'close': 115.82, 'volume': 10070.92}, {'date': '2023-05-23', 'open': 110.77, 'high': 120.85, 'low': 100.7, 'close': 115.81, 'volume': 10070.42}, {'date': '2023-05-24', 'open': 110.77, 'high': 120.84, 'low': 100.7, 'close': 115.8, 'volume': 10069.93}, {'date': '2023-05-25', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.8, 'volume': 10069.44}, {'date': '2023-05-26', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.79, 'volume': 10068.97}, {'date': '2023-05-27', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.79, 'volume': 10068.49}, {'date': '2023-05-28', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.78, 'volume': 10068.03}, {'date': '2023-05-29', 'open': 110.74, 'high': 120.81, 'low': 100.68, 'close': 115.78, 'volume': 10067.57}, {'date': '2023-05-30', 'open': 110.74, 'high': 120.81, 'low': 100.67, 'close': 115.77, 'volume': 10067.11}, {'date': '2023-05-31', 'open': 110.73, 'high': 120.8, 'low': 100.67, 'close': 115.77, 'volume': 10066.67}, {'date': '2023-06-01', 'open': 110.73, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10066.23}, {'date': '2023-06-02', 'open': 110.72, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10065.79}, {'date': '2023-06-03', 'open': 110.72, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10065.36}, {'date': '2023-06-04', 'open': 110.71, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10064.94}, {'date': '2023-06-05', 'open': 110.71, 'high': 120.77, 'low': 100.65, 'close': 115.74, 'volume': 10064.52}, {'date': '2023-06-06', 'open': 110.71, 'high': 120.77, 'low': 100.64, 'close': 115.74, 'volume': 10064.1}, {'date': '2023-06-07', 'open': 110.7, 'high': 120.76, 'low': 100.64, 'close': 115.73, 'volume': 10063.69}, {'date': '2023-06-08', 'open': 110.7, 'high': 120.76, 'low': 100.63, 'close': 115.73, 'volume': 10063.29}, {'date': '2023-06-09', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.89}, {'date': '2023-06-10', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.5}, {'date': '2023-06-11', 'open': 110.68, 'high': 120.75, 'low': 100.62, 'close': 115.71, 'volume': 10062.11}, {'date': '2023-06-12', 'open': 110.68, 'high': 120.74, 'low': 100.62, 'close': 115.71, 'volume': 10061.73}, {'date': '2023-06-13', 'open': 110.67, 'high': 120.74, 'low': 100.61, 'close': 115.71, 'volume': 10061.35}, {'date': '2023-06-14', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.98}, {'date': '2023-06-15', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.61}, {'date': '2023-06-16', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10060.24}, {'date': '2023-06-17', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10059.88}, {'date': '2023-06-18', 'open': 110.65, 'high': 120.71, 'low': 100.6, 'close': 115.68, 'volume': 10059.52}, {'date': '2023-06-19', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10059.17}, {'date': '2023-06-20', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10058.82}, {'date': '2023-06-21', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.48}, {'date': '2023-06-22', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.14}, {'date': '2023-06-23', 'open': 110.64, 'high': 120.69, 'low': 100.58, 'close': 115.66, 'volume': 10057.8}, {'date': '2023-06-24', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.47}, {'date': '2023-06-25', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.14}, {'date': '2023-06-26', 'open': 110.62, 'high': 120.68, 'low': 100.57, 'close': 115.65, 'volume': 10056.82}, {'date': '2023-06-27', 'open': 110.62, 'high': 120.68, 'low': 100.56, 'close': 115.65, 'volume': 10056.5}, {'date': '2023-06-28', 'open': 110.62, 'high': 120.67, 'low': 100.56, 'close': 115.65, 'volume': 10056.18}, {'date': '2023-06-29', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.87}, {'date': '2023-06-30', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.56}, {'date': '2023-07-01', 'open': 110.61, 'high': 120.66, 'low': 100.55, 'close': 115.64, 'volume': 10055.25}, {'date': '2023-07-02', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.95}, {'date': '2023-07-03', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.64}, {'date': '2023-07-04', 'open': 110.6, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.35}, {'date': '2023-07-05', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.05}, {'date': '2023-07-06', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10053.76}, {'date': '2023-07-07', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.48}, {'date': '2023-07-08', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.19}, {'date': '2023-07-09', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.91}, {'date': '2023-07-10', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.63}, {'date': '2023-07-11', 'open': 110.58, 'high': 120.63, 'low': 100.52, 'close': 115.6, 'volume': 10052.36}, {'date': '2023-07-12', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10052.08}, {'date': '2023-07-13', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10051.81}, {'date': '2023-07-14', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.59, 'volume': 10051.55}, {'date': '2023-07-15', 'open': 110.56, 'high': 120.62, 'low': 100.51, 'close': 115.59, 'volume': 10051.28}, {'date': '2023-07-16', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.59, 'volume': 10051.02}, {'date': '2023-07-17', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.76}, {'date': '2023-07-18', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.51}, {'date': '2023-07-19', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.58, 'volume': 10050.25}, {'date': '2023-07-20', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10050.0}, {'date': '2023-07-21', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10049.75}, {'date': '2023-07-22', 'open': 110.54, 'high': 120.59, 'low': 100.5, 'close': 115.57, 'volume': 10049.5}, {'date': '2023-07-23', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.57, 'volume': 10049.26}, {'date': '2023-07-24', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10049.02}, {'date': '2023-07-25', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10048.78}, {'date': '2023-07-26', 'open': 110.53, 'high': 120.58, 'low': 100.49, 'close': 115.56, 'volume': 10048.54}, {'date': '2023-07-27', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.56, 'volume': 10048.31}, {'date': '2023-07-28', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.55, 'volume': 10048.08}, {'date': '2023-07-29', 'open': 110.53, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.85}, {'date': '2023-07-30', 'open': 110.52, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.62}, {'date': '2023-07-31', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.55, 'volume': 10047.39}, {'date': '2023-08-01', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.54, 'volume': 10047.17}, {'date': '2023-08-02', 'open': 110.52, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.95}, {'date': '2023-08-03', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.73}, {'date': '2023-08-04', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.53, 'volume': 10046.51}, {'date': '2023-08-05', 'open': 110.51, 'high': 120.56, 'low': 100.46, 'close': 115.53, 'volume': 10046.3}, {'date': '2023-08-06', 'open': 110.51, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10046.08}, {'date': '2023-08-07', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.87}, {'date': '2023-08-08', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.66}, {'date': '2023-08-09', 'open': 110.5, 'high': 120.55, 'low': 100.45, 'close': 115.52, 'volume': 10045.45}, {'date': '2023-08-10', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.25}, {'date': '2023-08-11', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.05}, {'date': '2023-08-12', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10044.84}, {'date': '2023-08-13', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.51, 'volume': 10044.64}, {'date': '2023-08-14', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.44}, {'date': '2023-08-15', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.25}, {'date': '2023-08-16', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.05}, {'date': '2023-08-17', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.5, 'volume': 10043.86}, {'date': '2023-08-18', 'open': 110.48, 'high': 120.52, 'low': 100.44, 'close': 115.5, 'volume': 10043.67}, {'date': '2023-08-19', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.48}, {'date': '2023-08-20', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.29}, {'date': '2023-08-21', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.1}, {'date': '2023-08-22', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.49, 'volume': 10042.92}, {'date': '2023-08-23', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.74}, {'date': '2023-08-24', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.55}, {'date': '2023-08-25', 'open': 110.47, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.37}, {'date': '2023-08-26', 'open': 110.46, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.19}, {'date': '2023-08-27', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10042.02}, {'date': '2023-08-28', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.84}, {'date': '2023-08-29', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.67}, {'date': '2023-08-30', 'open': 110.46, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.49}, {'date': '2023-08-31', 'open': 110.45, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.32}, {'date': '2023-09-01', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10041.15}, {'date': '2023-09-02', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.98}, {'date': '2023-09-03', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.82}, {'date': '2023-09-04', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.65}, {'date': '2023-09-05', 'open': 110.45, 'high': 120.49, 'low': 100.4, 'close': 115.47, 'volume': 10040.49}, {'date': '2023-09-06', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.32}, {'date': '2023-09-07', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.16}, {'date': '2023-09-08', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.0}, {'date': '2023-09-09', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.84}, {'date': '2023-09-10', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.68}, {'date': '2023-09-11', 'open': 110.43, 'high': 120.47, 'low': 100.4, 'close': 115.45, 'volume': 10039.53}, {'date': '2023-09-12', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.37}, {'date': '2023-09-13', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.22}, {'date': '2023-09-14', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.06}, {'date': '2023-09-15', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.91}, {'date': '2023-09-16', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.76}, {'date': '2023-09-17', 'open': 110.42, 'high': 120.46, 'low': 100.39, 'close': 115.44, 'volume': 10038.61}, {'date': '2023-09-18', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.46}, {'date': '2023-09-19', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.31}, {'date': '2023-09-20', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.17}, {'date': '2023-09-21', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.02}, {'date': '2023-09-22', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.44, 'volume': 10037.88}, {'date': '2023-09-23', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.74}, {'date': '2023-09-24', 'open': 110.41, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.59}, {'date': '2023-09-25', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.45}, {'date': '2023-09-26', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.31}, {'date': '2023-09-27', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.17}, {'date': '2023-09-28', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.43, 'volume': 10037.04}, {'date': '2023-09-29', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.9}, {'date': '2023-09-30', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.76}, {'date': '2023-10-01', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.63}, {'date': '2023-10-02', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.5}, {'date': '2023-10-03', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.36}, {'date': '2023-10-04', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.23}, {'date': '2023-10-05', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.1}, {'date': '2023-10-06', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.97}, {'date': '2023-10-07', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.84}, {'date': '2023-10-08', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.71}, {'date': '2023-10-09', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.59}, {'date': '2023-10-10', 'open': 110.39, 'high': 120.43, 'low': 100.35, 'close': 115.41, 'volume': 10035.46}, {'date': '2023-10-11', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.41, 'volume': 10035.34}, {'date': '2023-10-12', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.21}, {'date': '2023-10-13', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.09}, {'date': '2023-10-14', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.97}, {'date': '2023-10-15', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.84}, {'date': '2023-10-16', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.72}, {'date': '2023-10-17', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.6}, {'date': '2023-10-18', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.48}, {'date': '2023-10-19', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.36}, {'date': '2023-10-20', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.25}, {'date': '2023-10-21', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.13}, {'date': '2023-10-22', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.01}, {'date': '2023-10-23', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.9}, {'date': '2023-10-24', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.78}, {'date': '2023-10-25', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.67}, {'date': '2023-10-26', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.56}, {'date': '2023-10-27', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.44}, {'date': '2023-10-28', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.33}, {'date': '2023-10-29', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.22}, {'date': '2023-10-30', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.11}, {'date': '2023-10-31', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.0}, {'date': '2023-11-01', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.89}, {'date': '2023-11-02', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.79}, {'date': '2023-11-03', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.68}, {'date': '2023-11-04', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.37, 'volume': 10032.57}, {'date': '2023-11-05', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.47}, {'date': '2023-11-06', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.36}, {'date': '2023-11-07', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.26}, {'date': '2023-11-08', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.15}, {'date': '2023-11-09', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10032.05}, {'date': '2023-11-10', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.95}, {'date': '2023-11-11', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.85}, {'date': '2023-11-12', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.75}, {'date': '2023-11-13', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.65}, {'date': '2023-11-14', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.55}, {'date': '2023-11-15', 'open': 110.35, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.45}, {'date': '2023-11-16', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.35}, {'date': '2023-11-17', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.25}, {'date': '2023-11-18', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.15}, {'date': '2023-11-19', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.06}, {'date': '2023-11-20', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10030.96}, {'date': '2023-11-21', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.86}, {'date': '2023-11-22', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.77}, {'date': '2023-11-23', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.67}, {'date': '2023-11-24', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.58}, {'date': '2023-11-25', 'open': 110.34, 'high': 120.37, 'low': 100.3, 'close': 115.35, 'volume': 10030.49}, {'date': '2023-11-26', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.4}, {'date': '2023-11-27', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.3}, {'date': '2023-11-28', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.21}, {'date': '2023-11-29', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.12}, {'date': '2023-11-30', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.03}, {'date': '2023-12-01', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.94}, {'date': '2023-12-02', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.85}, {'date': '2023-12-03', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.76}, {'date': '2023-12-04', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.67}, {'date': '2023-12-05', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.59}, {'date': '2023-12-06', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.5}, {'date': '2023-12-07', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.41}, {'date': '2023-12-08', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.33}, {'date': '2023-12-09', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.24}, {'date': '2023-12-10', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.15}, {'date': '2023-12-11', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10029.07}, {'date': '2023-12-12', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.99}, {'date': '2023-12-13', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.9}, {'date': '2023-12-14', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.82}, {'date': '2023-12-15', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.74}, {'date': '2023-12-16', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.65}, {'date': '2023-12-17', 'open': 110.31, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.57}, {'date': '2023-12-18', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.49}, {'date': '2023-12-19', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.41}, {'date': '2023-12-20', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.33}, {'date': '2023-12-21', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.25}, {'date': '2023-12-22', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.17}, {'date': '2023-12-23', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.09}, {'date': '2023-12-24', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.01}, {'date': '2023-12-25', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10027.93}, {'date': '2023-12-26', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.86}, {'date': '2023-12-27', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.78}, {'date': '2023-12-28', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.7}, {'date': '2023-12-29', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.62}, {'date': '2023-12-30', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.55}, {'date': '2023-12-31', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.47}, {'date': '2024-01-01', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.4}, {'date': '2024-01-02', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.32}, {'date': '2024-01-03', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.25}, {'date': '2024-01-04', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.17}, {'date': '2024-01-05', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.1}, {'date': '2024-01-06', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10027.03}, {'date': '2024-01-07', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.95}, {'date': '2024-01-08', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.88}, {'date': '2024-01-09', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.81}, {'date': '2024-01-10', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.74}, {'date': '2024-01-11', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.67}, {'date': '2024-01-12', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.6}, {'date': '2024-01-13', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.53}, {'date': '2024-01-14', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.46}, {'date': '2024-01-15', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.39}, {'date': '2024-01-16', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.32}, {'date': '2024-01-17', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.25}, {'date': '2024-01-18', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.18}, {'date': '2024-01-19', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.11}, {'date': '2024-01-20', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.04}, {'date': '2024-01-21', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.97}, {'date': '2024-01-22', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.91}, {'date': '2024-01-23', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.84}, {'date': '2024-01-24', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.77}, {'date': '2024-01-25', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.71}, {'date': '2024-01-26', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.64}, {'date': '2024-01-27', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.58}, {'date': '2024-01-28', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.51}, {'date': '2024-01-29', 'open': 110.28, 'high': 120.31, 'low': 100.25, 'close': 115.29, 'volume': 10025.45}, {'date': '2024-01-30', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.38}, {'date': '2024-01-31', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.32}, {'date': '2024-02-01', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.25}, {'date': '2024-02-02', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.19}, {'date': '2024-02-03', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.13}, {'date': '2024-02-04', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.06}, {'date': '2024-02-05', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.0}, {'date': '2024-02-06', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.94}, {'date': '2024-02-07', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.88}, {'date': '2024-02-08', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.81}, {'date': '2024-02-09', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.75}, {'date': '2024-02-10', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.69}, {'date': '2024-02-11', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.63}, {'date': '2024-02-12', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.57}, {'date': '2024-02-13', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.51}, {'date': '2024-02-14', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.45}, {'date': '2024-02-15', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.39}, {'date': '2024-02-16', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.33}, {'date': '2024-02-17', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.27}, {'date': '2024-02-18', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.21}, {'date': '2024-02-19', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.15}, {'date': '2024-02-20', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.1}, {'date': '2024-02-21', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.04}, {'date': '2024-02-22', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.98}, {'date': '2024-02-23', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.92}, {'date': '2024-02-24', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.87}, {'date': '2024-02-25', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.81}, {'date': '2024-02-26', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.75}, {'date': '2024-02-27', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.7}, {'date': '2024-02-28', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.64}, {'date': '2024-02-29', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.58}, {'date': '2024-03-01', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.53}, {'date': '2024-03-02', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.47}, {'date': '2024-03-03', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.42}, {'date': '2024-03-04', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.36}, {'date': '2024-03-05', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.31}, {'date': '2024-03-06', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.26}, {'date': '2024-03-07', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.2}, {'date': '2024-03-08', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.15}, {'date': '2024-03-09', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.09}, {'date': '2024-03-10', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10023.04}, {'date': '2024-03-11', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.99}, {'date': '2024-03-12', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.94}, {'date': '2024-03-13', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.88}, {'date': '2024-03-14', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.83}, {'date': '2024-03-15', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.78}, {'date': '2024-03-16', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.73}, {'date': '2024-03-17', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.68}, {'date': '2024-03-18', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.62}, {'date': '2024-03-19', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.57}, {'date': '2024-03-20', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.52}, {'date': '2024-03-21', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.47}, {'date': '2024-03-22', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.42}, {'date': '2024-03-23', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.37}, {'date': '2024-03-24', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.32}, {'date': '2024-03-25', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.27}, {'date': '2024-03-26', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.22}, {'date': '2024-03-27', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.17}, {'date': '2024-03-28', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.12}, {'date': '2024-03-29', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.08}, {'date': '2024-03-30', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.03}, {'date': '2024-03-31', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.98}, {'date': '2024-04-01', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.93}, {'date': '2024-04-02', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.88}, {'date': '2024-04-03', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.83}, {'date': '2024-04-04', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.79}, {'date': '2024-04-05', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.74}, {'date': '2024-04-06', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.69}, {'date': '2024-04-07', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.65}, {'date': '2024-04-08', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.6}, {'date': '2024-04-09', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.55}, {'date': '2024-04-10', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.51}, {'date': '2024-04-11', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.46}, {'date': '2024-04-12', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.41}, {'date': '2024-04-13', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.37}, {'date': '2024-04-14', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.32}, {'date': '2024-04-15', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.24, 'volume': 10021.28}, {'date': '2024-04-16', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.23}, {'date': '2024-04-17', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.19}, {'date': '2024-04-18', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.14}, {'date': '2024-04-19', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.1}, {'date': '2024-04-20', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.05}, {'date': '2024-04-21', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.01}, {'date': '2024-04-22', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.96}, {'date': '2024-04-23', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.92}, {'date': '2024-04-24', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.88}, {'date': '2024-04-25', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.83}, {'date': '2024-04-26', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.79}, {'date': '2024-04-27', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.75}, {'date': '2024-04-28', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.7}, {'date': '2024-04-29', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.66}, {'date': '2024-04-30', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.62}, {'date': '2024-05-01', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.58}, {'date': '2024-05-02', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.53}, {'date': '2024-05-03', 'open': 110.23, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.49}, {'date': '2024-05-04', 'open': 110.22, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.45}, {'date': '2024-05-05', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.41}, {'date': '2024-05-06', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.37}, {'date': '2024-05-07', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.33}, {'date': '2024-05-08', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.28}, {'date': '2024-05-09', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.24}, {'date': '2024-05-10', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.2}, {'date': '2024-05-11', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.16}, {'date': '2024-05-12', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.12}, {'date': '2024-05-13', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.08}, {'date': '2024-05-14', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.04}, {'date': '2024-05-15', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.0}, {'date': '2024-05-16', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.96}, {'date': '2024-05-17', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.92}, {'date': '2024-05-18', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.88}, {'date': '2024-05-19', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.84}, {'date': '2024-05-20', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.8}, {'date': '2024-05-21', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.76}, {'date': '2024-05-22', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.72}, {'date': '2024-05-23', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.69}, {'date': '2024-05-24', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.65}, {'date': '2024-05-25', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.61}, {'date': '2024-05-26', 'open': 110.22, 'high': 120.23, 'low': 100.2, 'close': 115.23, 'volume': 10019.57}, {'date': '2024-05-27', 'open': 110.21, 'high': 120.23, 'low': 100.2, 'close': 115.22, 'volume': 10019.53}, {'date': '2024-05-28', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.49}, {'date': '2024-05-29', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.46}, {'date': '2024-05-30', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.42}, {'date': '2024-05-31', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.38}, {'date': '2024-06-01', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.34}, {'date': '2024-06-02', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.31}, {'date': '2024-06-03', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.27}, {'date': '2024-06-04', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.23}, {'date': '2024-06-05', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.19}, {'date': '2024-06-06', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.16}, {'date': '2024-06-07', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.12}, {'date': '2024-06-08', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.08}, {'date': '2024-06-09', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.05}, {'date': '2024-06-10', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.01}, {'date': '2024-06-11', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.98}, {'date': '2024-06-12', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.94}, {'date': '2024-06-13', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.9}, {'date': '2024-06-14', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.87}, {'date': '2024-06-15', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.83}, {'date': '2024-06-16', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.8}, {'date': '2024-06-17', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.76}, {'date': '2024-06-18', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.22, 'volume': 10018.73}, {'date': '2024-06-19', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.69}, {'date': '2024-06-20', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.66}, {'date': '2024-06-21', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.62}, {'date': '2024-06-22', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.59}, {'date': '2024-06-23', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.55}, {'date': '2024-06-24', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.52}, {'date': '2024-06-25', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.48}, {'date': '2024-06-26', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.45}, {'date': '2024-06-27', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.42}, {'date': '2024-06-28', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.38}, {'date': '2024-06-29', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.35}, {'date': '2024-06-30', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.32}, {'date': '2024-07-01', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.28}, {'date': '2024-07-02', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.25}, {'date': '2024-07-03', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.21}, {'date': '2024-07-04', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.18}, {'date': '2024-07-05', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.15}, {'date': '2024-07-06', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.12}, {'date': '2024-07-07', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.08}, {'date': '2024-07-08', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.05}, {'date': '2024-07-09', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.02}, {'date': '2024-07-10', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.99}, {'date': '2024-07-11', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.95}, {'date': '2024-07-12', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.92}, {'date': '2024-07-13', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.89}, {'date': '2024-07-14', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.86}, {'date': '2024-07-15', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.83}, {'date': '2024-07-16', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.79}, {'date': '2024-07-17', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.76}, {'date': '2024-07-18', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.73}, {'date': '2024-07-19', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.7}, {'date': '2024-07-20', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.67}, {'date': '2024-07-21', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.64}, {'date': '2024-07-22', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.61}, {'date': '2024-07-23', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.57}, {'date': '2024-07-24', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.54}, {'date': '2024-07-25', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.51}, {'date': '2024-07-26', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.48}, {'date': '2024-07-27', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.45}, {'date': '2024-07-28', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.42}, {'date': '2024-07-29', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.39}, {'date': '2024-07-30', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.36}, {'date': '2024-07-31', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.33}, {'date': '2024-08-01', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.3}, {'date': '2024-08-02', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.27}, {'date': '2024-08-03', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.24}, {'date': '2024-08-04', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.21}, {'date': '2024-08-05', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.18}, {'date': '2024-08-06', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.15}, {'date': '2024-08-07', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.12}, {'date': '2024-08-08', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.09}, {'date': '2024-08-09', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.06}, {'date': '2024-08-10', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.04}, {'date': '2024-08-11', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.01}, {'date': '2024-08-12', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10016.98}, {'date': '2024-08-13', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.95}, {'date': '2024-08-14', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.92}, {'date': '2024-08-15', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.89}, {'date': '2024-08-16', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.86}, {'date': '2024-08-17', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.84}, {'date': '2024-08-18', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.81}, {'date': '2024-08-19', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.78}, {'date': '2024-08-20', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.75}, {'date': '2024-08-21', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.72}, {'date': '2024-08-22', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.69}, {'date': '2024-08-23', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.67}, {'date': '2024-08-24', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.64}, {'date': '2024-08-25', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.61}, {'date': '2024-08-26', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.58}, {'date': '2024-08-27', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.56}, {'date': '2024-08-28', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.53}, {'date': '2024-08-29', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.5}, {'date': '2024-08-30', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.47}, {'date': '2024-08-31', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.45}, {'date': '2024-09-01', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.42}, {'date': '2024-09-02', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.39}, {'date': '2024-09-03', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.37}, {'date': '2024-09-04', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.34}, {'date': '2024-09-05', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.31}, {'date': '2024-09-06', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.29}, {'date': '2024-09-07', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.26}, {'date': '2024-09-08', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.23}, {'date': '2024-09-09', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.21}, {'date': '2024-09-10', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.18}, {'date': '2024-09-11', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.16}, {'date': '2024-09-12', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.13}, {'date': '2024-09-13', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.1}, {'date': '2024-09-14', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.08}, {'date': '2024-09-15', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.05}, {'date': '2024-09-16', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.03}, {'date': '2024-09-17', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.0}, {'date': '2024-09-18', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.97}, {'date': '2024-09-19', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.95}, {'date': '2024-09-20', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.92}, {'date': '2024-09-21', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.9}, {'date': '2024-09-22', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.87}, {'date': '2024-09-23', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.85}, {'date': '2024-09-24', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.82}, {'date': '2024-09-25', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.8}, {'date': '2024-09-26', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.77}, {'date': '2024-09-27', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.75}, {'date': '2024-09-28', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.72}, {'date': '2024-09-29', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.7}, {'date': '2024-09-30', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.67}, {'date': '2024-10-01', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.65}, {'date': '2024-10-02', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.62}, {'date': '2024-10-03', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.6}, {'date': '2024-10-04', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.58}, {'date': '2024-10-05', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.55}, {'date': '2024-10-06', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.53}, {'date': '2024-10-07', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.5}, {'date': '2024-10-08', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.48}, {'date': '2024-10-09', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.46}, {'date': '2024-10-10', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.43}, {'date': '2024-10-11', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.41}, {'date': '2024-10-12', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.38}, {'date': '2024-10-13', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.36}, {'date': '2024-10-14', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.34}, {'date': '2024-10-15', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.31}, {'date': '2024-10-16', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.29}, {'date': '2024-10-17', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.27}, {'date': '2024-10-18', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.24}, {'date': '2024-10-19', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.22}, {'date': '2024-10-20', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.2}, {'date': '2024-10-21', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.17}, {'date': '2024-10-22', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.15}, {'date': '2024-10-23', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.13}, {'date': '2024-10-24', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.11}, {'date': '2024-10-25', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.08}, {'date': '2024-10-26', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.06}, {'date': '2024-10-27', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.04}, {'date': '2024-10-28', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.02}, {'date': '2024-10-29', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.99}, {'date': '2024-10-30', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.97}, {'date': '2024-10-31', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.95}, {'date': '2024-11-01', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.93}, {'date': '2024-11-02', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.9}, {'date': '2024-11-03', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.88}, {'date': '2024-11-04', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.86}, {'date': '2024-11-05', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.84}, {'date': '2024-11-06', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.81}, {'date': '2024-11-07', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.79}, {'date': '2024-11-08', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.77}, {'date': '2024-11-09', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.75}, {'date': '2024-11-10', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.73}, {'date': '2024-11-11', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.71}, {'date': '2024-11-12', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.68}, {'date': '2024-11-13', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.66}, {'date': '2024-11-14', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.64}, {'date': '2024-11-15', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.62}, {'date': '2024-11-16', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.6}, {'date': '2024-11-17', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.58}, {'date': '2024-11-18', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.56}, {'date': '2024-11-19', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.53}, {'date': '2024-11-20', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.51}, {'date': '2024-11-21', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.49}, {'date': '2024-11-22', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.47}, {'date': '2024-11-23', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.45}, {'date': '2024-11-24', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.43}, {'date': '2024-11-25', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.41}, {'date': '2024-11-26', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.39}, {'date': '2024-11-27', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.37}, {'date': '2024-11-28', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.35}, {'date': '2024-11-29', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.33}, {'date': '2024-11-30', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.31}, {'date': '2024-12-01', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.29}, {'date': '2024-12-02', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.27}, {'date': '2024-12-03', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.25}, {'date': '2024-12-04', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.22}, {'date': '2024-12-05', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.2}, {'date': '2024-12-06', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.18}, {'date': '2024-12-07', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.16}, {'date': '2024-12-08', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.14}, {'date': '2024-12-09', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.12}, {'date': '2024-12-10', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.1}, {'date': '2024-12-11', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.08}, {'date': '2024-12-12', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.06}, {'date': '2024-12-13', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.04}, {'date': '2024-12-14', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.03}, {'date': '2024-12-15', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.01}, {'date': '2024-12-16', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.99}, {'date': '2024-12-17', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.97}, {'date': '2024-12-18', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.95}, {'date': '2024-12-19', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.93}, {'date': '2024-12-20', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.91}, {'date': '2024-12-21', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.89}, {'date': '2024-12-22', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.87}, {'date': '2024-12-23', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.85}, {'date': '2024-12-24', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.83}, {'date': '2024-12-25', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.81}, {'date': '2024-12-26', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.79}, {'date': '2024-12-27', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.77}, {'date': '2024-12-28', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.76}, {'date': '2024-12-29', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.74}, {'date': '2024-12-30', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.72}, {'date': '2024-12-31', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.7}, {'date': '2025-01-01', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.68}, {'date': '2025-01-02', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.66}, {'date': '2025-01-03', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.64}, {'date': '2025-01-04', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.62}, {'date': '2025-01-05', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.61}, {'date': '2025-01-06', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.59}, {'date': '2025-01-07', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.57}, {'date': '2025-01-08', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.55}, {'date': '2025-01-09', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.53}, {'date': '2025-01-10', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.51}, {'date': '2025-01-11', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.16, 'volume': 10013.5}, {'date': '2025-01-12', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.48}, {'date': '2025-01-13', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.46}, {'date': '2025-01-14', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.44}, {'date': '2025-01-15', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.42}, {'date': '2025-01-16', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.4}, {'date': '2025-01-17', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.39}, {'date': '2025-01-18', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.37}, {'date': '2025-01-19', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.35}, {'date': '2025-01-20', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.33}, {'date': '2025-01-21', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.32}, {'date': '2025-01-22', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.3}, {'date': '2025-01-23', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.28}, {'date': '2025-01-24', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.26}, {'date': '2025-01-25', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.25}, {'date': '2025-01-26', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.23}, {'date': '2025-01-27', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.21}, {'date': '2025-01-28', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.19}, {'date': '2025-01-29', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.18}, {'date': '2025-01-30', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.16}, {'date': '2025-01-31', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.14}, {'date': '2025-02-01', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.12}, {'date': '2025-02-02', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.11}, {'date': '2025-02-03', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.09}, {'date': '2025-02-04', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.07}, {'date': '2025-02-05', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.05}, {'date': '2025-02-06', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.04}, {'date': '2025-02-07', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.02}, {'date': '2025-02-08', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.0}, {'date': '2025-02-09', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.99}, {'date': '2025-02-10', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.97}, {'date': '2025-02-11', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.95}, {'date': '2025-02-12', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.94}, {'date': '2025-02-13', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.92}, {'date': '2025-02-14', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.9}, {'date': '2025-02-15', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.89}, {'date': '2025-02-16', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.87}, {'date': '2025-02-17', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.85}, {'date': '2025-02-18', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.84}, {'date': '2025-02-19', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.82}, {'date': '2025-02-20', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.8}, {'date': '2025-02-21', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.79}, {'date': '2025-02-22', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.77}, {'date': '2025-02-23', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.76}, {'date': '2025-02-24', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.74}, {'date': '2025-02-25', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.72}, {'date': '2025-02-26', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.71}, {'date': '2025-02-27', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.69}, {'date': '2025-02-28', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.67}, {'date': '2025-03-01', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.66}, {'date': '2025-03-02', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.64}, {'date': '2025-03-03', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.63}, {'date': '2025-03-04', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.61}, {'date': '2025-03-05', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.59}, {'date': '2025-03-06', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.58}, {'date': '2025-03-07', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.56}, {'date': '2025-03-08', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.55}, {'date': '2025-03-09', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.53}, {'date': '2025-03-10', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.52}, {'date': '2025-03-11', 'open': 110.14, 'high': 120.15, 'low': 100.12, 'close': 115.14, 'volume': 10012.5}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Target time series (index column plus the target column).", + "default": null, + "optional": false + }, + { + "name": "factors_data", + "type": "list[Data]", + "description": "Factor matrix (index column plus one column per factor).", + "default": null, + "optional": false + }, + { + "name": "periods", + "type": "list[str]", + "description": "Named look-back periods to regress over, anchored to the latest factor observation. Supported values: '1 Month', '3 Month', 'YTD', '1 Year', '3 Year', 'Max'.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column in `data` to regress on the factor matrix.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column shared by `data` and `factors_data`.", + "default": "date", + "optional": true + }, + { + "name": "risk_free_column", + "type": "str", + "description": "Optional name of the risk-free rate column in `factors_data`. When provided, the target is regressed in excess form (target minus the risk-free column) and the column itself is dropped from the regressors.", + "default": null, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[FactorRegressionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "period", + "type": "str", + "description": "Look-back period the regression was run over.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "factor", + "type": "str", + "description": "Factor name (or 'const' for the regression intercept).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "OLS coefficient estimate for the factor.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "p_value", + "type": "float", + "description": "Two-sided p-value of the coefficient estimate.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "lower_ci", + "type": "float", + "description": "Lower bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "upper_ci", + "type": "float", + "description": "Upper bound of the 95% confidence interval.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "r_squared", + "type": "float", + "description": "R-squared of the regression for this period (repeated per row).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/risk_decomposition": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Decompose Var(target) into per-factor contributions plus residual variance.\n\nFor each look-back period, runs OLS of the target on the factor matrix and\nsplits the target's variance via the additive identity\n`Var(target) = sum_i beta_i * Cov(factor_i, target) + Var(residual)`.\nReturned shares sum to 1 per period: factor shares sum to R^2 and the residual\nrow holds `1 - R^2`. Handles correlated factors cleanly because each share is\nmeasured by the covariance with the target rather than by the factor's\nstandalone variance.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.risk_decomposition(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}, {'date': '2023-03-03', 'open': 111.8, 'high': 121.97, 'low': 101.64, 'close': 116.89, 'volume': 10163.93}, {'date': '2023-03-04', 'open': 111.77, 'high': 121.94, 'low': 101.61, 'close': 116.85, 'volume': 10161.29}, {'date': '2023-03-05', 'open': 111.75, 'high': 121.9, 'low': 101.59, 'close': 116.83, 'volume': 10158.73}, {'date': '2023-03-06', 'open': 111.72, 'high': 121.88, 'low': 101.56, 'close': 116.8, 'volume': 10156.25}, {'date': '2023-03-07', 'open': 111.69, 'high': 121.85, 'low': 101.54, 'close': 116.77, 'volume': 10153.85}, {'date': '2023-03-08', 'open': 111.67, 'high': 121.82, 'low': 101.52, 'close': 116.74, 'volume': 10151.52}, {'date': '2023-03-09', 'open': 111.64, 'high': 121.79, 'low': 101.49, 'close': 116.72, 'volume': 10149.25}, {'date': '2023-03-10', 'open': 111.62, 'high': 121.76, 'low': 101.47, 'close': 116.69, 'volume': 10147.06}, {'date': '2023-03-11', 'open': 111.59, 'high': 121.74, 'low': 101.45, 'close': 116.67, 'volume': 10144.93}, {'date': '2023-03-12', 'open': 111.57, 'high': 121.71, 'low': 101.43, 'close': 116.64, 'volume': 10142.86}, {'date': '2023-03-13', 'open': 111.55, 'high': 121.69, 'low': 101.41, 'close': 116.62, 'volume': 10140.85}, {'date': '2023-03-14', 'open': 111.53, 'high': 121.67, 'low': 101.39, 'close': 116.6, 'volume': 10138.89}, {'date': '2023-03-15', 'open': 111.51, 'high': 121.64, 'low': 101.37, 'close': 116.58, 'volume': 10136.99}, {'date': '2023-03-16', 'open': 111.49, 'high': 121.62, 'low': 101.35, 'close': 116.55, 'volume': 10135.14}, {'date': '2023-03-17', 'open': 111.47, 'high': 121.6, 'low': 101.33, 'close': 116.53, 'volume': 10133.33}, {'date': '2023-03-18', 'open': 111.45, 'high': 121.58, 'low': 101.32, 'close': 116.51, 'volume': 10131.58}, {'date': '2023-03-19', 'open': 111.43, 'high': 121.56, 'low': 101.3, 'close': 116.49, 'volume': 10129.87}, {'date': '2023-03-20', 'open': 111.41, 'high': 121.54, 'low': 101.28, 'close': 116.47, 'volume': 10128.21}, {'date': '2023-03-21', 'open': 111.39, 'high': 121.52, 'low': 101.27, 'close': 116.46, 'volume': 10126.58}, {'date': '2023-03-22', 'open': 111.38, 'high': 121.5, 'low': 101.25, 'close': 116.44, 'volume': 10125.0}, {'date': '2023-03-23', 'open': 111.36, 'high': 121.48, 'low': 101.23, 'close': 116.42, 'volume': 10123.46}, {'date': '2023-03-24', 'open': 111.34, 'high': 121.46, 'low': 101.22, 'close': 116.4, 'volume': 10121.95}, {'date': '2023-03-25', 'open': 111.33, 'high': 121.45, 'low': 101.2, 'close': 116.39, 'volume': 10120.48}, {'date': '2023-03-26', 'open': 111.31, 'high': 121.43, 'low': 101.19, 'close': 116.37, 'volume': 10119.05}, {'date': '2023-03-27', 'open': 111.29, 'high': 121.41, 'low': 101.18, 'close': 116.35, 'volume': 10117.65}, {'date': '2023-03-28', 'open': 111.28, 'high': 121.4, 'low': 101.16, 'close': 116.34, 'volume': 10116.28}, {'date': '2023-03-29', 'open': 111.26, 'high': 121.38, 'low': 101.15, 'close': 116.32, 'volume': 10114.94}, {'date': '2023-03-30', 'open': 111.25, 'high': 121.36, 'low': 101.14, 'close': 116.31, 'volume': 10113.64}, {'date': '2023-03-31', 'open': 111.24, 'high': 121.35, 'low': 101.12, 'close': 116.29, 'volume': 10112.36}, {'date': '2023-04-01', 'open': 111.22, 'high': 121.33, 'low': 101.11, 'close': 116.28, 'volume': 10111.11}, {'date': '2023-04-02', 'open': 111.21, 'high': 121.32, 'low': 101.1, 'close': 116.26, 'volume': 10109.89}, {'date': '2023-04-03', 'open': 111.2, 'high': 121.3, 'low': 101.09, 'close': 116.25, 'volume': 10108.7}, {'date': '2023-04-04', 'open': 111.18, 'high': 121.29, 'low': 101.08, 'close': 116.24, 'volume': 10107.53}, {'date': '2023-04-05', 'open': 111.17, 'high': 121.28, 'low': 101.06, 'close': 116.22, 'volume': 10106.38}, {'date': '2023-04-06', 'open': 111.16, 'high': 121.26, 'low': 101.05, 'close': 116.21, 'volume': 10105.26}, {'date': '2023-04-07', 'open': 111.15, 'high': 121.25, 'low': 101.04, 'close': 116.2, 'volume': 10104.17}, {'date': '2023-04-08', 'open': 111.13, 'high': 121.24, 'low': 101.03, 'close': 116.19, 'volume': 10103.09}, {'date': '2023-04-09', 'open': 111.12, 'high': 121.22, 'low': 101.02, 'close': 116.17, 'volume': 10102.04}, {'date': '2023-04-10', 'open': 111.11, 'high': 121.21, 'low': 101.01, 'close': 116.16, 'volume': 10101.01}, {'date': '2023-04-11', 'open': 111.1, 'high': 121.2, 'low': 101.0, 'close': 116.15, 'volume': 10100.0}, {'date': '2023-04-12', 'open': 111.09, 'high': 121.19, 'low': 100.99, 'close': 116.14, 'volume': 10099.01}, {'date': '2023-04-13', 'open': 111.08, 'high': 121.18, 'low': 100.98, 'close': 116.13, 'volume': 10098.04}, {'date': '2023-04-14', 'open': 111.07, 'high': 121.17, 'low': 100.97, 'close': 116.12, 'volume': 10097.09}, {'date': '2023-04-15', 'open': 111.06, 'high': 121.15, 'low': 100.96, 'close': 116.11, 'volume': 10096.15}, {'date': '2023-04-16', 'open': 111.05, 'high': 121.14, 'low': 100.95, 'close': 116.1, 'volume': 10095.24}, {'date': '2023-04-17', 'open': 111.04, 'high': 121.13, 'low': 100.94, 'close': 116.08, 'volume': 10094.34}, {'date': '2023-04-18', 'open': 111.03, 'high': 121.12, 'low': 100.93, 'close': 116.07, 'volume': 10093.46}, {'date': '2023-04-19', 'open': 111.02, 'high': 121.11, 'low': 100.93, 'close': 116.06, 'volume': 10092.59}, {'date': '2023-04-20', 'open': 111.01, 'high': 121.1, 'low': 100.92, 'close': 116.06, 'volume': 10091.74}, {'date': '2023-04-21', 'open': 111.0, 'high': 121.09, 'low': 100.91, 'close': 116.05, 'volume': 10090.91}, {'date': '2023-04-22', 'open': 110.99, 'high': 121.08, 'low': 100.9, 'close': 116.04, 'volume': 10090.09}, {'date': '2023-04-23', 'open': 110.98, 'high': 121.07, 'low': 100.89, 'close': 116.03, 'volume': 10089.29}, {'date': '2023-04-24', 'open': 110.97, 'high': 121.06, 'low': 100.88, 'close': 116.02, 'volume': 10088.5}, {'date': '2023-04-25', 'open': 110.96, 'high': 121.05, 'low': 100.88, 'close': 116.01, 'volume': 10087.72}, {'date': '2023-04-26', 'open': 110.96, 'high': 121.04, 'low': 100.87, 'close': 116.0, 'volume': 10086.96}, {'date': '2023-04-27', 'open': 110.95, 'high': 121.03, 'low': 100.86, 'close': 115.99, 'volume': 10086.21}, {'date': '2023-04-28', 'open': 110.94, 'high': 121.03, 'low': 100.85, 'close': 115.98, 'volume': 10085.47}, {'date': '2023-04-29', 'open': 110.93, 'high': 121.02, 'low': 100.85, 'close': 115.97, 'volume': 10084.75}, {'date': '2023-04-30', 'open': 110.92, 'high': 121.01, 'low': 100.84, 'close': 115.97, 'volume': 10084.03}, {'date': '2023-05-01', 'open': 110.92, 'high': 121.0, 'low': 100.83, 'close': 115.96, 'volume': 10083.33}, {'date': '2023-05-02', 'open': 110.91, 'high': 120.99, 'low': 100.83, 'close': 115.95, 'volume': 10082.64}, {'date': '2023-05-03', 'open': 110.9, 'high': 120.98, 'low': 100.82, 'close': 115.94, 'volume': 10081.97}, {'date': '2023-05-04', 'open': 110.89, 'high': 120.98, 'low': 100.81, 'close': 115.93, 'volume': 10081.3}, {'date': '2023-05-05', 'open': 110.89, 'high': 120.97, 'low': 100.81, 'close': 115.93, 'volume': 10080.65}, {'date': '2023-05-06', 'open': 110.88, 'high': 120.96, 'low': 100.8, 'close': 115.92, 'volume': 10080.0}, {'date': '2023-05-07', 'open': 110.87, 'high': 120.95, 'low': 100.79, 'close': 115.91, 'volume': 10079.37}, {'date': '2023-05-08', 'open': 110.87, 'high': 120.94, 'low': 100.79, 'close': 115.91, 'volume': 10078.74}, {'date': '2023-05-09', 'open': 110.86, 'high': 120.94, 'low': 100.78, 'close': 115.9, 'volume': 10078.12}, {'date': '2023-05-10', 'open': 110.85, 'high': 120.93, 'low': 100.78, 'close': 115.89, 'volume': 10077.52}, {'date': '2023-05-11', 'open': 110.85, 'high': 120.92, 'low': 100.77, 'close': 115.88, 'volume': 10076.92}, {'date': '2023-05-12', 'open': 110.84, 'high': 120.92, 'low': 100.76, 'close': 115.88, 'volume': 10076.34}, {'date': '2023-05-13', 'open': 110.83, 'high': 120.91, 'low': 100.76, 'close': 115.87, 'volume': 10075.76}, {'date': '2023-05-14', 'open': 110.83, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10075.19}, {'date': '2023-05-15', 'open': 110.82, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10074.63}, {'date': '2023-05-16', 'open': 110.81, 'high': 120.89, 'low': 100.74, 'close': 115.85, 'volume': 10074.07}, {'date': '2023-05-17', 'open': 110.81, 'high': 120.88, 'low': 100.74, 'close': 115.85, 'volume': 10073.53}, {'date': '2023-05-18', 'open': 110.8, 'high': 120.88, 'low': 100.73, 'close': 115.84, 'volume': 10072.99}, {'date': '2023-05-19', 'open': 110.8, 'high': 120.87, 'low': 100.72, 'close': 115.83, 'volume': 10072.46}, {'date': '2023-05-20', 'open': 110.79, 'high': 120.86, 'low': 100.72, 'close': 115.83, 'volume': 10071.94}, {'date': '2023-05-21', 'open': 110.79, 'high': 120.86, 'low': 100.71, 'close': 115.82, 'volume': 10071.43}, {'date': '2023-05-22', 'open': 110.78, 'high': 120.85, 'low': 100.71, 'close': 115.82, 'volume': 10070.92}, {'date': '2023-05-23', 'open': 110.77, 'high': 120.85, 'low': 100.7, 'close': 115.81, 'volume': 10070.42}, {'date': '2023-05-24', 'open': 110.77, 'high': 120.84, 'low': 100.7, 'close': 115.8, 'volume': 10069.93}, {'date': '2023-05-25', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.8, 'volume': 10069.44}, {'date': '2023-05-26', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.79, 'volume': 10068.97}, {'date': '2023-05-27', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.79, 'volume': 10068.49}, {'date': '2023-05-28', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.78, 'volume': 10068.03}, {'date': '2023-05-29', 'open': 110.74, 'high': 120.81, 'low': 100.68, 'close': 115.78, 'volume': 10067.57}, {'date': '2023-05-30', 'open': 110.74, 'high': 120.81, 'low': 100.67, 'close': 115.77, 'volume': 10067.11}, {'date': '2023-05-31', 'open': 110.73, 'high': 120.8, 'low': 100.67, 'close': 115.77, 'volume': 10066.67}, {'date': '2023-06-01', 'open': 110.73, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10066.23}, {'date': '2023-06-02', 'open': 110.72, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10065.79}, {'date': '2023-06-03', 'open': 110.72, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10065.36}, {'date': '2023-06-04', 'open': 110.71, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10064.94}, {'date': '2023-06-05', 'open': 110.71, 'high': 120.77, 'low': 100.65, 'close': 115.74, 'volume': 10064.52}, {'date': '2023-06-06', 'open': 110.71, 'high': 120.77, 'low': 100.64, 'close': 115.74, 'volume': 10064.1}, {'date': '2023-06-07', 'open': 110.7, 'high': 120.76, 'low': 100.64, 'close': 115.73, 'volume': 10063.69}, {'date': '2023-06-08', 'open': 110.7, 'high': 120.76, 'low': 100.63, 'close': 115.73, 'volume': 10063.29}, {'date': '2023-06-09', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.89}, {'date': '2023-06-10', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.5}, {'date': '2023-06-11', 'open': 110.68, 'high': 120.75, 'low': 100.62, 'close': 115.71, 'volume': 10062.11}, {'date': '2023-06-12', 'open': 110.68, 'high': 120.74, 'low': 100.62, 'close': 115.71, 'volume': 10061.73}, {'date': '2023-06-13', 'open': 110.67, 'high': 120.74, 'low': 100.61, 'close': 115.71, 'volume': 10061.35}, {'date': '2023-06-14', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.98}, {'date': '2023-06-15', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.61}, {'date': '2023-06-16', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10060.24}, {'date': '2023-06-17', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10059.88}, {'date': '2023-06-18', 'open': 110.65, 'high': 120.71, 'low': 100.6, 'close': 115.68, 'volume': 10059.52}, {'date': '2023-06-19', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10059.17}, {'date': '2023-06-20', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10058.82}, {'date': '2023-06-21', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.48}, {'date': '2023-06-22', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.14}, {'date': '2023-06-23', 'open': 110.64, 'high': 120.69, 'low': 100.58, 'close': 115.66, 'volume': 10057.8}, {'date': '2023-06-24', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.47}, {'date': '2023-06-25', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.14}, {'date': '2023-06-26', 'open': 110.62, 'high': 120.68, 'low': 100.57, 'close': 115.65, 'volume': 10056.82}, {'date': '2023-06-27', 'open': 110.62, 'high': 120.68, 'low': 100.56, 'close': 115.65, 'volume': 10056.5}, {'date': '2023-06-28', 'open': 110.62, 'high': 120.67, 'low': 100.56, 'close': 115.65, 'volume': 10056.18}, {'date': '2023-06-29', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.87}, {'date': '2023-06-30', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.56}, {'date': '2023-07-01', 'open': 110.61, 'high': 120.66, 'low': 100.55, 'close': 115.64, 'volume': 10055.25}, {'date': '2023-07-02', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.95}, {'date': '2023-07-03', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.64}, {'date': '2023-07-04', 'open': 110.6, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.35}, {'date': '2023-07-05', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.05}, {'date': '2023-07-06', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10053.76}, {'date': '2023-07-07', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.48}, {'date': '2023-07-08', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.19}, {'date': '2023-07-09', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.91}, {'date': '2023-07-10', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.63}, {'date': '2023-07-11', 'open': 110.58, 'high': 120.63, 'low': 100.52, 'close': 115.6, 'volume': 10052.36}, {'date': '2023-07-12', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10052.08}, {'date': '2023-07-13', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10051.81}, {'date': '2023-07-14', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.59, 'volume': 10051.55}, {'date': '2023-07-15', 'open': 110.56, 'high': 120.62, 'low': 100.51, 'close': 115.59, 'volume': 10051.28}, {'date': '2023-07-16', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.59, 'volume': 10051.02}, {'date': '2023-07-17', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.76}, {'date': '2023-07-18', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.51}, {'date': '2023-07-19', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.58, 'volume': 10050.25}, {'date': '2023-07-20', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10050.0}, {'date': '2023-07-21', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10049.75}, {'date': '2023-07-22', 'open': 110.54, 'high': 120.59, 'low': 100.5, 'close': 115.57, 'volume': 10049.5}, {'date': '2023-07-23', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.57, 'volume': 10049.26}, {'date': '2023-07-24', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10049.02}, {'date': '2023-07-25', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10048.78}, {'date': '2023-07-26', 'open': 110.53, 'high': 120.58, 'low': 100.49, 'close': 115.56, 'volume': 10048.54}, {'date': '2023-07-27', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.56, 'volume': 10048.31}, {'date': '2023-07-28', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.55, 'volume': 10048.08}, {'date': '2023-07-29', 'open': 110.53, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.85}, {'date': '2023-07-30', 'open': 110.52, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.62}, {'date': '2023-07-31', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.55, 'volume': 10047.39}, {'date': '2023-08-01', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.54, 'volume': 10047.17}, {'date': '2023-08-02', 'open': 110.52, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.95}, {'date': '2023-08-03', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.73}, {'date': '2023-08-04', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.53, 'volume': 10046.51}, {'date': '2023-08-05', 'open': 110.51, 'high': 120.56, 'low': 100.46, 'close': 115.53, 'volume': 10046.3}, {'date': '2023-08-06', 'open': 110.51, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10046.08}, {'date': '2023-08-07', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.87}, {'date': '2023-08-08', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.66}, {'date': '2023-08-09', 'open': 110.5, 'high': 120.55, 'low': 100.45, 'close': 115.52, 'volume': 10045.45}, {'date': '2023-08-10', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.25}, {'date': '2023-08-11', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.05}, {'date': '2023-08-12', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10044.84}, {'date': '2023-08-13', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.51, 'volume': 10044.64}, {'date': '2023-08-14', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.44}, {'date': '2023-08-15', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.25}, {'date': '2023-08-16', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.05}, {'date': '2023-08-17', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.5, 'volume': 10043.86}, {'date': '2023-08-18', 'open': 110.48, 'high': 120.52, 'low': 100.44, 'close': 115.5, 'volume': 10043.67}, {'date': '2023-08-19', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.48}, {'date': '2023-08-20', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.29}, {'date': '2023-08-21', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.1}, {'date': '2023-08-22', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.49, 'volume': 10042.92}, {'date': '2023-08-23', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.74}, {'date': '2023-08-24', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.55}, {'date': '2023-08-25', 'open': 110.47, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.37}, {'date': '2023-08-26', 'open': 110.46, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.19}, {'date': '2023-08-27', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10042.02}, {'date': '2023-08-28', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.84}, {'date': '2023-08-29', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.67}, {'date': '2023-08-30', 'open': 110.46, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.49}, {'date': '2023-08-31', 'open': 110.45, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.32}, {'date': '2023-09-01', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10041.15}, {'date': '2023-09-02', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.98}, {'date': '2023-09-03', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.82}, {'date': '2023-09-04', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.65}, {'date': '2023-09-05', 'open': 110.45, 'high': 120.49, 'low': 100.4, 'close': 115.47, 'volume': 10040.49}, {'date': '2023-09-06', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.32}, {'date': '2023-09-07', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.16}, {'date': '2023-09-08', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.0}, {'date': '2023-09-09', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.84}, {'date': '2023-09-10', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.68}, {'date': '2023-09-11', 'open': 110.43, 'high': 120.47, 'low': 100.4, 'close': 115.45, 'volume': 10039.53}, {'date': '2023-09-12', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.37}, {'date': '2023-09-13', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.22}, {'date': '2023-09-14', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.06}, {'date': '2023-09-15', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.91}, {'date': '2023-09-16', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.76}, {'date': '2023-09-17', 'open': 110.42, 'high': 120.46, 'low': 100.39, 'close': 115.44, 'volume': 10038.61}, {'date': '2023-09-18', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.46}, {'date': '2023-09-19', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.31}, {'date': '2023-09-20', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.17}, {'date': '2023-09-21', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.02}, {'date': '2023-09-22', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.44, 'volume': 10037.88}, {'date': '2023-09-23', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.74}, {'date': '2023-09-24', 'open': 110.41, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.59}, {'date': '2023-09-25', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.45}, {'date': '2023-09-26', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.31}, {'date': '2023-09-27', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.17}, {'date': '2023-09-28', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.43, 'volume': 10037.04}, {'date': '2023-09-29', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.9}, {'date': '2023-09-30', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.76}, {'date': '2023-10-01', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.63}, {'date': '2023-10-02', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.5}, {'date': '2023-10-03', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.36}, {'date': '2023-10-04', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.23}, {'date': '2023-10-05', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.1}, {'date': '2023-10-06', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.97}, {'date': '2023-10-07', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.84}, {'date': '2023-10-08', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.71}, {'date': '2023-10-09', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.59}, {'date': '2023-10-10', 'open': 110.39, 'high': 120.43, 'low': 100.35, 'close': 115.41, 'volume': 10035.46}, {'date': '2023-10-11', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.41, 'volume': 10035.34}, {'date': '2023-10-12', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.21}, {'date': '2023-10-13', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.09}, {'date': '2023-10-14', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.97}, {'date': '2023-10-15', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.84}, {'date': '2023-10-16', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.72}, {'date': '2023-10-17', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.6}, {'date': '2023-10-18', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.48}, {'date': '2023-10-19', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.36}, {'date': '2023-10-20', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.25}, {'date': '2023-10-21', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.13}, {'date': '2023-10-22', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.01}, {'date': '2023-10-23', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.9}, {'date': '2023-10-24', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.78}, {'date': '2023-10-25', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.67}, {'date': '2023-10-26', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.56}, {'date': '2023-10-27', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.44}, {'date': '2023-10-28', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.33}, {'date': '2023-10-29', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.22}, {'date': '2023-10-30', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.11}, {'date': '2023-10-31', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.0}, {'date': '2023-11-01', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.89}, {'date': '2023-11-02', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.79}, {'date': '2023-11-03', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.68}, {'date': '2023-11-04', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.37, 'volume': 10032.57}, {'date': '2023-11-05', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.47}, {'date': '2023-11-06', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.36}, {'date': '2023-11-07', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.26}, {'date': '2023-11-08', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.15}, {'date': '2023-11-09', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10032.05}, {'date': '2023-11-10', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.95}, {'date': '2023-11-11', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.85}, {'date': '2023-11-12', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.75}, {'date': '2023-11-13', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.65}, {'date': '2023-11-14', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.55}, {'date': '2023-11-15', 'open': 110.35, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.45}, {'date': '2023-11-16', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.35}, {'date': '2023-11-17', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.25}, {'date': '2023-11-18', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.15}, {'date': '2023-11-19', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.06}, {'date': '2023-11-20', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10030.96}, {'date': '2023-11-21', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.86}, {'date': '2023-11-22', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.77}, {'date': '2023-11-23', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.67}, {'date': '2023-11-24', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.58}, {'date': '2023-11-25', 'open': 110.34, 'high': 120.37, 'low': 100.3, 'close': 115.35, 'volume': 10030.49}, {'date': '2023-11-26', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.4}, {'date': '2023-11-27', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.3}, {'date': '2023-11-28', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.21}, {'date': '2023-11-29', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.12}, {'date': '2023-11-30', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.03}, {'date': '2023-12-01', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.94}, {'date': '2023-12-02', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.85}, {'date': '2023-12-03', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.76}, {'date': '2023-12-04', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.67}, {'date': '2023-12-05', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.59}, {'date': '2023-12-06', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.5}, {'date': '2023-12-07', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.41}, {'date': '2023-12-08', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.33}, {'date': '2023-12-09', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.24}, {'date': '2023-12-10', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.15}, {'date': '2023-12-11', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10029.07}, {'date': '2023-12-12', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.99}, {'date': '2023-12-13', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.9}, {'date': '2023-12-14', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.82}, {'date': '2023-12-15', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.74}, {'date': '2023-12-16', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.65}, {'date': '2023-12-17', 'open': 110.31, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.57}, {'date': '2023-12-18', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.49}, {'date': '2023-12-19', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.41}, {'date': '2023-12-20', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.33}, {'date': '2023-12-21', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.25}, {'date': '2023-12-22', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.17}, {'date': '2023-12-23', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.09}, {'date': '2023-12-24', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.01}, {'date': '2023-12-25', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10027.93}, {'date': '2023-12-26', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.86}, {'date': '2023-12-27', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.78}, {'date': '2023-12-28', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.7}, {'date': '2023-12-29', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.62}, {'date': '2023-12-30', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.55}, {'date': '2023-12-31', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.47}, {'date': '2024-01-01', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.4}, {'date': '2024-01-02', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.32}, {'date': '2024-01-03', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.25}, {'date': '2024-01-04', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.17}, {'date': '2024-01-05', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.1}, {'date': '2024-01-06', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10027.03}, {'date': '2024-01-07', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.95}, {'date': '2024-01-08', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.88}, {'date': '2024-01-09', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.81}, {'date': '2024-01-10', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.74}, {'date': '2024-01-11', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.67}, {'date': '2024-01-12', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.6}, {'date': '2024-01-13', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.53}, {'date': '2024-01-14', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.46}, {'date': '2024-01-15', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.39}, {'date': '2024-01-16', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.32}, {'date': '2024-01-17', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.25}, {'date': '2024-01-18', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.18}, {'date': '2024-01-19', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.11}, {'date': '2024-01-20', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.04}, {'date': '2024-01-21', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.97}, {'date': '2024-01-22', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.91}, {'date': '2024-01-23', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.84}, {'date': '2024-01-24', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.77}, {'date': '2024-01-25', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.71}, {'date': '2024-01-26', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.64}, {'date': '2024-01-27', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.58}, {'date': '2024-01-28', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.51}, {'date': '2024-01-29', 'open': 110.28, 'high': 120.31, 'low': 100.25, 'close': 115.29, 'volume': 10025.45}, {'date': '2024-01-30', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.38}, {'date': '2024-01-31', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.32}, {'date': '2024-02-01', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.25}, {'date': '2024-02-02', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.19}, {'date': '2024-02-03', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.13}, {'date': '2024-02-04', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.06}, {'date': '2024-02-05', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.0}, {'date': '2024-02-06', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.94}, {'date': '2024-02-07', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.88}, {'date': '2024-02-08', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.81}, {'date': '2024-02-09', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.75}, {'date': '2024-02-10', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.69}, {'date': '2024-02-11', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.63}, {'date': '2024-02-12', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.57}, {'date': '2024-02-13', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.51}, {'date': '2024-02-14', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.45}, {'date': '2024-02-15', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.39}, {'date': '2024-02-16', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.33}, {'date': '2024-02-17', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.27}, {'date': '2024-02-18', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.21}, {'date': '2024-02-19', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.15}, {'date': '2024-02-20', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.1}, {'date': '2024-02-21', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.04}, {'date': '2024-02-22', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.98}, {'date': '2024-02-23', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.92}, {'date': '2024-02-24', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.87}, {'date': '2024-02-25', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.81}, {'date': '2024-02-26', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.75}, {'date': '2024-02-27', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.7}, {'date': '2024-02-28', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.64}, {'date': '2024-02-29', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.58}, {'date': '2024-03-01', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.53}, {'date': '2024-03-02', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.47}, {'date': '2024-03-03', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.42}, {'date': '2024-03-04', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.36}, {'date': '2024-03-05', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.31}, {'date': '2024-03-06', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.26}, {'date': '2024-03-07', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.2}, {'date': '2024-03-08', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.15}, {'date': '2024-03-09', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.09}, {'date': '2024-03-10', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10023.04}, {'date': '2024-03-11', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.99}, {'date': '2024-03-12', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.94}, {'date': '2024-03-13', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.88}, {'date': '2024-03-14', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.83}, {'date': '2024-03-15', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.78}, {'date': '2024-03-16', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.73}, {'date': '2024-03-17', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.68}, {'date': '2024-03-18', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.62}, {'date': '2024-03-19', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.57}, {'date': '2024-03-20', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.52}, {'date': '2024-03-21', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.47}, {'date': '2024-03-22', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.42}, {'date': '2024-03-23', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.37}, {'date': '2024-03-24', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.32}, {'date': '2024-03-25', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.27}, {'date': '2024-03-26', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.22}, {'date': '2024-03-27', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.17}, {'date': '2024-03-28', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.12}, {'date': '2024-03-29', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.08}, {'date': '2024-03-30', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.03}, {'date': '2024-03-31', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.98}, {'date': '2024-04-01', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.93}, {'date': '2024-04-02', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.88}, {'date': '2024-04-03', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.83}, {'date': '2024-04-04', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.79}, {'date': '2024-04-05', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.74}, {'date': '2024-04-06', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.69}, {'date': '2024-04-07', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.65}, {'date': '2024-04-08', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.6}, {'date': '2024-04-09', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.55}, {'date': '2024-04-10', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.51}, {'date': '2024-04-11', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.46}, {'date': '2024-04-12', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.41}, {'date': '2024-04-13', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.37}, {'date': '2024-04-14', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.32}, {'date': '2024-04-15', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.24, 'volume': 10021.28}, {'date': '2024-04-16', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.23}, {'date': '2024-04-17', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.19}, {'date': '2024-04-18', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.14}, {'date': '2024-04-19', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.1}, {'date': '2024-04-20', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.05}, {'date': '2024-04-21', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.01}, {'date': '2024-04-22', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.96}, {'date': '2024-04-23', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.92}, {'date': '2024-04-24', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.88}, {'date': '2024-04-25', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.83}, {'date': '2024-04-26', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.79}, {'date': '2024-04-27', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.75}, {'date': '2024-04-28', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.7}, {'date': '2024-04-29', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.66}, {'date': '2024-04-30', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.62}, {'date': '2024-05-01', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.58}, {'date': '2024-05-02', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.53}, {'date': '2024-05-03', 'open': 110.23, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.49}, {'date': '2024-05-04', 'open': 110.22, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.45}, {'date': '2024-05-05', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.41}, {'date': '2024-05-06', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.37}, {'date': '2024-05-07', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.33}, {'date': '2024-05-08', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.28}, {'date': '2024-05-09', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.24}, {'date': '2024-05-10', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.2}, {'date': '2024-05-11', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.16}, {'date': '2024-05-12', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.12}, {'date': '2024-05-13', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.08}, {'date': '2024-05-14', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.04}, {'date': '2024-05-15', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.0}, {'date': '2024-05-16', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.96}, {'date': '2024-05-17', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.92}, {'date': '2024-05-18', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.88}, {'date': '2024-05-19', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.84}, {'date': '2024-05-20', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.8}, {'date': '2024-05-21', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.76}, {'date': '2024-05-22', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.72}, {'date': '2024-05-23', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.69}, {'date': '2024-05-24', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.65}, {'date': '2024-05-25', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.61}, {'date': '2024-05-26', 'open': 110.22, 'high': 120.23, 'low': 100.2, 'close': 115.23, 'volume': 10019.57}, {'date': '2024-05-27', 'open': 110.21, 'high': 120.23, 'low': 100.2, 'close': 115.22, 'volume': 10019.53}, {'date': '2024-05-28', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.49}, {'date': '2024-05-29', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.46}, {'date': '2024-05-30', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.42}, {'date': '2024-05-31', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.38}, {'date': '2024-06-01', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.34}, {'date': '2024-06-02', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.31}, {'date': '2024-06-03', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.27}, {'date': '2024-06-04', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.23}, {'date': '2024-06-05', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.19}, {'date': '2024-06-06', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.16}, {'date': '2024-06-07', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.12}, {'date': '2024-06-08', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.08}, {'date': '2024-06-09', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.05}, {'date': '2024-06-10', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.01}, {'date': '2024-06-11', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.98}, {'date': '2024-06-12', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.94}, {'date': '2024-06-13', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.9}, {'date': '2024-06-14', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.87}, {'date': '2024-06-15', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.83}, {'date': '2024-06-16', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.8}, {'date': '2024-06-17', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.76}, {'date': '2024-06-18', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.22, 'volume': 10018.73}, {'date': '2024-06-19', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.69}, {'date': '2024-06-20', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.66}, {'date': '2024-06-21', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.62}, {'date': '2024-06-22', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.59}, {'date': '2024-06-23', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.55}, {'date': '2024-06-24', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.52}, {'date': '2024-06-25', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.48}, {'date': '2024-06-26', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.45}, {'date': '2024-06-27', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.42}, {'date': '2024-06-28', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.38}, {'date': '2024-06-29', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.35}, {'date': '2024-06-30', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.32}, {'date': '2024-07-01', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.28}, {'date': '2024-07-02', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.25}, {'date': '2024-07-03', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.21}, {'date': '2024-07-04', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.18}, {'date': '2024-07-05', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.15}, {'date': '2024-07-06', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.12}, {'date': '2024-07-07', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.08}, {'date': '2024-07-08', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.05}, {'date': '2024-07-09', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.02}, {'date': '2024-07-10', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.99}, {'date': '2024-07-11', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.95}, {'date': '2024-07-12', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.92}, {'date': '2024-07-13', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.89}, {'date': '2024-07-14', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.86}, {'date': '2024-07-15', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.83}, {'date': '2024-07-16', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.79}, {'date': '2024-07-17', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.76}, {'date': '2024-07-18', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.73}, {'date': '2024-07-19', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.7}, {'date': '2024-07-20', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.67}, {'date': '2024-07-21', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.64}, {'date': '2024-07-22', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.61}, {'date': '2024-07-23', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.57}, {'date': '2024-07-24', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.54}, {'date': '2024-07-25', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.51}, {'date': '2024-07-26', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.48}, {'date': '2024-07-27', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.45}, {'date': '2024-07-28', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.42}, {'date': '2024-07-29', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.39}, {'date': '2024-07-30', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.36}, {'date': '2024-07-31', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.33}, {'date': '2024-08-01', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.3}, {'date': '2024-08-02', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.27}, {'date': '2024-08-03', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.24}, {'date': '2024-08-04', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.21}, {'date': '2024-08-05', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.18}, {'date': '2024-08-06', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.15}, {'date': '2024-08-07', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.12}, {'date': '2024-08-08', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.09}, {'date': '2024-08-09', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.06}, {'date': '2024-08-10', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.04}, {'date': '2024-08-11', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.01}, {'date': '2024-08-12', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10016.98}, {'date': '2024-08-13', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.95}, {'date': '2024-08-14', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.92}, {'date': '2024-08-15', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.89}, {'date': '2024-08-16', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.86}, {'date': '2024-08-17', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.84}, {'date': '2024-08-18', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.81}, {'date': '2024-08-19', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.78}, {'date': '2024-08-20', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.75}, {'date': '2024-08-21', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.72}, {'date': '2024-08-22', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.69}, {'date': '2024-08-23', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.67}, {'date': '2024-08-24', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.64}, {'date': '2024-08-25', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.61}, {'date': '2024-08-26', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.58}, {'date': '2024-08-27', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.56}, {'date': '2024-08-28', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.53}, {'date': '2024-08-29', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.5}, {'date': '2024-08-30', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.47}, {'date': '2024-08-31', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.45}, {'date': '2024-09-01', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.42}, {'date': '2024-09-02', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.39}, {'date': '2024-09-03', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.37}, {'date': '2024-09-04', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.34}, {'date': '2024-09-05', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.31}, {'date': '2024-09-06', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.29}, {'date': '2024-09-07', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.26}, {'date': '2024-09-08', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.23}, {'date': '2024-09-09', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.21}, {'date': '2024-09-10', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.18}, {'date': '2024-09-11', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.16}, {'date': '2024-09-12', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.13}, {'date': '2024-09-13', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.1}, {'date': '2024-09-14', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.08}, {'date': '2024-09-15', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.05}, {'date': '2024-09-16', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.03}, {'date': '2024-09-17', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.0}, {'date': '2024-09-18', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.97}, {'date': '2024-09-19', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.95}, {'date': '2024-09-20', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.92}, {'date': '2024-09-21', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.9}, {'date': '2024-09-22', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.87}, {'date': '2024-09-23', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.85}, {'date': '2024-09-24', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.82}, {'date': '2024-09-25', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.8}, {'date': '2024-09-26', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.77}, {'date': '2024-09-27', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.75}, {'date': '2024-09-28', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.72}, {'date': '2024-09-29', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.7}, {'date': '2024-09-30', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.67}, {'date': '2024-10-01', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.65}, {'date': '2024-10-02', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.62}, {'date': '2024-10-03', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.6}, {'date': '2024-10-04', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.58}, {'date': '2024-10-05', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.55}, {'date': '2024-10-06', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.53}, {'date': '2024-10-07', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.5}, {'date': '2024-10-08', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.48}, {'date': '2024-10-09', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.46}, {'date': '2024-10-10', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.43}, {'date': '2024-10-11', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.41}, {'date': '2024-10-12', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.38}, {'date': '2024-10-13', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.36}, {'date': '2024-10-14', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.34}, {'date': '2024-10-15', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.31}, {'date': '2024-10-16', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.29}, {'date': '2024-10-17', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.27}, {'date': '2024-10-18', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.24}, {'date': '2024-10-19', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.22}, {'date': '2024-10-20', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.2}, {'date': '2024-10-21', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.17}, {'date': '2024-10-22', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.15}, {'date': '2024-10-23', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.13}, {'date': '2024-10-24', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.11}, {'date': '2024-10-25', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.08}, {'date': '2024-10-26', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.06}, {'date': '2024-10-27', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.04}, {'date': '2024-10-28', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.02}, {'date': '2024-10-29', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.99}, {'date': '2024-10-30', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.97}, {'date': '2024-10-31', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.95}, {'date': '2024-11-01', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.93}, {'date': '2024-11-02', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.9}, {'date': '2024-11-03', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.88}, {'date': '2024-11-04', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.86}, {'date': '2024-11-05', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.84}, {'date': '2024-11-06', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.81}, {'date': '2024-11-07', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.79}, {'date': '2024-11-08', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.77}, {'date': '2024-11-09', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.75}, {'date': '2024-11-10', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.73}, {'date': '2024-11-11', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.71}, {'date': '2024-11-12', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.68}, {'date': '2024-11-13', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.66}, {'date': '2024-11-14', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.64}, {'date': '2024-11-15', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.62}, {'date': '2024-11-16', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.6}, {'date': '2024-11-17', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.58}, {'date': '2024-11-18', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.56}, {'date': '2024-11-19', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.53}, {'date': '2024-11-20', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.51}, {'date': '2024-11-21', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.49}, {'date': '2024-11-22', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.47}, {'date': '2024-11-23', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.45}, {'date': '2024-11-24', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.43}, {'date': '2024-11-25', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.41}, {'date': '2024-11-26', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.39}, {'date': '2024-11-27', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.37}, {'date': '2024-11-28', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.35}, {'date': '2024-11-29', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.33}, {'date': '2024-11-30', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.31}, {'date': '2024-12-01', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.29}, {'date': '2024-12-02', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.27}, {'date': '2024-12-03', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.25}, {'date': '2024-12-04', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.22}, {'date': '2024-12-05', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.2}, {'date': '2024-12-06', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.18}, {'date': '2024-12-07', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.16}, {'date': '2024-12-08', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.14}, {'date': '2024-12-09', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.12}, {'date': '2024-12-10', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.1}, {'date': '2024-12-11', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.08}, {'date': '2024-12-12', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.06}, {'date': '2024-12-13', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.04}, {'date': '2024-12-14', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.03}, {'date': '2024-12-15', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.01}, {'date': '2024-12-16', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.99}, {'date': '2024-12-17', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.97}, {'date': '2024-12-18', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.95}, {'date': '2024-12-19', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.93}, {'date': '2024-12-20', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.91}, {'date': '2024-12-21', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.89}, {'date': '2024-12-22', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.87}, {'date': '2024-12-23', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.85}, {'date': '2024-12-24', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.83}, {'date': '2024-12-25', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.81}, {'date': '2024-12-26', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.79}, {'date': '2024-12-27', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.77}, {'date': '2024-12-28', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.76}, {'date': '2024-12-29', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.74}, {'date': '2024-12-30', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.72}, {'date': '2024-12-31', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.7}, {'date': '2025-01-01', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.68}, {'date': '2025-01-02', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.66}, {'date': '2025-01-03', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.64}, {'date': '2025-01-04', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.62}, {'date': '2025-01-05', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.61}, {'date': '2025-01-06', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.59}, {'date': '2025-01-07', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.57}, {'date': '2025-01-08', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.55}, {'date': '2025-01-09', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.53}, {'date': '2025-01-10', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.51}, {'date': '2025-01-11', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.16, 'volume': 10013.5}, {'date': '2025-01-12', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.48}, {'date': '2025-01-13', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.46}, {'date': '2025-01-14', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.44}, {'date': '2025-01-15', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.42}, {'date': '2025-01-16', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.4}, {'date': '2025-01-17', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.39}, {'date': '2025-01-18', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.37}, {'date': '2025-01-19', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.35}, {'date': '2025-01-20', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.33}, {'date': '2025-01-21', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.32}, {'date': '2025-01-22', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.3}, {'date': '2025-01-23', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.28}, {'date': '2025-01-24', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.26}, {'date': '2025-01-25', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.25}, {'date': '2025-01-26', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.23}, {'date': '2025-01-27', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.21}, {'date': '2025-01-28', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.19}, {'date': '2025-01-29', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.18}, {'date': '2025-01-30', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.16}, {'date': '2025-01-31', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.14}, {'date': '2025-02-01', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.12}, {'date': '2025-02-02', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.11}, {'date': '2025-02-03', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.09}, {'date': '2025-02-04', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.07}, {'date': '2025-02-05', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.05}, {'date': '2025-02-06', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.04}, {'date': '2025-02-07', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.02}, {'date': '2025-02-08', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.0}, {'date': '2025-02-09', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.99}, {'date': '2025-02-10', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.97}, {'date': '2025-02-11', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.95}, {'date': '2025-02-12', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.94}, {'date': '2025-02-13', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.92}, {'date': '2025-02-14', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.9}, {'date': '2025-02-15', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.89}, {'date': '2025-02-16', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.87}, {'date': '2025-02-17', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.85}, {'date': '2025-02-18', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.84}, {'date': '2025-02-19', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.82}, {'date': '2025-02-20', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.8}, {'date': '2025-02-21', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.79}, {'date': '2025-02-22', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.77}, {'date': '2025-02-23', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.76}, {'date': '2025-02-24', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.74}, {'date': '2025-02-25', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.72}, {'date': '2025-02-26', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.71}, {'date': '2025-02-27', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.69}, {'date': '2025-02-28', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.67}, {'date': '2025-03-01', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.66}, {'date': '2025-03-02', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.64}, {'date': '2025-03-03', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.63}, {'date': '2025-03-04', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.61}, {'date': '2025-03-05', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.59}, {'date': '2025-03-06', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.58}, {'date': '2025-03-07', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.56}, {'date': '2025-03-08', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.55}, {'date': '2025-03-09', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.53}, {'date': '2025-03-10', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.52}, {'date': '2025-03-11', 'open': 110.14, 'high': 120.15, 'low': 100.12, 'close': 115.14, 'volume': 10012.5}], factors_data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}, {'date': '2023-03-03', 'open': 111.8, 'high': 121.97, 'low': 101.64, 'close': 116.89, 'volume': 10163.93}, {'date': '2023-03-04', 'open': 111.77, 'high': 121.94, 'low': 101.61, 'close': 116.85, 'volume': 10161.29}, {'date': '2023-03-05', 'open': 111.75, 'high': 121.9, 'low': 101.59, 'close': 116.83, 'volume': 10158.73}, {'date': '2023-03-06', 'open': 111.72, 'high': 121.88, 'low': 101.56, 'close': 116.8, 'volume': 10156.25}, {'date': '2023-03-07', 'open': 111.69, 'high': 121.85, 'low': 101.54, 'close': 116.77, 'volume': 10153.85}, {'date': '2023-03-08', 'open': 111.67, 'high': 121.82, 'low': 101.52, 'close': 116.74, 'volume': 10151.52}, {'date': '2023-03-09', 'open': 111.64, 'high': 121.79, 'low': 101.49, 'close': 116.72, 'volume': 10149.25}, {'date': '2023-03-10', 'open': 111.62, 'high': 121.76, 'low': 101.47, 'close': 116.69, 'volume': 10147.06}, {'date': '2023-03-11', 'open': 111.59, 'high': 121.74, 'low': 101.45, 'close': 116.67, 'volume': 10144.93}, {'date': '2023-03-12', 'open': 111.57, 'high': 121.71, 'low': 101.43, 'close': 116.64, 'volume': 10142.86}, {'date': '2023-03-13', 'open': 111.55, 'high': 121.69, 'low': 101.41, 'close': 116.62, 'volume': 10140.85}, {'date': '2023-03-14', 'open': 111.53, 'high': 121.67, 'low': 101.39, 'close': 116.6, 'volume': 10138.89}, {'date': '2023-03-15', 'open': 111.51, 'high': 121.64, 'low': 101.37, 'close': 116.58, 'volume': 10136.99}, {'date': '2023-03-16', 'open': 111.49, 'high': 121.62, 'low': 101.35, 'close': 116.55, 'volume': 10135.14}, {'date': '2023-03-17', 'open': 111.47, 'high': 121.6, 'low': 101.33, 'close': 116.53, 'volume': 10133.33}, {'date': '2023-03-18', 'open': 111.45, 'high': 121.58, 'low': 101.32, 'close': 116.51, 'volume': 10131.58}, {'date': '2023-03-19', 'open': 111.43, 'high': 121.56, 'low': 101.3, 'close': 116.49, 'volume': 10129.87}, {'date': '2023-03-20', 'open': 111.41, 'high': 121.54, 'low': 101.28, 'close': 116.47, 'volume': 10128.21}, {'date': '2023-03-21', 'open': 111.39, 'high': 121.52, 'low': 101.27, 'close': 116.46, 'volume': 10126.58}, {'date': '2023-03-22', 'open': 111.38, 'high': 121.5, 'low': 101.25, 'close': 116.44, 'volume': 10125.0}, {'date': '2023-03-23', 'open': 111.36, 'high': 121.48, 'low': 101.23, 'close': 116.42, 'volume': 10123.46}, {'date': '2023-03-24', 'open': 111.34, 'high': 121.46, 'low': 101.22, 'close': 116.4, 'volume': 10121.95}, {'date': '2023-03-25', 'open': 111.33, 'high': 121.45, 'low': 101.2, 'close': 116.39, 'volume': 10120.48}, {'date': '2023-03-26', 'open': 111.31, 'high': 121.43, 'low': 101.19, 'close': 116.37, 'volume': 10119.05}, {'date': '2023-03-27', 'open': 111.29, 'high': 121.41, 'low': 101.18, 'close': 116.35, 'volume': 10117.65}, {'date': '2023-03-28', 'open': 111.28, 'high': 121.4, 'low': 101.16, 'close': 116.34, 'volume': 10116.28}, {'date': '2023-03-29', 'open': 111.26, 'high': 121.38, 'low': 101.15, 'close': 116.32, 'volume': 10114.94}, {'date': '2023-03-30', 'open': 111.25, 'high': 121.36, 'low': 101.14, 'close': 116.31, 'volume': 10113.64}, {'date': '2023-03-31', 'open': 111.24, 'high': 121.35, 'low': 101.12, 'close': 116.29, 'volume': 10112.36}, {'date': '2023-04-01', 'open': 111.22, 'high': 121.33, 'low': 101.11, 'close': 116.28, 'volume': 10111.11}, {'date': '2023-04-02', 'open': 111.21, 'high': 121.32, 'low': 101.1, 'close': 116.26, 'volume': 10109.89}, {'date': '2023-04-03', 'open': 111.2, 'high': 121.3, 'low': 101.09, 'close': 116.25, 'volume': 10108.7}, {'date': '2023-04-04', 'open': 111.18, 'high': 121.29, 'low': 101.08, 'close': 116.24, 'volume': 10107.53}, {'date': '2023-04-05', 'open': 111.17, 'high': 121.28, 'low': 101.06, 'close': 116.22, 'volume': 10106.38}, {'date': '2023-04-06', 'open': 111.16, 'high': 121.26, 'low': 101.05, 'close': 116.21, 'volume': 10105.26}, {'date': '2023-04-07', 'open': 111.15, 'high': 121.25, 'low': 101.04, 'close': 116.2, 'volume': 10104.17}, {'date': '2023-04-08', 'open': 111.13, 'high': 121.24, 'low': 101.03, 'close': 116.19, 'volume': 10103.09}, {'date': '2023-04-09', 'open': 111.12, 'high': 121.22, 'low': 101.02, 'close': 116.17, 'volume': 10102.04}, {'date': '2023-04-10', 'open': 111.11, 'high': 121.21, 'low': 101.01, 'close': 116.16, 'volume': 10101.01}, {'date': '2023-04-11', 'open': 111.1, 'high': 121.2, 'low': 101.0, 'close': 116.15, 'volume': 10100.0}, {'date': '2023-04-12', 'open': 111.09, 'high': 121.19, 'low': 100.99, 'close': 116.14, 'volume': 10099.01}, {'date': '2023-04-13', 'open': 111.08, 'high': 121.18, 'low': 100.98, 'close': 116.13, 'volume': 10098.04}, {'date': '2023-04-14', 'open': 111.07, 'high': 121.17, 'low': 100.97, 'close': 116.12, 'volume': 10097.09}, {'date': '2023-04-15', 'open': 111.06, 'high': 121.15, 'low': 100.96, 'close': 116.11, 'volume': 10096.15}, {'date': '2023-04-16', 'open': 111.05, 'high': 121.14, 'low': 100.95, 'close': 116.1, 'volume': 10095.24}, {'date': '2023-04-17', 'open': 111.04, 'high': 121.13, 'low': 100.94, 'close': 116.08, 'volume': 10094.34}, {'date': '2023-04-18', 'open': 111.03, 'high': 121.12, 'low': 100.93, 'close': 116.07, 'volume': 10093.46}, {'date': '2023-04-19', 'open': 111.02, 'high': 121.11, 'low': 100.93, 'close': 116.06, 'volume': 10092.59}, {'date': '2023-04-20', 'open': 111.01, 'high': 121.1, 'low': 100.92, 'close': 116.06, 'volume': 10091.74}, {'date': '2023-04-21', 'open': 111.0, 'high': 121.09, 'low': 100.91, 'close': 116.05, 'volume': 10090.91}, {'date': '2023-04-22', 'open': 110.99, 'high': 121.08, 'low': 100.9, 'close': 116.04, 'volume': 10090.09}, {'date': '2023-04-23', 'open': 110.98, 'high': 121.07, 'low': 100.89, 'close': 116.03, 'volume': 10089.29}, {'date': '2023-04-24', 'open': 110.97, 'high': 121.06, 'low': 100.88, 'close': 116.02, 'volume': 10088.5}, {'date': '2023-04-25', 'open': 110.96, 'high': 121.05, 'low': 100.88, 'close': 116.01, 'volume': 10087.72}, {'date': '2023-04-26', 'open': 110.96, 'high': 121.04, 'low': 100.87, 'close': 116.0, 'volume': 10086.96}, {'date': '2023-04-27', 'open': 110.95, 'high': 121.03, 'low': 100.86, 'close': 115.99, 'volume': 10086.21}, {'date': '2023-04-28', 'open': 110.94, 'high': 121.03, 'low': 100.85, 'close': 115.98, 'volume': 10085.47}, {'date': '2023-04-29', 'open': 110.93, 'high': 121.02, 'low': 100.85, 'close': 115.97, 'volume': 10084.75}, {'date': '2023-04-30', 'open': 110.92, 'high': 121.01, 'low': 100.84, 'close': 115.97, 'volume': 10084.03}, {'date': '2023-05-01', 'open': 110.92, 'high': 121.0, 'low': 100.83, 'close': 115.96, 'volume': 10083.33}, {'date': '2023-05-02', 'open': 110.91, 'high': 120.99, 'low': 100.83, 'close': 115.95, 'volume': 10082.64}, {'date': '2023-05-03', 'open': 110.9, 'high': 120.98, 'low': 100.82, 'close': 115.94, 'volume': 10081.97}, {'date': '2023-05-04', 'open': 110.89, 'high': 120.98, 'low': 100.81, 'close': 115.93, 'volume': 10081.3}, {'date': '2023-05-05', 'open': 110.89, 'high': 120.97, 'low': 100.81, 'close': 115.93, 'volume': 10080.65}, {'date': '2023-05-06', 'open': 110.88, 'high': 120.96, 'low': 100.8, 'close': 115.92, 'volume': 10080.0}, {'date': '2023-05-07', 'open': 110.87, 'high': 120.95, 'low': 100.79, 'close': 115.91, 'volume': 10079.37}, {'date': '2023-05-08', 'open': 110.87, 'high': 120.94, 'low': 100.79, 'close': 115.91, 'volume': 10078.74}, {'date': '2023-05-09', 'open': 110.86, 'high': 120.94, 'low': 100.78, 'close': 115.9, 'volume': 10078.12}, {'date': '2023-05-10', 'open': 110.85, 'high': 120.93, 'low': 100.78, 'close': 115.89, 'volume': 10077.52}, {'date': '2023-05-11', 'open': 110.85, 'high': 120.92, 'low': 100.77, 'close': 115.88, 'volume': 10076.92}, {'date': '2023-05-12', 'open': 110.84, 'high': 120.92, 'low': 100.76, 'close': 115.88, 'volume': 10076.34}, {'date': '2023-05-13', 'open': 110.83, 'high': 120.91, 'low': 100.76, 'close': 115.87, 'volume': 10075.76}, {'date': '2023-05-14', 'open': 110.83, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10075.19}, {'date': '2023-05-15', 'open': 110.82, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10074.63}, {'date': '2023-05-16', 'open': 110.81, 'high': 120.89, 'low': 100.74, 'close': 115.85, 'volume': 10074.07}, {'date': '2023-05-17', 'open': 110.81, 'high': 120.88, 'low': 100.74, 'close': 115.85, 'volume': 10073.53}, {'date': '2023-05-18', 'open': 110.8, 'high': 120.88, 'low': 100.73, 'close': 115.84, 'volume': 10072.99}, {'date': '2023-05-19', 'open': 110.8, 'high': 120.87, 'low': 100.72, 'close': 115.83, 'volume': 10072.46}, {'date': '2023-05-20', 'open': 110.79, 'high': 120.86, 'low': 100.72, 'close': 115.83, 'volume': 10071.94}, {'date': '2023-05-21', 'open': 110.79, 'high': 120.86, 'low': 100.71, 'close': 115.82, 'volume': 10071.43}, {'date': '2023-05-22', 'open': 110.78, 'high': 120.85, 'low': 100.71, 'close': 115.82, 'volume': 10070.92}, {'date': '2023-05-23', 'open': 110.77, 'high': 120.85, 'low': 100.7, 'close': 115.81, 'volume': 10070.42}, {'date': '2023-05-24', 'open': 110.77, 'high': 120.84, 'low': 100.7, 'close': 115.8, 'volume': 10069.93}, {'date': '2023-05-25', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.8, 'volume': 10069.44}, {'date': '2023-05-26', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.79, 'volume': 10068.97}, {'date': '2023-05-27', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.79, 'volume': 10068.49}, {'date': '2023-05-28', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.78, 'volume': 10068.03}, {'date': '2023-05-29', 'open': 110.74, 'high': 120.81, 'low': 100.68, 'close': 115.78, 'volume': 10067.57}, {'date': '2023-05-30', 'open': 110.74, 'high': 120.81, 'low': 100.67, 'close': 115.77, 'volume': 10067.11}, {'date': '2023-05-31', 'open': 110.73, 'high': 120.8, 'low': 100.67, 'close': 115.77, 'volume': 10066.67}, {'date': '2023-06-01', 'open': 110.73, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10066.23}, {'date': '2023-06-02', 'open': 110.72, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10065.79}, {'date': '2023-06-03', 'open': 110.72, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10065.36}, {'date': '2023-06-04', 'open': 110.71, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10064.94}, {'date': '2023-06-05', 'open': 110.71, 'high': 120.77, 'low': 100.65, 'close': 115.74, 'volume': 10064.52}, {'date': '2023-06-06', 'open': 110.71, 'high': 120.77, 'low': 100.64, 'close': 115.74, 'volume': 10064.1}, {'date': '2023-06-07', 'open': 110.7, 'high': 120.76, 'low': 100.64, 'close': 115.73, 'volume': 10063.69}, {'date': '2023-06-08', 'open': 110.7, 'high': 120.76, 'low': 100.63, 'close': 115.73, 'volume': 10063.29}, {'date': '2023-06-09', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.89}, {'date': '2023-06-10', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.5}, {'date': '2023-06-11', 'open': 110.68, 'high': 120.75, 'low': 100.62, 'close': 115.71, 'volume': 10062.11}, {'date': '2023-06-12', 'open': 110.68, 'high': 120.74, 'low': 100.62, 'close': 115.71, 'volume': 10061.73}, {'date': '2023-06-13', 'open': 110.67, 'high': 120.74, 'low': 100.61, 'close': 115.71, 'volume': 10061.35}, {'date': '2023-06-14', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.98}, {'date': '2023-06-15', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.61}, {'date': '2023-06-16', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10060.24}, {'date': '2023-06-17', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10059.88}, {'date': '2023-06-18', 'open': 110.65, 'high': 120.71, 'low': 100.6, 'close': 115.68, 'volume': 10059.52}, {'date': '2023-06-19', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10059.17}, {'date': '2023-06-20', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10058.82}, {'date': '2023-06-21', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.48}, {'date': '2023-06-22', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.14}, {'date': '2023-06-23', 'open': 110.64, 'high': 120.69, 'low': 100.58, 'close': 115.66, 'volume': 10057.8}, {'date': '2023-06-24', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.47}, {'date': '2023-06-25', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.14}, {'date': '2023-06-26', 'open': 110.62, 'high': 120.68, 'low': 100.57, 'close': 115.65, 'volume': 10056.82}, {'date': '2023-06-27', 'open': 110.62, 'high': 120.68, 'low': 100.56, 'close': 115.65, 'volume': 10056.5}, {'date': '2023-06-28', 'open': 110.62, 'high': 120.67, 'low': 100.56, 'close': 115.65, 'volume': 10056.18}, {'date': '2023-06-29', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.87}, {'date': '2023-06-30', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.56}, {'date': '2023-07-01', 'open': 110.61, 'high': 120.66, 'low': 100.55, 'close': 115.64, 'volume': 10055.25}, {'date': '2023-07-02', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.95}, {'date': '2023-07-03', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.64}, {'date': '2023-07-04', 'open': 110.6, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.35}, {'date': '2023-07-05', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.05}, {'date': '2023-07-06', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10053.76}, {'date': '2023-07-07', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.48}, {'date': '2023-07-08', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.19}, {'date': '2023-07-09', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.91}, {'date': '2023-07-10', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.63}, {'date': '2023-07-11', 'open': 110.58, 'high': 120.63, 'low': 100.52, 'close': 115.6, 'volume': 10052.36}, {'date': '2023-07-12', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10052.08}, {'date': '2023-07-13', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10051.81}, {'date': '2023-07-14', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.59, 'volume': 10051.55}, {'date': '2023-07-15', 'open': 110.56, 'high': 120.62, 'low': 100.51, 'close': 115.59, 'volume': 10051.28}, {'date': '2023-07-16', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.59, 'volume': 10051.02}, {'date': '2023-07-17', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.76}, {'date': '2023-07-18', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.51}, {'date': '2023-07-19', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.58, 'volume': 10050.25}, {'date': '2023-07-20', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10050.0}, {'date': '2023-07-21', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10049.75}, {'date': '2023-07-22', 'open': 110.54, 'high': 120.59, 'low': 100.5, 'close': 115.57, 'volume': 10049.5}, {'date': '2023-07-23', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.57, 'volume': 10049.26}, {'date': '2023-07-24', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10049.02}, {'date': '2023-07-25', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10048.78}, {'date': '2023-07-26', 'open': 110.53, 'high': 120.58, 'low': 100.49, 'close': 115.56, 'volume': 10048.54}, {'date': '2023-07-27', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.56, 'volume': 10048.31}, {'date': '2023-07-28', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.55, 'volume': 10048.08}, {'date': '2023-07-29', 'open': 110.53, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.85}, {'date': '2023-07-30', 'open': 110.52, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.62}, {'date': '2023-07-31', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.55, 'volume': 10047.39}, {'date': '2023-08-01', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.54, 'volume': 10047.17}, {'date': '2023-08-02', 'open': 110.52, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.95}, {'date': '2023-08-03', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.73}, {'date': '2023-08-04', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.53, 'volume': 10046.51}, {'date': '2023-08-05', 'open': 110.51, 'high': 120.56, 'low': 100.46, 'close': 115.53, 'volume': 10046.3}, {'date': '2023-08-06', 'open': 110.51, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10046.08}, {'date': '2023-08-07', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.87}, {'date': '2023-08-08', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.66}, {'date': '2023-08-09', 'open': 110.5, 'high': 120.55, 'low': 100.45, 'close': 115.52, 'volume': 10045.45}, {'date': '2023-08-10', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.25}, {'date': '2023-08-11', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.05}, {'date': '2023-08-12', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10044.84}, {'date': '2023-08-13', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.51, 'volume': 10044.64}, {'date': '2023-08-14', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.44}, {'date': '2023-08-15', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.25}, {'date': '2023-08-16', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.05}, {'date': '2023-08-17', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.5, 'volume': 10043.86}, {'date': '2023-08-18', 'open': 110.48, 'high': 120.52, 'low': 100.44, 'close': 115.5, 'volume': 10043.67}, {'date': '2023-08-19', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.48}, {'date': '2023-08-20', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.29}, {'date': '2023-08-21', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.1}, {'date': '2023-08-22', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.49, 'volume': 10042.92}, {'date': '2023-08-23', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.74}, {'date': '2023-08-24', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.55}, {'date': '2023-08-25', 'open': 110.47, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.37}, {'date': '2023-08-26', 'open': 110.46, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.19}, {'date': '2023-08-27', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10042.02}, {'date': '2023-08-28', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.84}, {'date': '2023-08-29', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.67}, {'date': '2023-08-30', 'open': 110.46, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.49}, {'date': '2023-08-31', 'open': 110.45, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.32}, {'date': '2023-09-01', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10041.15}, {'date': '2023-09-02', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.98}, {'date': '2023-09-03', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.82}, {'date': '2023-09-04', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.65}, {'date': '2023-09-05', 'open': 110.45, 'high': 120.49, 'low': 100.4, 'close': 115.47, 'volume': 10040.49}, {'date': '2023-09-06', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.32}, {'date': '2023-09-07', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.16}, {'date': '2023-09-08', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.0}, {'date': '2023-09-09', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.84}, {'date': '2023-09-10', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.68}, {'date': '2023-09-11', 'open': 110.43, 'high': 120.47, 'low': 100.4, 'close': 115.45, 'volume': 10039.53}, {'date': '2023-09-12', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.37}, {'date': '2023-09-13', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.22}, {'date': '2023-09-14', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.06}, {'date': '2023-09-15', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.91}, {'date': '2023-09-16', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.76}, {'date': '2023-09-17', 'open': 110.42, 'high': 120.46, 'low': 100.39, 'close': 115.44, 'volume': 10038.61}, {'date': '2023-09-18', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.46}, {'date': '2023-09-19', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.31}, {'date': '2023-09-20', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.17}, {'date': '2023-09-21', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.02}, {'date': '2023-09-22', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.44, 'volume': 10037.88}, {'date': '2023-09-23', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.74}, {'date': '2023-09-24', 'open': 110.41, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.59}, {'date': '2023-09-25', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.45}, {'date': '2023-09-26', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.31}, {'date': '2023-09-27', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.17}, {'date': '2023-09-28', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.43, 'volume': 10037.04}, {'date': '2023-09-29', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.9}, {'date': '2023-09-30', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.76}, {'date': '2023-10-01', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.63}, {'date': '2023-10-02', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.5}, {'date': '2023-10-03', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.36}, {'date': '2023-10-04', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.23}, {'date': '2023-10-05', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.1}, {'date': '2023-10-06', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.97}, {'date': '2023-10-07', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.84}, {'date': '2023-10-08', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.71}, {'date': '2023-10-09', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.59}, {'date': '2023-10-10', 'open': 110.39, 'high': 120.43, 'low': 100.35, 'close': 115.41, 'volume': 10035.46}, {'date': '2023-10-11', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.41, 'volume': 10035.34}, {'date': '2023-10-12', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.21}, {'date': '2023-10-13', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.09}, {'date': '2023-10-14', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.97}, {'date': '2023-10-15', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.84}, {'date': '2023-10-16', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.72}, {'date': '2023-10-17', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.6}, {'date': '2023-10-18', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.48}, {'date': '2023-10-19', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.36}, {'date': '2023-10-20', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.25}, {'date': '2023-10-21', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.13}, {'date': '2023-10-22', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.01}, {'date': '2023-10-23', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.9}, {'date': '2023-10-24', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.78}, {'date': '2023-10-25', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.67}, {'date': '2023-10-26', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.56}, {'date': '2023-10-27', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.44}, {'date': '2023-10-28', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.33}, {'date': '2023-10-29', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.22}, {'date': '2023-10-30', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.11}, {'date': '2023-10-31', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.0}, {'date': '2023-11-01', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.89}, {'date': '2023-11-02', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.79}, {'date': '2023-11-03', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.68}, {'date': '2023-11-04', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.37, 'volume': 10032.57}, {'date': '2023-11-05', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.47}, {'date': '2023-11-06', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.36}, {'date': '2023-11-07', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.26}, {'date': '2023-11-08', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.15}, {'date': '2023-11-09', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10032.05}, {'date': '2023-11-10', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.95}, {'date': '2023-11-11', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.85}, {'date': '2023-11-12', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.75}, {'date': '2023-11-13', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.65}, {'date': '2023-11-14', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.55}, {'date': '2023-11-15', 'open': 110.35, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.45}, {'date': '2023-11-16', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.35}, {'date': '2023-11-17', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.25}, {'date': '2023-11-18', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.15}, {'date': '2023-11-19', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.06}, {'date': '2023-11-20', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10030.96}, {'date': '2023-11-21', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.86}, {'date': '2023-11-22', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.77}, {'date': '2023-11-23', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.67}, {'date': '2023-11-24', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.58}, {'date': '2023-11-25', 'open': 110.34, 'high': 120.37, 'low': 100.3, 'close': 115.35, 'volume': 10030.49}, {'date': '2023-11-26', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.4}, {'date': '2023-11-27', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.3}, {'date': '2023-11-28', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.21}, {'date': '2023-11-29', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.12}, {'date': '2023-11-30', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.03}, {'date': '2023-12-01', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.94}, {'date': '2023-12-02', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.85}, {'date': '2023-12-03', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.76}, {'date': '2023-12-04', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.67}, {'date': '2023-12-05', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.59}, {'date': '2023-12-06', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.5}, {'date': '2023-12-07', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.41}, {'date': '2023-12-08', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.33}, {'date': '2023-12-09', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.24}, {'date': '2023-12-10', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.15}, {'date': '2023-12-11', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10029.07}, {'date': '2023-12-12', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.99}, {'date': '2023-12-13', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.9}, {'date': '2023-12-14', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.82}, {'date': '2023-12-15', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.74}, {'date': '2023-12-16', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.65}, {'date': '2023-12-17', 'open': 110.31, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.57}, {'date': '2023-12-18', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.49}, {'date': '2023-12-19', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.41}, {'date': '2023-12-20', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.33}, {'date': '2023-12-21', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.25}, {'date': '2023-12-22', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.17}, {'date': '2023-12-23', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.09}, {'date': '2023-12-24', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.01}, {'date': '2023-12-25', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10027.93}, {'date': '2023-12-26', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.86}, {'date': '2023-12-27', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.78}, {'date': '2023-12-28', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.7}, {'date': '2023-12-29', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.62}, {'date': '2023-12-30', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.55}, {'date': '2023-12-31', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.47}, {'date': '2024-01-01', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.4}, {'date': '2024-01-02', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.32}, {'date': '2024-01-03', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.25}, {'date': '2024-01-04', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.17}, {'date': '2024-01-05', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.1}, {'date': '2024-01-06', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10027.03}, {'date': '2024-01-07', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.95}, {'date': '2024-01-08', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.88}, {'date': '2024-01-09', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.81}, {'date': '2024-01-10', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.74}, {'date': '2024-01-11', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.67}, {'date': '2024-01-12', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.6}, {'date': '2024-01-13', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.53}, {'date': '2024-01-14', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.46}, {'date': '2024-01-15', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.39}, {'date': '2024-01-16', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.32}, {'date': '2024-01-17', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.25}, {'date': '2024-01-18', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.18}, {'date': '2024-01-19', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.11}, {'date': '2024-01-20', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.04}, {'date': '2024-01-21', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.97}, {'date': '2024-01-22', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.91}, {'date': '2024-01-23', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.84}, {'date': '2024-01-24', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.77}, {'date': '2024-01-25', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.71}, {'date': '2024-01-26', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.64}, {'date': '2024-01-27', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.58}, {'date': '2024-01-28', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.51}, {'date': '2024-01-29', 'open': 110.28, 'high': 120.31, 'low': 100.25, 'close': 115.29, 'volume': 10025.45}, {'date': '2024-01-30', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.38}, {'date': '2024-01-31', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.32}, {'date': '2024-02-01', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.25}, {'date': '2024-02-02', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.19}, {'date': '2024-02-03', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.13}, {'date': '2024-02-04', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.06}, {'date': '2024-02-05', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.0}, {'date': '2024-02-06', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.94}, {'date': '2024-02-07', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.88}, {'date': '2024-02-08', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.81}, {'date': '2024-02-09', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.75}, {'date': '2024-02-10', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.69}, {'date': '2024-02-11', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.63}, {'date': '2024-02-12', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.57}, {'date': '2024-02-13', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.51}, {'date': '2024-02-14', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.45}, {'date': '2024-02-15', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.39}, {'date': '2024-02-16', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.33}, {'date': '2024-02-17', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.27}, {'date': '2024-02-18', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.21}, {'date': '2024-02-19', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.15}, {'date': '2024-02-20', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.1}, {'date': '2024-02-21', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.04}, {'date': '2024-02-22', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.98}, {'date': '2024-02-23', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.92}, {'date': '2024-02-24', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.87}, {'date': '2024-02-25', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.81}, {'date': '2024-02-26', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.75}, {'date': '2024-02-27', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.7}, {'date': '2024-02-28', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.64}, {'date': '2024-02-29', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.58}, {'date': '2024-03-01', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.53}, {'date': '2024-03-02', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.47}, {'date': '2024-03-03', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.42}, {'date': '2024-03-04', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.36}, {'date': '2024-03-05', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.31}, {'date': '2024-03-06', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.26}, {'date': '2024-03-07', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.2}, {'date': '2024-03-08', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.15}, {'date': '2024-03-09', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.09}, {'date': '2024-03-10', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10023.04}, {'date': '2024-03-11', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.99}, {'date': '2024-03-12', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.94}, {'date': '2024-03-13', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.88}, {'date': '2024-03-14', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.83}, {'date': '2024-03-15', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.78}, {'date': '2024-03-16', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.73}, {'date': '2024-03-17', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.68}, {'date': '2024-03-18', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.62}, {'date': '2024-03-19', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.57}, {'date': '2024-03-20', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.52}, {'date': '2024-03-21', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.47}, {'date': '2024-03-22', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.42}, {'date': '2024-03-23', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.37}, {'date': '2024-03-24', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.32}, {'date': '2024-03-25', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.27}, {'date': '2024-03-26', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.22}, {'date': '2024-03-27', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.17}, {'date': '2024-03-28', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.12}, {'date': '2024-03-29', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.08}, {'date': '2024-03-30', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.03}, {'date': '2024-03-31', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.98}, {'date': '2024-04-01', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.93}, {'date': '2024-04-02', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.88}, {'date': '2024-04-03', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.83}, {'date': '2024-04-04', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.79}, {'date': '2024-04-05', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.74}, {'date': '2024-04-06', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.69}, {'date': '2024-04-07', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.65}, {'date': '2024-04-08', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.6}, {'date': '2024-04-09', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.55}, {'date': '2024-04-10', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.51}, {'date': '2024-04-11', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.46}, {'date': '2024-04-12', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.41}, {'date': '2024-04-13', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.37}, {'date': '2024-04-14', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.32}, {'date': '2024-04-15', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.24, 'volume': 10021.28}, {'date': '2024-04-16', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.23}, {'date': '2024-04-17', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.19}, {'date': '2024-04-18', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.14}, {'date': '2024-04-19', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.1}, {'date': '2024-04-20', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.05}, {'date': '2024-04-21', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.01}, {'date': '2024-04-22', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.96}, {'date': '2024-04-23', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.92}, {'date': '2024-04-24', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.88}, {'date': '2024-04-25', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.83}, {'date': '2024-04-26', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.79}, {'date': '2024-04-27', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.75}, {'date': '2024-04-28', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.7}, {'date': '2024-04-29', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.66}, {'date': '2024-04-30', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.62}, {'date': '2024-05-01', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.58}, {'date': '2024-05-02', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.53}, {'date': '2024-05-03', 'open': 110.23, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.49}, {'date': '2024-05-04', 'open': 110.22, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.45}, {'date': '2024-05-05', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.41}, {'date': '2024-05-06', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.37}, {'date': '2024-05-07', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.33}, {'date': '2024-05-08', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.28}, {'date': '2024-05-09', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.24}, {'date': '2024-05-10', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.2}, {'date': '2024-05-11', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.16}, {'date': '2024-05-12', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.12}, {'date': '2024-05-13', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.08}, {'date': '2024-05-14', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.04}, {'date': '2024-05-15', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.0}, {'date': '2024-05-16', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.96}, {'date': '2024-05-17', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.92}, {'date': '2024-05-18', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.88}, {'date': '2024-05-19', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.84}, {'date': '2024-05-20', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.8}, {'date': '2024-05-21', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.76}, {'date': '2024-05-22', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.72}, {'date': '2024-05-23', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.69}, {'date': '2024-05-24', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.65}, {'date': '2024-05-25', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.61}, {'date': '2024-05-26', 'open': 110.22, 'high': 120.23, 'low': 100.2, 'close': 115.23, 'volume': 10019.57}, {'date': '2024-05-27', 'open': 110.21, 'high': 120.23, 'low': 100.2, 'close': 115.22, 'volume': 10019.53}, {'date': '2024-05-28', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.49}, {'date': '2024-05-29', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.46}, {'date': '2024-05-30', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.42}, {'date': '2024-05-31', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.38}, {'date': '2024-06-01', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.34}, {'date': '2024-06-02', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.31}, {'date': '2024-06-03', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.27}, {'date': '2024-06-04', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.23}, {'date': '2024-06-05', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.19}, {'date': '2024-06-06', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.16}, {'date': '2024-06-07', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.12}, {'date': '2024-06-08', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.08}, {'date': '2024-06-09', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.05}, {'date': '2024-06-10', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.01}, {'date': '2024-06-11', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.98}, {'date': '2024-06-12', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.94}, {'date': '2024-06-13', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.9}, {'date': '2024-06-14', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.87}, {'date': '2024-06-15', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.83}, {'date': '2024-06-16', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.8}, {'date': '2024-06-17', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.76}, {'date': '2024-06-18', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.22, 'volume': 10018.73}, {'date': '2024-06-19', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.69}, {'date': '2024-06-20', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.66}, {'date': '2024-06-21', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.62}, {'date': '2024-06-22', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.59}, {'date': '2024-06-23', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.55}, {'date': '2024-06-24', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.52}, {'date': '2024-06-25', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.48}, {'date': '2024-06-26', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.45}, {'date': '2024-06-27', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.42}, {'date': '2024-06-28', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.38}, {'date': '2024-06-29', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.35}, {'date': '2024-06-30', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.32}, {'date': '2024-07-01', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.28}, {'date': '2024-07-02', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.25}, {'date': '2024-07-03', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.21}, {'date': '2024-07-04', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.18}, {'date': '2024-07-05', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.15}, {'date': '2024-07-06', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.12}, {'date': '2024-07-07', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.08}, {'date': '2024-07-08', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.05}, {'date': '2024-07-09', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.02}, {'date': '2024-07-10', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.99}, {'date': '2024-07-11', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.95}, {'date': '2024-07-12', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.92}, {'date': '2024-07-13', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.89}, {'date': '2024-07-14', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.86}, {'date': '2024-07-15', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.83}, {'date': '2024-07-16', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.79}, {'date': '2024-07-17', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.76}, {'date': '2024-07-18', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.73}, {'date': '2024-07-19', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.7}, {'date': '2024-07-20', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.67}, {'date': '2024-07-21', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.64}, {'date': '2024-07-22', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.61}, {'date': '2024-07-23', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.57}, {'date': '2024-07-24', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.54}, {'date': '2024-07-25', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.51}, {'date': '2024-07-26', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.48}, {'date': '2024-07-27', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.45}, {'date': '2024-07-28', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.42}, {'date': '2024-07-29', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.39}, {'date': '2024-07-30', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.36}, {'date': '2024-07-31', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.33}, {'date': '2024-08-01', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.3}, {'date': '2024-08-02', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.27}, {'date': '2024-08-03', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.24}, {'date': '2024-08-04', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.21}, {'date': '2024-08-05', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.18}, {'date': '2024-08-06', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.15}, {'date': '2024-08-07', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.12}, {'date': '2024-08-08', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.09}, {'date': '2024-08-09', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.06}, {'date': '2024-08-10', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.04}, {'date': '2024-08-11', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.01}, {'date': '2024-08-12', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10016.98}, {'date': '2024-08-13', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.95}, {'date': '2024-08-14', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.92}, {'date': '2024-08-15', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.89}, {'date': '2024-08-16', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.86}, {'date': '2024-08-17', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.84}, {'date': '2024-08-18', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.81}, {'date': '2024-08-19', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.78}, {'date': '2024-08-20', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.75}, {'date': '2024-08-21', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.72}, {'date': '2024-08-22', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.69}, {'date': '2024-08-23', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.67}, {'date': '2024-08-24', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.64}, {'date': '2024-08-25', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.61}, {'date': '2024-08-26', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.58}, {'date': '2024-08-27', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.56}, {'date': '2024-08-28', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.53}, {'date': '2024-08-29', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.5}, {'date': '2024-08-30', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.47}, {'date': '2024-08-31', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.45}, {'date': '2024-09-01', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.42}, {'date': '2024-09-02', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.39}, {'date': '2024-09-03', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.37}, {'date': '2024-09-04', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.34}, {'date': '2024-09-05', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.31}, {'date': '2024-09-06', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.29}, {'date': '2024-09-07', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.26}, {'date': '2024-09-08', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.23}, {'date': '2024-09-09', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.21}, {'date': '2024-09-10', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.18}, {'date': '2024-09-11', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.16}, {'date': '2024-09-12', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.13}, {'date': '2024-09-13', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.1}, {'date': '2024-09-14', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.08}, {'date': '2024-09-15', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.05}, {'date': '2024-09-16', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.03}, {'date': '2024-09-17', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.0}, {'date': '2024-09-18', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.97}, {'date': '2024-09-19', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.95}, {'date': '2024-09-20', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.92}, {'date': '2024-09-21', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.9}, {'date': '2024-09-22', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.87}, {'date': '2024-09-23', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.85}, {'date': '2024-09-24', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.82}, {'date': '2024-09-25', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.8}, {'date': '2024-09-26', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.77}, {'date': '2024-09-27', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.75}, {'date': '2024-09-28', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.72}, {'date': '2024-09-29', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.7}, {'date': '2024-09-30', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.67}, {'date': '2024-10-01', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.65}, {'date': '2024-10-02', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.62}, {'date': '2024-10-03', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.6}, {'date': '2024-10-04', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.58}, {'date': '2024-10-05', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.55}, {'date': '2024-10-06', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.53}, {'date': '2024-10-07', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.5}, {'date': '2024-10-08', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.48}, {'date': '2024-10-09', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.46}, {'date': '2024-10-10', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.43}, {'date': '2024-10-11', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.41}, {'date': '2024-10-12', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.38}, {'date': '2024-10-13', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.36}, {'date': '2024-10-14', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.34}, {'date': '2024-10-15', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.31}, {'date': '2024-10-16', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.29}, {'date': '2024-10-17', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.27}, {'date': '2024-10-18', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.24}, {'date': '2024-10-19', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.22}, {'date': '2024-10-20', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.2}, {'date': '2024-10-21', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.17}, {'date': '2024-10-22', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.15}, {'date': '2024-10-23', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.13}, {'date': '2024-10-24', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.11}, {'date': '2024-10-25', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.08}, {'date': '2024-10-26', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.06}, {'date': '2024-10-27', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.04}, {'date': '2024-10-28', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.02}, {'date': '2024-10-29', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.99}, {'date': '2024-10-30', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.97}, {'date': '2024-10-31', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.95}, {'date': '2024-11-01', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.93}, {'date': '2024-11-02', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.9}, {'date': '2024-11-03', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.88}, {'date': '2024-11-04', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.86}, {'date': '2024-11-05', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.84}, {'date': '2024-11-06', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.81}, {'date': '2024-11-07', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.79}, {'date': '2024-11-08', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.77}, {'date': '2024-11-09', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.75}, {'date': '2024-11-10', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.73}, {'date': '2024-11-11', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.71}, {'date': '2024-11-12', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.68}, {'date': '2024-11-13', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.66}, {'date': '2024-11-14', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.64}, {'date': '2024-11-15', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.62}, {'date': '2024-11-16', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.6}, {'date': '2024-11-17', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.58}, {'date': '2024-11-18', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.56}, {'date': '2024-11-19', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.53}, {'date': '2024-11-20', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.51}, {'date': '2024-11-21', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.49}, {'date': '2024-11-22', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.47}, {'date': '2024-11-23', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.45}, {'date': '2024-11-24', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.43}, {'date': '2024-11-25', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.41}, {'date': '2024-11-26', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.39}, {'date': '2024-11-27', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.37}, {'date': '2024-11-28', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.35}, {'date': '2024-11-29', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.33}, {'date': '2024-11-30', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.31}, {'date': '2024-12-01', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.29}, {'date': '2024-12-02', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.27}, {'date': '2024-12-03', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.25}, {'date': '2024-12-04', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.22}, {'date': '2024-12-05', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.2}, {'date': '2024-12-06', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.18}, {'date': '2024-12-07', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.16}, {'date': '2024-12-08', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.14}, {'date': '2024-12-09', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.12}, {'date': '2024-12-10', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.1}, {'date': '2024-12-11', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.08}, {'date': '2024-12-12', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.06}, {'date': '2024-12-13', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.04}, {'date': '2024-12-14', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.03}, {'date': '2024-12-15', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.01}, {'date': '2024-12-16', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.99}, {'date': '2024-12-17', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.97}, {'date': '2024-12-18', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.95}, {'date': '2024-12-19', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.93}, {'date': '2024-12-20', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.91}, {'date': '2024-12-21', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.89}, {'date': '2024-12-22', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.87}, {'date': '2024-12-23', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.85}, {'date': '2024-12-24', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.83}, {'date': '2024-12-25', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.81}, {'date': '2024-12-26', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.79}, {'date': '2024-12-27', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.77}, {'date': '2024-12-28', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.76}, {'date': '2024-12-29', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.74}, {'date': '2024-12-30', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.72}, {'date': '2024-12-31', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.7}, {'date': '2025-01-01', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.68}, {'date': '2025-01-02', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.66}, {'date': '2025-01-03', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.64}, {'date': '2025-01-04', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.62}, {'date': '2025-01-05', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.61}, {'date': '2025-01-06', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.59}, {'date': '2025-01-07', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.57}, {'date': '2025-01-08', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.55}, {'date': '2025-01-09', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.53}, {'date': '2025-01-10', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.51}, {'date': '2025-01-11', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.16, 'volume': 10013.5}, {'date': '2025-01-12', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.48}, {'date': '2025-01-13', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.46}, {'date': '2025-01-14', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.44}, {'date': '2025-01-15', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.42}, {'date': '2025-01-16', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.4}, {'date': '2025-01-17', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.39}, {'date': '2025-01-18', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.37}, {'date': '2025-01-19', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.35}, {'date': '2025-01-20', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.33}, {'date': '2025-01-21', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.32}, {'date': '2025-01-22', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.3}, {'date': '2025-01-23', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.28}, {'date': '2025-01-24', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.26}, {'date': '2025-01-25', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.25}, {'date': '2025-01-26', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.23}, {'date': '2025-01-27', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.21}, {'date': '2025-01-28', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.19}, {'date': '2025-01-29', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.18}, {'date': '2025-01-30', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.16}, {'date': '2025-01-31', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.14}, {'date': '2025-02-01', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.12}, {'date': '2025-02-02', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.11}, {'date': '2025-02-03', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.09}, {'date': '2025-02-04', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.07}, {'date': '2025-02-05', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.05}, {'date': '2025-02-06', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.04}, {'date': '2025-02-07', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.02}, {'date': '2025-02-08', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.0}, {'date': '2025-02-09', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.99}, {'date': '2025-02-10', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.97}, {'date': '2025-02-11', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.95}, {'date': '2025-02-12', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.94}, {'date': '2025-02-13', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.92}, {'date': '2025-02-14', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.9}, {'date': '2025-02-15', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.89}, {'date': '2025-02-16', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.87}, {'date': '2025-02-17', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.85}, {'date': '2025-02-18', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.84}, {'date': '2025-02-19', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.82}, {'date': '2025-02-20', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.8}, {'date': '2025-02-21', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.79}, {'date': '2025-02-22', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.77}, {'date': '2025-02-23', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.76}, {'date': '2025-02-24', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.74}, {'date': '2025-02-25', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.72}, {'date': '2025-02-26', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.71}, {'date': '2025-02-27', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.69}, {'date': '2025-02-28', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.67}, {'date': '2025-03-01', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.66}, {'date': '2025-03-02', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.64}, {'date': '2025-03-03', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.63}, {'date': '2025-03-04', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.61}, {'date': '2025-03-05', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.59}, {'date': '2025-03-06', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.58}, {'date': '2025-03-07', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.56}, {'date': '2025-03-08', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.55}, {'date': '2025-03-09', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.53}, {'date': '2025-03-10', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.52}, {'date': '2025-03-11', 'open': 110.14, 'high': 120.15, 'low': 100.12, 'close': 115.14, 'volume': 10012.5}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Target time series (index column plus the target column).", + "default": null, + "optional": false + }, + { + "name": "factors_data", + "type": "list[Data]", + "description": "Factor matrix (index column plus one column per factor).", + "default": null, + "optional": false + }, + { + "name": "periods", + "type": "list[str]", + "description": "Named look-back periods to decompose over, anchored to the latest factor observation.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column in `data` to decompose against the factors.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column shared by `data` and `factors_data`.", + "default": "date", + "optional": true + }, + { + "name": "risk_free_column", + "type": "str", + "description": "Optional name of the risk-free rate column in `factors_data`. When provided, the target is converted to excess returns before decomposition and the column is dropped from the factor set.", + "default": null, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RiskDecompositionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "period", + "type": "str", + "description": "Look-back period the decomposition was run over.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "factor", + "type": "str", + "description": "Factor name, or 'Residual' for the idiosyncratic component.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "contribution", + "type": "float", + "description": "Covariance-based contribution to Var(target): for a factor, beta_i * Cov(factor_i, target); for 'Residual', Var(target) * (1 - R^2).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "share", + "type": "float", + "description": "Fraction of Var(target) attributed to this factor (or to the residual). Factor shares sum to R^2; the residual share equals 1 - R^2.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/quantitative/attribution": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Decompose the target's total return into per-factor contributions plus alpha\nand residual.\n\nFor each look-back period, runs OLS of the target on the factor matrix and\nsplits the period's cumulative target return via the identity\n`sum_t y_t = alpha * n + sum_i beta_i * sum_t f_{i,t} + sum_t residual_t`.\nThe three components are returned as rows: one per factor, one `Alpha`, and one\n`Residual`. Use with arithmetic (additive) returns; for geometric compounding,\nconvert returns to log-returns before passing in.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.quantitative.attribution(target='close', data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}, {'date': '2023-03-03', 'open': 111.8, 'high': 121.97, 'low': 101.64, 'close': 116.89, 'volume': 10163.93}, {'date': '2023-03-04', 'open': 111.77, 'high': 121.94, 'low': 101.61, 'close': 116.85, 'volume': 10161.29}, {'date': '2023-03-05', 'open': 111.75, 'high': 121.9, 'low': 101.59, 'close': 116.83, 'volume': 10158.73}, {'date': '2023-03-06', 'open': 111.72, 'high': 121.88, 'low': 101.56, 'close': 116.8, 'volume': 10156.25}, {'date': '2023-03-07', 'open': 111.69, 'high': 121.85, 'low': 101.54, 'close': 116.77, 'volume': 10153.85}, {'date': '2023-03-08', 'open': 111.67, 'high': 121.82, 'low': 101.52, 'close': 116.74, 'volume': 10151.52}, {'date': '2023-03-09', 'open': 111.64, 'high': 121.79, 'low': 101.49, 'close': 116.72, 'volume': 10149.25}, {'date': '2023-03-10', 'open': 111.62, 'high': 121.76, 'low': 101.47, 'close': 116.69, 'volume': 10147.06}, {'date': '2023-03-11', 'open': 111.59, 'high': 121.74, 'low': 101.45, 'close': 116.67, 'volume': 10144.93}, {'date': '2023-03-12', 'open': 111.57, 'high': 121.71, 'low': 101.43, 'close': 116.64, 'volume': 10142.86}, {'date': '2023-03-13', 'open': 111.55, 'high': 121.69, 'low': 101.41, 'close': 116.62, 'volume': 10140.85}, {'date': '2023-03-14', 'open': 111.53, 'high': 121.67, 'low': 101.39, 'close': 116.6, 'volume': 10138.89}, {'date': '2023-03-15', 'open': 111.51, 'high': 121.64, 'low': 101.37, 'close': 116.58, 'volume': 10136.99}, {'date': '2023-03-16', 'open': 111.49, 'high': 121.62, 'low': 101.35, 'close': 116.55, 'volume': 10135.14}, {'date': '2023-03-17', 'open': 111.47, 'high': 121.6, 'low': 101.33, 'close': 116.53, 'volume': 10133.33}, {'date': '2023-03-18', 'open': 111.45, 'high': 121.58, 'low': 101.32, 'close': 116.51, 'volume': 10131.58}, {'date': '2023-03-19', 'open': 111.43, 'high': 121.56, 'low': 101.3, 'close': 116.49, 'volume': 10129.87}, {'date': '2023-03-20', 'open': 111.41, 'high': 121.54, 'low': 101.28, 'close': 116.47, 'volume': 10128.21}, {'date': '2023-03-21', 'open': 111.39, 'high': 121.52, 'low': 101.27, 'close': 116.46, 'volume': 10126.58}, {'date': '2023-03-22', 'open': 111.38, 'high': 121.5, 'low': 101.25, 'close': 116.44, 'volume': 10125.0}, {'date': '2023-03-23', 'open': 111.36, 'high': 121.48, 'low': 101.23, 'close': 116.42, 'volume': 10123.46}, {'date': '2023-03-24', 'open': 111.34, 'high': 121.46, 'low': 101.22, 'close': 116.4, 'volume': 10121.95}, {'date': '2023-03-25', 'open': 111.33, 'high': 121.45, 'low': 101.2, 'close': 116.39, 'volume': 10120.48}, {'date': '2023-03-26', 'open': 111.31, 'high': 121.43, 'low': 101.19, 'close': 116.37, 'volume': 10119.05}, {'date': '2023-03-27', 'open': 111.29, 'high': 121.41, 'low': 101.18, 'close': 116.35, 'volume': 10117.65}, {'date': '2023-03-28', 'open': 111.28, 'high': 121.4, 'low': 101.16, 'close': 116.34, 'volume': 10116.28}, {'date': '2023-03-29', 'open': 111.26, 'high': 121.38, 'low': 101.15, 'close': 116.32, 'volume': 10114.94}, {'date': '2023-03-30', 'open': 111.25, 'high': 121.36, 'low': 101.14, 'close': 116.31, 'volume': 10113.64}, {'date': '2023-03-31', 'open': 111.24, 'high': 121.35, 'low': 101.12, 'close': 116.29, 'volume': 10112.36}, {'date': '2023-04-01', 'open': 111.22, 'high': 121.33, 'low': 101.11, 'close': 116.28, 'volume': 10111.11}, {'date': '2023-04-02', 'open': 111.21, 'high': 121.32, 'low': 101.1, 'close': 116.26, 'volume': 10109.89}, {'date': '2023-04-03', 'open': 111.2, 'high': 121.3, 'low': 101.09, 'close': 116.25, 'volume': 10108.7}, {'date': '2023-04-04', 'open': 111.18, 'high': 121.29, 'low': 101.08, 'close': 116.24, 'volume': 10107.53}, {'date': '2023-04-05', 'open': 111.17, 'high': 121.28, 'low': 101.06, 'close': 116.22, 'volume': 10106.38}, {'date': '2023-04-06', 'open': 111.16, 'high': 121.26, 'low': 101.05, 'close': 116.21, 'volume': 10105.26}, {'date': '2023-04-07', 'open': 111.15, 'high': 121.25, 'low': 101.04, 'close': 116.2, 'volume': 10104.17}, {'date': '2023-04-08', 'open': 111.13, 'high': 121.24, 'low': 101.03, 'close': 116.19, 'volume': 10103.09}, {'date': '2023-04-09', 'open': 111.12, 'high': 121.22, 'low': 101.02, 'close': 116.17, 'volume': 10102.04}, {'date': '2023-04-10', 'open': 111.11, 'high': 121.21, 'low': 101.01, 'close': 116.16, 'volume': 10101.01}, {'date': '2023-04-11', 'open': 111.1, 'high': 121.2, 'low': 101.0, 'close': 116.15, 'volume': 10100.0}, {'date': '2023-04-12', 'open': 111.09, 'high': 121.19, 'low': 100.99, 'close': 116.14, 'volume': 10099.01}, {'date': '2023-04-13', 'open': 111.08, 'high': 121.18, 'low': 100.98, 'close': 116.13, 'volume': 10098.04}, {'date': '2023-04-14', 'open': 111.07, 'high': 121.17, 'low': 100.97, 'close': 116.12, 'volume': 10097.09}, {'date': '2023-04-15', 'open': 111.06, 'high': 121.15, 'low': 100.96, 'close': 116.11, 'volume': 10096.15}, {'date': '2023-04-16', 'open': 111.05, 'high': 121.14, 'low': 100.95, 'close': 116.1, 'volume': 10095.24}, {'date': '2023-04-17', 'open': 111.04, 'high': 121.13, 'low': 100.94, 'close': 116.08, 'volume': 10094.34}, {'date': '2023-04-18', 'open': 111.03, 'high': 121.12, 'low': 100.93, 'close': 116.07, 'volume': 10093.46}, {'date': '2023-04-19', 'open': 111.02, 'high': 121.11, 'low': 100.93, 'close': 116.06, 'volume': 10092.59}, {'date': '2023-04-20', 'open': 111.01, 'high': 121.1, 'low': 100.92, 'close': 116.06, 'volume': 10091.74}, {'date': '2023-04-21', 'open': 111.0, 'high': 121.09, 'low': 100.91, 'close': 116.05, 'volume': 10090.91}, {'date': '2023-04-22', 'open': 110.99, 'high': 121.08, 'low': 100.9, 'close': 116.04, 'volume': 10090.09}, {'date': '2023-04-23', 'open': 110.98, 'high': 121.07, 'low': 100.89, 'close': 116.03, 'volume': 10089.29}, {'date': '2023-04-24', 'open': 110.97, 'high': 121.06, 'low': 100.88, 'close': 116.02, 'volume': 10088.5}, {'date': '2023-04-25', 'open': 110.96, 'high': 121.05, 'low': 100.88, 'close': 116.01, 'volume': 10087.72}, {'date': '2023-04-26', 'open': 110.96, 'high': 121.04, 'low': 100.87, 'close': 116.0, 'volume': 10086.96}, {'date': '2023-04-27', 'open': 110.95, 'high': 121.03, 'low': 100.86, 'close': 115.99, 'volume': 10086.21}, {'date': '2023-04-28', 'open': 110.94, 'high': 121.03, 'low': 100.85, 'close': 115.98, 'volume': 10085.47}, {'date': '2023-04-29', 'open': 110.93, 'high': 121.02, 'low': 100.85, 'close': 115.97, 'volume': 10084.75}, {'date': '2023-04-30', 'open': 110.92, 'high': 121.01, 'low': 100.84, 'close': 115.97, 'volume': 10084.03}, {'date': '2023-05-01', 'open': 110.92, 'high': 121.0, 'low': 100.83, 'close': 115.96, 'volume': 10083.33}, {'date': '2023-05-02', 'open': 110.91, 'high': 120.99, 'low': 100.83, 'close': 115.95, 'volume': 10082.64}, {'date': '2023-05-03', 'open': 110.9, 'high': 120.98, 'low': 100.82, 'close': 115.94, 'volume': 10081.97}, {'date': '2023-05-04', 'open': 110.89, 'high': 120.98, 'low': 100.81, 'close': 115.93, 'volume': 10081.3}, {'date': '2023-05-05', 'open': 110.89, 'high': 120.97, 'low': 100.81, 'close': 115.93, 'volume': 10080.65}, {'date': '2023-05-06', 'open': 110.88, 'high': 120.96, 'low': 100.8, 'close': 115.92, 'volume': 10080.0}, {'date': '2023-05-07', 'open': 110.87, 'high': 120.95, 'low': 100.79, 'close': 115.91, 'volume': 10079.37}, {'date': '2023-05-08', 'open': 110.87, 'high': 120.94, 'low': 100.79, 'close': 115.91, 'volume': 10078.74}, {'date': '2023-05-09', 'open': 110.86, 'high': 120.94, 'low': 100.78, 'close': 115.9, 'volume': 10078.12}, {'date': '2023-05-10', 'open': 110.85, 'high': 120.93, 'low': 100.78, 'close': 115.89, 'volume': 10077.52}, {'date': '2023-05-11', 'open': 110.85, 'high': 120.92, 'low': 100.77, 'close': 115.88, 'volume': 10076.92}, {'date': '2023-05-12', 'open': 110.84, 'high': 120.92, 'low': 100.76, 'close': 115.88, 'volume': 10076.34}, {'date': '2023-05-13', 'open': 110.83, 'high': 120.91, 'low': 100.76, 'close': 115.87, 'volume': 10075.76}, {'date': '2023-05-14', 'open': 110.83, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10075.19}, {'date': '2023-05-15', 'open': 110.82, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10074.63}, {'date': '2023-05-16', 'open': 110.81, 'high': 120.89, 'low': 100.74, 'close': 115.85, 'volume': 10074.07}, {'date': '2023-05-17', 'open': 110.81, 'high': 120.88, 'low': 100.74, 'close': 115.85, 'volume': 10073.53}, {'date': '2023-05-18', 'open': 110.8, 'high': 120.88, 'low': 100.73, 'close': 115.84, 'volume': 10072.99}, {'date': '2023-05-19', 'open': 110.8, 'high': 120.87, 'low': 100.72, 'close': 115.83, 'volume': 10072.46}, {'date': '2023-05-20', 'open': 110.79, 'high': 120.86, 'low': 100.72, 'close': 115.83, 'volume': 10071.94}, {'date': '2023-05-21', 'open': 110.79, 'high': 120.86, 'low': 100.71, 'close': 115.82, 'volume': 10071.43}, {'date': '2023-05-22', 'open': 110.78, 'high': 120.85, 'low': 100.71, 'close': 115.82, 'volume': 10070.92}, {'date': '2023-05-23', 'open': 110.77, 'high': 120.85, 'low': 100.7, 'close': 115.81, 'volume': 10070.42}, {'date': '2023-05-24', 'open': 110.77, 'high': 120.84, 'low': 100.7, 'close': 115.8, 'volume': 10069.93}, {'date': '2023-05-25', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.8, 'volume': 10069.44}, {'date': '2023-05-26', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.79, 'volume': 10068.97}, {'date': '2023-05-27', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.79, 'volume': 10068.49}, {'date': '2023-05-28', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.78, 'volume': 10068.03}, {'date': '2023-05-29', 'open': 110.74, 'high': 120.81, 'low': 100.68, 'close': 115.78, 'volume': 10067.57}, {'date': '2023-05-30', 'open': 110.74, 'high': 120.81, 'low': 100.67, 'close': 115.77, 'volume': 10067.11}, {'date': '2023-05-31', 'open': 110.73, 'high': 120.8, 'low': 100.67, 'close': 115.77, 'volume': 10066.67}, {'date': '2023-06-01', 'open': 110.73, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10066.23}, {'date': '2023-06-02', 'open': 110.72, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10065.79}, {'date': '2023-06-03', 'open': 110.72, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10065.36}, {'date': '2023-06-04', 'open': 110.71, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10064.94}, {'date': '2023-06-05', 'open': 110.71, 'high': 120.77, 'low': 100.65, 'close': 115.74, 'volume': 10064.52}, {'date': '2023-06-06', 'open': 110.71, 'high': 120.77, 'low': 100.64, 'close': 115.74, 'volume': 10064.1}, {'date': '2023-06-07', 'open': 110.7, 'high': 120.76, 'low': 100.64, 'close': 115.73, 'volume': 10063.69}, {'date': '2023-06-08', 'open': 110.7, 'high': 120.76, 'low': 100.63, 'close': 115.73, 'volume': 10063.29}, {'date': '2023-06-09', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.89}, {'date': '2023-06-10', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.5}, {'date': '2023-06-11', 'open': 110.68, 'high': 120.75, 'low': 100.62, 'close': 115.71, 'volume': 10062.11}, {'date': '2023-06-12', 'open': 110.68, 'high': 120.74, 'low': 100.62, 'close': 115.71, 'volume': 10061.73}, {'date': '2023-06-13', 'open': 110.67, 'high': 120.74, 'low': 100.61, 'close': 115.71, 'volume': 10061.35}, {'date': '2023-06-14', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.98}, {'date': '2023-06-15', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.61}, {'date': '2023-06-16', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10060.24}, {'date': '2023-06-17', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10059.88}, {'date': '2023-06-18', 'open': 110.65, 'high': 120.71, 'low': 100.6, 'close': 115.68, 'volume': 10059.52}, {'date': '2023-06-19', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10059.17}, {'date': '2023-06-20', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10058.82}, {'date': '2023-06-21', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.48}, {'date': '2023-06-22', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.14}, {'date': '2023-06-23', 'open': 110.64, 'high': 120.69, 'low': 100.58, 'close': 115.66, 'volume': 10057.8}, {'date': '2023-06-24', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.47}, {'date': '2023-06-25', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.14}, {'date': '2023-06-26', 'open': 110.62, 'high': 120.68, 'low': 100.57, 'close': 115.65, 'volume': 10056.82}, {'date': '2023-06-27', 'open': 110.62, 'high': 120.68, 'low': 100.56, 'close': 115.65, 'volume': 10056.5}, {'date': '2023-06-28', 'open': 110.62, 'high': 120.67, 'low': 100.56, 'close': 115.65, 'volume': 10056.18}, {'date': '2023-06-29', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.87}, {'date': '2023-06-30', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.56}, {'date': '2023-07-01', 'open': 110.61, 'high': 120.66, 'low': 100.55, 'close': 115.64, 'volume': 10055.25}, {'date': '2023-07-02', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.95}, {'date': '2023-07-03', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.64}, {'date': '2023-07-04', 'open': 110.6, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.35}, {'date': '2023-07-05', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.05}, {'date': '2023-07-06', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10053.76}, {'date': '2023-07-07', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.48}, {'date': '2023-07-08', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.19}, {'date': '2023-07-09', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.91}, {'date': '2023-07-10', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.63}, {'date': '2023-07-11', 'open': 110.58, 'high': 120.63, 'low': 100.52, 'close': 115.6, 'volume': 10052.36}, {'date': '2023-07-12', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10052.08}, {'date': '2023-07-13', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10051.81}, {'date': '2023-07-14', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.59, 'volume': 10051.55}, {'date': '2023-07-15', 'open': 110.56, 'high': 120.62, 'low': 100.51, 'close': 115.59, 'volume': 10051.28}, {'date': '2023-07-16', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.59, 'volume': 10051.02}, {'date': '2023-07-17', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.76}, {'date': '2023-07-18', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.51}, {'date': '2023-07-19', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.58, 'volume': 10050.25}, {'date': '2023-07-20', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10050.0}, {'date': '2023-07-21', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10049.75}, {'date': '2023-07-22', 'open': 110.54, 'high': 120.59, 'low': 100.5, 'close': 115.57, 'volume': 10049.5}, {'date': '2023-07-23', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.57, 'volume': 10049.26}, {'date': '2023-07-24', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10049.02}, {'date': '2023-07-25', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10048.78}, {'date': '2023-07-26', 'open': 110.53, 'high': 120.58, 'low': 100.49, 'close': 115.56, 'volume': 10048.54}, {'date': '2023-07-27', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.56, 'volume': 10048.31}, {'date': '2023-07-28', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.55, 'volume': 10048.08}, {'date': '2023-07-29', 'open': 110.53, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.85}, {'date': '2023-07-30', 'open': 110.52, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.62}, {'date': '2023-07-31', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.55, 'volume': 10047.39}, {'date': '2023-08-01', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.54, 'volume': 10047.17}, {'date': '2023-08-02', 'open': 110.52, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.95}, {'date': '2023-08-03', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.73}, {'date': '2023-08-04', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.53, 'volume': 10046.51}, {'date': '2023-08-05', 'open': 110.51, 'high': 120.56, 'low': 100.46, 'close': 115.53, 'volume': 10046.3}, {'date': '2023-08-06', 'open': 110.51, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10046.08}, {'date': '2023-08-07', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.87}, {'date': '2023-08-08', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.66}, {'date': '2023-08-09', 'open': 110.5, 'high': 120.55, 'low': 100.45, 'close': 115.52, 'volume': 10045.45}, {'date': '2023-08-10', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.25}, {'date': '2023-08-11', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.05}, {'date': '2023-08-12', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10044.84}, {'date': '2023-08-13', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.51, 'volume': 10044.64}, {'date': '2023-08-14', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.44}, {'date': '2023-08-15', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.25}, {'date': '2023-08-16', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.05}, {'date': '2023-08-17', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.5, 'volume': 10043.86}, {'date': '2023-08-18', 'open': 110.48, 'high': 120.52, 'low': 100.44, 'close': 115.5, 'volume': 10043.67}, {'date': '2023-08-19', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.48}, {'date': '2023-08-20', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.29}, {'date': '2023-08-21', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.1}, {'date': '2023-08-22', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.49, 'volume': 10042.92}, {'date': '2023-08-23', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.74}, {'date': '2023-08-24', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.55}, {'date': '2023-08-25', 'open': 110.47, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.37}, {'date': '2023-08-26', 'open': 110.46, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.19}, {'date': '2023-08-27', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10042.02}, {'date': '2023-08-28', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.84}, {'date': '2023-08-29', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.67}, {'date': '2023-08-30', 'open': 110.46, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.49}, {'date': '2023-08-31', 'open': 110.45, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.32}, {'date': '2023-09-01', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10041.15}, {'date': '2023-09-02', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.98}, {'date': '2023-09-03', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.82}, {'date': '2023-09-04', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.65}, {'date': '2023-09-05', 'open': 110.45, 'high': 120.49, 'low': 100.4, 'close': 115.47, 'volume': 10040.49}, {'date': '2023-09-06', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.32}, {'date': '2023-09-07', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.16}, {'date': '2023-09-08', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.0}, {'date': '2023-09-09', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.84}, {'date': '2023-09-10', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.68}, {'date': '2023-09-11', 'open': 110.43, 'high': 120.47, 'low': 100.4, 'close': 115.45, 'volume': 10039.53}, {'date': '2023-09-12', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.37}, {'date': '2023-09-13', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.22}, {'date': '2023-09-14', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.06}, {'date': '2023-09-15', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.91}, {'date': '2023-09-16', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.76}, {'date': '2023-09-17', 'open': 110.42, 'high': 120.46, 'low': 100.39, 'close': 115.44, 'volume': 10038.61}, {'date': '2023-09-18', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.46}, {'date': '2023-09-19', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.31}, {'date': '2023-09-20', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.17}, {'date': '2023-09-21', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.02}, {'date': '2023-09-22', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.44, 'volume': 10037.88}, {'date': '2023-09-23', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.74}, {'date': '2023-09-24', 'open': 110.41, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.59}, {'date': '2023-09-25', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.45}, {'date': '2023-09-26', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.31}, {'date': '2023-09-27', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.17}, {'date': '2023-09-28', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.43, 'volume': 10037.04}, {'date': '2023-09-29', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.9}, {'date': '2023-09-30', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.76}, {'date': '2023-10-01', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.63}, {'date': '2023-10-02', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.5}, {'date': '2023-10-03', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.36}, {'date': '2023-10-04', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.23}, {'date': '2023-10-05', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.1}, {'date': '2023-10-06', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.97}, {'date': '2023-10-07', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.84}, {'date': '2023-10-08', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.71}, {'date': '2023-10-09', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.59}, {'date': '2023-10-10', 'open': 110.39, 'high': 120.43, 'low': 100.35, 'close': 115.41, 'volume': 10035.46}, {'date': '2023-10-11', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.41, 'volume': 10035.34}, {'date': '2023-10-12', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.21}, {'date': '2023-10-13', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.09}, {'date': '2023-10-14', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.97}, {'date': '2023-10-15', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.84}, {'date': '2023-10-16', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.72}, {'date': '2023-10-17', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.6}, {'date': '2023-10-18', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.48}, {'date': '2023-10-19', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.36}, {'date': '2023-10-20', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.25}, {'date': '2023-10-21', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.13}, {'date': '2023-10-22', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.01}, {'date': '2023-10-23', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.9}, {'date': '2023-10-24', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.78}, {'date': '2023-10-25', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.67}, {'date': '2023-10-26', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.56}, {'date': '2023-10-27', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.44}, {'date': '2023-10-28', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.33}, {'date': '2023-10-29', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.22}, {'date': '2023-10-30', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.11}, {'date': '2023-10-31', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.0}, {'date': '2023-11-01', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.89}, {'date': '2023-11-02', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.79}, {'date': '2023-11-03', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.68}, {'date': '2023-11-04', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.37, 'volume': 10032.57}, {'date': '2023-11-05', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.47}, {'date': '2023-11-06', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.36}, {'date': '2023-11-07', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.26}, {'date': '2023-11-08', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.15}, {'date': '2023-11-09', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10032.05}, {'date': '2023-11-10', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.95}, {'date': '2023-11-11', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.85}, {'date': '2023-11-12', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.75}, {'date': '2023-11-13', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.65}, {'date': '2023-11-14', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.55}, {'date': '2023-11-15', 'open': 110.35, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.45}, {'date': '2023-11-16', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.35}, {'date': '2023-11-17', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.25}, {'date': '2023-11-18', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.15}, {'date': '2023-11-19', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.06}, {'date': '2023-11-20', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10030.96}, {'date': '2023-11-21', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.86}, {'date': '2023-11-22', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.77}, {'date': '2023-11-23', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.67}, {'date': '2023-11-24', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.58}, {'date': '2023-11-25', 'open': 110.34, 'high': 120.37, 'low': 100.3, 'close': 115.35, 'volume': 10030.49}, {'date': '2023-11-26', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.4}, {'date': '2023-11-27', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.3}, {'date': '2023-11-28', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.21}, {'date': '2023-11-29', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.12}, {'date': '2023-11-30', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.03}, {'date': '2023-12-01', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.94}, {'date': '2023-12-02', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.85}, {'date': '2023-12-03', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.76}, {'date': '2023-12-04', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.67}, {'date': '2023-12-05', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.59}, {'date': '2023-12-06', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.5}, {'date': '2023-12-07', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.41}, {'date': '2023-12-08', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.33}, {'date': '2023-12-09', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.24}, {'date': '2023-12-10', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.15}, {'date': '2023-12-11', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10029.07}, {'date': '2023-12-12', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.99}, {'date': '2023-12-13', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.9}, {'date': '2023-12-14', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.82}, {'date': '2023-12-15', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.74}, {'date': '2023-12-16', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.65}, {'date': '2023-12-17', 'open': 110.31, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.57}, {'date': '2023-12-18', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.49}, {'date': '2023-12-19', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.41}, {'date': '2023-12-20', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.33}, {'date': '2023-12-21', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.25}, {'date': '2023-12-22', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.17}, {'date': '2023-12-23', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.09}, {'date': '2023-12-24', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.01}, {'date': '2023-12-25', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10027.93}, {'date': '2023-12-26', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.86}, {'date': '2023-12-27', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.78}, {'date': '2023-12-28', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.7}, {'date': '2023-12-29', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.62}, {'date': '2023-12-30', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.55}, {'date': '2023-12-31', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.47}, {'date': '2024-01-01', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.4}, {'date': '2024-01-02', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.32}, {'date': '2024-01-03', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.25}, {'date': '2024-01-04', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.17}, {'date': '2024-01-05', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.1}, {'date': '2024-01-06', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10027.03}, {'date': '2024-01-07', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.95}, {'date': '2024-01-08', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.88}, {'date': '2024-01-09', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.81}, {'date': '2024-01-10', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.74}, {'date': '2024-01-11', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.67}, {'date': '2024-01-12', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.6}, {'date': '2024-01-13', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.53}, {'date': '2024-01-14', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.46}, {'date': '2024-01-15', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.39}, {'date': '2024-01-16', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.32}, {'date': '2024-01-17', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.25}, {'date': '2024-01-18', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.18}, {'date': '2024-01-19', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.11}, {'date': '2024-01-20', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.04}, {'date': '2024-01-21', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.97}, {'date': '2024-01-22', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.91}, {'date': '2024-01-23', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.84}, {'date': '2024-01-24', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.77}, {'date': '2024-01-25', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.71}, {'date': '2024-01-26', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.64}, {'date': '2024-01-27', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.58}, {'date': '2024-01-28', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.51}, {'date': '2024-01-29', 'open': 110.28, 'high': 120.31, 'low': 100.25, 'close': 115.29, 'volume': 10025.45}, {'date': '2024-01-30', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.38}, {'date': '2024-01-31', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.32}, {'date': '2024-02-01', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.25}, {'date': '2024-02-02', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.19}, {'date': '2024-02-03', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.13}, {'date': '2024-02-04', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.06}, {'date': '2024-02-05', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.0}, {'date': '2024-02-06', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.94}, {'date': '2024-02-07', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.88}, {'date': '2024-02-08', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.81}, {'date': '2024-02-09', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.75}, {'date': '2024-02-10', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.69}, {'date': '2024-02-11', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.63}, {'date': '2024-02-12', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.57}, {'date': '2024-02-13', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.51}, {'date': '2024-02-14', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.45}, {'date': '2024-02-15', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.39}, {'date': '2024-02-16', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.33}, {'date': '2024-02-17', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.27}, {'date': '2024-02-18', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.21}, {'date': '2024-02-19', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.15}, {'date': '2024-02-20', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.1}, {'date': '2024-02-21', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.04}, {'date': '2024-02-22', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.98}, {'date': '2024-02-23', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.92}, {'date': '2024-02-24', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.87}, {'date': '2024-02-25', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.81}, {'date': '2024-02-26', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.75}, {'date': '2024-02-27', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.7}, {'date': '2024-02-28', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.64}, {'date': '2024-02-29', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.58}, {'date': '2024-03-01', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.53}, {'date': '2024-03-02', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.47}, {'date': '2024-03-03', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.42}, {'date': '2024-03-04', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.36}, {'date': '2024-03-05', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.31}, {'date': '2024-03-06', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.26}, {'date': '2024-03-07', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.2}, {'date': '2024-03-08', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.15}, {'date': '2024-03-09', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.09}, {'date': '2024-03-10', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10023.04}, {'date': '2024-03-11', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.99}, {'date': '2024-03-12', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.94}, {'date': '2024-03-13', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.88}, {'date': '2024-03-14', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.83}, {'date': '2024-03-15', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.78}, {'date': '2024-03-16', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.73}, {'date': '2024-03-17', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.68}, {'date': '2024-03-18', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.62}, {'date': '2024-03-19', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.57}, {'date': '2024-03-20', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.52}, {'date': '2024-03-21', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.47}, {'date': '2024-03-22', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.42}, {'date': '2024-03-23', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.37}, {'date': '2024-03-24', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.32}, {'date': '2024-03-25', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.27}, {'date': '2024-03-26', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.22}, {'date': '2024-03-27', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.17}, {'date': '2024-03-28', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.12}, {'date': '2024-03-29', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.08}, {'date': '2024-03-30', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.03}, {'date': '2024-03-31', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.98}, {'date': '2024-04-01', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.93}, {'date': '2024-04-02', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.88}, {'date': '2024-04-03', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.83}, {'date': '2024-04-04', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.79}, {'date': '2024-04-05', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.74}, {'date': '2024-04-06', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.69}, {'date': '2024-04-07', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.65}, {'date': '2024-04-08', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.6}, {'date': '2024-04-09', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.55}, {'date': '2024-04-10', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.51}, {'date': '2024-04-11', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.46}, {'date': '2024-04-12', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.41}, {'date': '2024-04-13', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.37}, {'date': '2024-04-14', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.32}, {'date': '2024-04-15', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.24, 'volume': 10021.28}, {'date': '2024-04-16', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.23}, {'date': '2024-04-17', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.19}, {'date': '2024-04-18', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.14}, {'date': '2024-04-19', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.1}, {'date': '2024-04-20', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.05}, {'date': '2024-04-21', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.01}, {'date': '2024-04-22', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.96}, {'date': '2024-04-23', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.92}, {'date': '2024-04-24', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.88}, {'date': '2024-04-25', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.83}, {'date': '2024-04-26', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.79}, {'date': '2024-04-27', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.75}, {'date': '2024-04-28', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.7}, {'date': '2024-04-29', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.66}, {'date': '2024-04-30', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.62}, {'date': '2024-05-01', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.58}, {'date': '2024-05-02', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.53}, {'date': '2024-05-03', 'open': 110.23, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.49}, {'date': '2024-05-04', 'open': 110.22, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.45}, {'date': '2024-05-05', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.41}, {'date': '2024-05-06', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.37}, {'date': '2024-05-07', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.33}, {'date': '2024-05-08', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.28}, {'date': '2024-05-09', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.24}, {'date': '2024-05-10', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.2}, {'date': '2024-05-11', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.16}, {'date': '2024-05-12', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.12}, {'date': '2024-05-13', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.08}, {'date': '2024-05-14', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.04}, {'date': '2024-05-15', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.0}, {'date': '2024-05-16', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.96}, {'date': '2024-05-17', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.92}, {'date': '2024-05-18', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.88}, {'date': '2024-05-19', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.84}, {'date': '2024-05-20', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.8}, {'date': '2024-05-21', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.76}, {'date': '2024-05-22', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.72}, {'date': '2024-05-23', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.69}, {'date': '2024-05-24', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.65}, {'date': '2024-05-25', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.61}, {'date': '2024-05-26', 'open': 110.22, 'high': 120.23, 'low': 100.2, 'close': 115.23, 'volume': 10019.57}, {'date': '2024-05-27', 'open': 110.21, 'high': 120.23, 'low': 100.2, 'close': 115.22, 'volume': 10019.53}, {'date': '2024-05-28', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.49}, {'date': '2024-05-29', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.46}, {'date': '2024-05-30', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.42}, {'date': '2024-05-31', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.38}, {'date': '2024-06-01', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.34}, {'date': '2024-06-02', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.31}, {'date': '2024-06-03', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.27}, {'date': '2024-06-04', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.23}, {'date': '2024-06-05', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.19}, {'date': '2024-06-06', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.16}, {'date': '2024-06-07', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.12}, {'date': '2024-06-08', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.08}, {'date': '2024-06-09', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.05}, {'date': '2024-06-10', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.01}, {'date': '2024-06-11', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.98}, {'date': '2024-06-12', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.94}, {'date': '2024-06-13', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.9}, {'date': '2024-06-14', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.87}, {'date': '2024-06-15', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.83}, {'date': '2024-06-16', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.8}, {'date': '2024-06-17', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.76}, {'date': '2024-06-18', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.22, 'volume': 10018.73}, {'date': '2024-06-19', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.69}, {'date': '2024-06-20', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.66}, {'date': '2024-06-21', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.62}, {'date': '2024-06-22', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.59}, {'date': '2024-06-23', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.55}, {'date': '2024-06-24', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.52}, {'date': '2024-06-25', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.48}, {'date': '2024-06-26', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.45}, {'date': '2024-06-27', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.42}, {'date': '2024-06-28', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.38}, {'date': '2024-06-29', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.35}, {'date': '2024-06-30', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.32}, {'date': '2024-07-01', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.28}, {'date': '2024-07-02', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.25}, {'date': '2024-07-03', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.21}, {'date': '2024-07-04', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.18}, {'date': '2024-07-05', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.15}, {'date': '2024-07-06', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.12}, {'date': '2024-07-07', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.08}, {'date': '2024-07-08', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.05}, {'date': '2024-07-09', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.02}, {'date': '2024-07-10', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.99}, {'date': '2024-07-11', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.95}, {'date': '2024-07-12', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.92}, {'date': '2024-07-13', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.89}, {'date': '2024-07-14', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.86}, {'date': '2024-07-15', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.83}, {'date': '2024-07-16', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.79}, {'date': '2024-07-17', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.76}, {'date': '2024-07-18', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.73}, {'date': '2024-07-19', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.7}, {'date': '2024-07-20', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.67}, {'date': '2024-07-21', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.64}, {'date': '2024-07-22', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.61}, {'date': '2024-07-23', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.57}, {'date': '2024-07-24', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.54}, {'date': '2024-07-25', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.51}, {'date': '2024-07-26', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.48}, {'date': '2024-07-27', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.45}, {'date': '2024-07-28', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.42}, {'date': '2024-07-29', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.39}, {'date': '2024-07-30', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.36}, {'date': '2024-07-31', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.33}, {'date': '2024-08-01', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.3}, {'date': '2024-08-02', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.27}, {'date': '2024-08-03', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.24}, {'date': '2024-08-04', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.21}, {'date': '2024-08-05', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.18}, {'date': '2024-08-06', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.15}, {'date': '2024-08-07', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.12}, {'date': '2024-08-08', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.09}, {'date': '2024-08-09', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.06}, {'date': '2024-08-10', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.04}, {'date': '2024-08-11', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.01}, {'date': '2024-08-12', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10016.98}, {'date': '2024-08-13', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.95}, {'date': '2024-08-14', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.92}, {'date': '2024-08-15', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.89}, {'date': '2024-08-16', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.86}, {'date': '2024-08-17', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.84}, {'date': '2024-08-18', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.81}, {'date': '2024-08-19', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.78}, {'date': '2024-08-20', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.75}, {'date': '2024-08-21', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.72}, {'date': '2024-08-22', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.69}, {'date': '2024-08-23', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.67}, {'date': '2024-08-24', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.64}, {'date': '2024-08-25', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.61}, {'date': '2024-08-26', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.58}, {'date': '2024-08-27', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.56}, {'date': '2024-08-28', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.53}, {'date': '2024-08-29', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.5}, {'date': '2024-08-30', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.47}, {'date': '2024-08-31', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.45}, {'date': '2024-09-01', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.42}, {'date': '2024-09-02', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.39}, {'date': '2024-09-03', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.37}, {'date': '2024-09-04', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.34}, {'date': '2024-09-05', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.31}, {'date': '2024-09-06', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.29}, {'date': '2024-09-07', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.26}, {'date': '2024-09-08', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.23}, {'date': '2024-09-09', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.21}, {'date': '2024-09-10', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.18}, {'date': '2024-09-11', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.16}, {'date': '2024-09-12', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.13}, {'date': '2024-09-13', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.1}, {'date': '2024-09-14', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.08}, {'date': '2024-09-15', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.05}, {'date': '2024-09-16', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.03}, {'date': '2024-09-17', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.0}, {'date': '2024-09-18', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.97}, {'date': '2024-09-19', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.95}, {'date': '2024-09-20', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.92}, {'date': '2024-09-21', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.9}, {'date': '2024-09-22', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.87}, {'date': '2024-09-23', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.85}, {'date': '2024-09-24', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.82}, {'date': '2024-09-25', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.8}, {'date': '2024-09-26', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.77}, {'date': '2024-09-27', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.75}, {'date': '2024-09-28', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.72}, {'date': '2024-09-29', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.7}, {'date': '2024-09-30', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.67}, {'date': '2024-10-01', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.65}, {'date': '2024-10-02', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.62}, {'date': '2024-10-03', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.6}, {'date': '2024-10-04', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.58}, {'date': '2024-10-05', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.55}, {'date': '2024-10-06', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.53}, {'date': '2024-10-07', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.5}, {'date': '2024-10-08', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.48}, {'date': '2024-10-09', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.46}, {'date': '2024-10-10', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.43}, {'date': '2024-10-11', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.41}, {'date': '2024-10-12', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.38}, {'date': '2024-10-13', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.36}, {'date': '2024-10-14', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.34}, {'date': '2024-10-15', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.31}, {'date': '2024-10-16', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.29}, {'date': '2024-10-17', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.27}, {'date': '2024-10-18', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.24}, {'date': '2024-10-19', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.22}, {'date': '2024-10-20', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.2}, {'date': '2024-10-21', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.17}, {'date': '2024-10-22', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.15}, {'date': '2024-10-23', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.13}, {'date': '2024-10-24', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.11}, {'date': '2024-10-25', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.08}, {'date': '2024-10-26', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.06}, {'date': '2024-10-27', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.04}, {'date': '2024-10-28', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.02}, {'date': '2024-10-29', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.99}, {'date': '2024-10-30', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.97}, {'date': '2024-10-31', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.95}, {'date': '2024-11-01', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.93}, {'date': '2024-11-02', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.9}, {'date': '2024-11-03', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.88}, {'date': '2024-11-04', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.86}, {'date': '2024-11-05', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.84}, {'date': '2024-11-06', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.81}, {'date': '2024-11-07', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.79}, {'date': '2024-11-08', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.77}, {'date': '2024-11-09', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.75}, {'date': '2024-11-10', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.73}, {'date': '2024-11-11', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.71}, {'date': '2024-11-12', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.68}, {'date': '2024-11-13', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.66}, {'date': '2024-11-14', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.64}, {'date': '2024-11-15', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.62}, {'date': '2024-11-16', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.6}, {'date': '2024-11-17', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.58}, {'date': '2024-11-18', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.56}, {'date': '2024-11-19', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.53}, {'date': '2024-11-20', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.51}, {'date': '2024-11-21', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.49}, {'date': '2024-11-22', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.47}, {'date': '2024-11-23', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.45}, {'date': '2024-11-24', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.43}, {'date': '2024-11-25', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.41}, {'date': '2024-11-26', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.39}, {'date': '2024-11-27', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.37}, {'date': '2024-11-28', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.35}, {'date': '2024-11-29', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.33}, {'date': '2024-11-30', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.31}, {'date': '2024-12-01', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.29}, {'date': '2024-12-02', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.27}, {'date': '2024-12-03', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.25}, {'date': '2024-12-04', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.22}, {'date': '2024-12-05', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.2}, {'date': '2024-12-06', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.18}, {'date': '2024-12-07', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.16}, {'date': '2024-12-08', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.14}, {'date': '2024-12-09', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.12}, {'date': '2024-12-10', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.1}, {'date': '2024-12-11', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.08}, {'date': '2024-12-12', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.06}, {'date': '2024-12-13', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.04}, {'date': '2024-12-14', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.03}, {'date': '2024-12-15', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.01}, {'date': '2024-12-16', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.99}, {'date': '2024-12-17', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.97}, {'date': '2024-12-18', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.95}, {'date': '2024-12-19', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.93}, {'date': '2024-12-20', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.91}, {'date': '2024-12-21', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.89}, {'date': '2024-12-22', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.87}, {'date': '2024-12-23', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.85}, {'date': '2024-12-24', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.83}, {'date': '2024-12-25', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.81}, {'date': '2024-12-26', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.79}, {'date': '2024-12-27', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.77}, {'date': '2024-12-28', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.76}, {'date': '2024-12-29', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.74}, {'date': '2024-12-30', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.72}, {'date': '2024-12-31', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.7}, {'date': '2025-01-01', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.68}, {'date': '2025-01-02', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.66}, {'date': '2025-01-03', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.64}, {'date': '2025-01-04', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.62}, {'date': '2025-01-05', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.61}, {'date': '2025-01-06', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.59}, {'date': '2025-01-07', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.57}, {'date': '2025-01-08', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.55}, {'date': '2025-01-09', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.53}, {'date': '2025-01-10', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.51}, {'date': '2025-01-11', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.16, 'volume': 10013.5}, {'date': '2025-01-12', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.48}, {'date': '2025-01-13', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.46}, {'date': '2025-01-14', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.44}, {'date': '2025-01-15', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.42}, {'date': '2025-01-16', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.4}, {'date': '2025-01-17', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.39}, {'date': '2025-01-18', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.37}, {'date': '2025-01-19', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.35}, {'date': '2025-01-20', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.33}, {'date': '2025-01-21', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.32}, {'date': '2025-01-22', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.3}, {'date': '2025-01-23', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.28}, {'date': '2025-01-24', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.26}, {'date': '2025-01-25', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.25}, {'date': '2025-01-26', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.23}, {'date': '2025-01-27', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.21}, {'date': '2025-01-28', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.19}, {'date': '2025-01-29', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.18}, {'date': '2025-01-30', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.16}, {'date': '2025-01-31', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.14}, {'date': '2025-02-01', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.12}, {'date': '2025-02-02', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.11}, {'date': '2025-02-03', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.09}, {'date': '2025-02-04', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.07}, {'date': '2025-02-05', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.05}, {'date': '2025-02-06', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.04}, {'date': '2025-02-07', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.02}, {'date': '2025-02-08', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.0}, {'date': '2025-02-09', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.99}, {'date': '2025-02-10', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.97}, {'date': '2025-02-11', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.95}, {'date': '2025-02-12', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.94}, {'date': '2025-02-13', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.92}, {'date': '2025-02-14', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.9}, {'date': '2025-02-15', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.89}, {'date': '2025-02-16', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.87}, {'date': '2025-02-17', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.85}, {'date': '2025-02-18', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.84}, {'date': '2025-02-19', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.82}, {'date': '2025-02-20', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.8}, {'date': '2025-02-21', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.79}, {'date': '2025-02-22', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.77}, {'date': '2025-02-23', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.76}, {'date': '2025-02-24', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.74}, {'date': '2025-02-25', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.72}, {'date': '2025-02-26', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.71}, {'date': '2025-02-27', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.69}, {'date': '2025-02-28', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.67}, {'date': '2025-03-01', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.66}, {'date': '2025-03-02', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.64}, {'date': '2025-03-03', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.63}, {'date': '2025-03-04', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.61}, {'date': '2025-03-05', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.59}, {'date': '2025-03-06', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.58}, {'date': '2025-03-07', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.56}, {'date': '2025-03-08', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.55}, {'date': '2025-03-09', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.53}, {'date': '2025-03-10', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.52}, {'date': '2025-03-11', 'open': 110.14, 'high': 120.15, 'low': 100.12, 'close': 115.14, 'volume': 10012.5}], factors_data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}, {'date': '2023-01-07', 'open': 128.33, 'high': 140.0, 'low': 116.67, 'close': 134.17, 'volume': 11666.67}, {'date': '2023-01-08', 'open': 125.71, 'high': 137.14, 'low': 114.29, 'close': 131.43, 'volume': 11428.57}, {'date': '2023-01-09', 'open': 123.75, 'high': 135.0, 'low': 112.5, 'close': 129.38, 'volume': 11250.0}, {'date': '2023-01-10', 'open': 122.22, 'high': 133.33, 'low': 111.11, 'close': 127.78, 'volume': 11111.11}, {'date': '2023-01-11', 'open': 121.0, 'high': 132.0, 'low': 110.0, 'close': 126.5, 'volume': 11000.0}, {'date': '2023-01-12', 'open': 120.0, 'high': 130.91, 'low': 109.09, 'close': 125.45, 'volume': 10909.09}, {'date': '2023-01-13', 'open': 119.17, 'high': 130.0, 'low': 108.33, 'close': 124.58, 'volume': 10833.33}, {'date': '2023-01-14', 'open': 118.46, 'high': 129.23, 'low': 107.69, 'close': 123.85, 'volume': 10769.23}, {'date': '2023-01-15', 'open': 117.86, 'high': 128.57, 'low': 107.14, 'close': 123.21, 'volume': 10714.29}, {'date': '2023-01-16', 'open': 117.33, 'high': 128.0, 'low': 106.67, 'close': 122.67, 'volume': 10666.67}, {'date': '2023-01-17', 'open': 116.88, 'high': 127.5, 'low': 106.25, 'close': 122.19, 'volume': 10625.0}, {'date': '2023-01-18', 'open': 116.47, 'high': 127.06, 'low': 105.88, 'close': 121.76, 'volume': 10588.24}, {'date': '2023-01-19', 'open': 116.11, 'high': 126.67, 'low': 105.56, 'close': 121.39, 'volume': 10555.56}, {'date': '2023-01-20', 'open': 115.79, 'high': 126.32, 'low': 105.26, 'close': 121.05, 'volume': 10526.32}, {'date': '2023-01-21', 'open': 115.5, 'high': 126.0, 'low': 105.0, 'close': 120.75, 'volume': 10500.0}, {'date': '2023-01-22', 'open': 115.24, 'high': 125.71, 'low': 104.76, 'close': 120.48, 'volume': 10476.19}, {'date': '2023-01-23', 'open': 115.0, 'high': 125.45, 'low': 104.55, 'close': 120.23, 'volume': 10454.55}, {'date': '2023-01-24', 'open': 114.78, 'high': 125.22, 'low': 104.35, 'close': 120.0, 'volume': 10434.78}, {'date': '2023-01-25', 'open': 114.58, 'high': 125.0, 'low': 104.17, 'close': 119.79, 'volume': 10416.67}, {'date': '2023-01-26', 'open': 114.4, 'high': 124.8, 'low': 104.0, 'close': 119.6, 'volume': 10400.0}, {'date': '2023-01-27', 'open': 114.23, 'high': 124.62, 'low': 103.85, 'close': 119.42, 'volume': 10384.62}, {'date': '2023-01-28', 'open': 114.07, 'high': 124.44, 'low': 103.7, 'close': 119.26, 'volume': 10370.37}, {'date': '2023-01-29', 'open': 113.93, 'high': 124.29, 'low': 103.57, 'close': 119.11, 'volume': 10357.14}, {'date': '2023-01-30', 'open': 113.79, 'high': 124.14, 'low': 103.45, 'close': 118.97, 'volume': 10344.83}, {'date': '2023-01-31', 'open': 113.67, 'high': 124.0, 'low': 103.33, 'close': 118.83, 'volume': 10333.33}, {'date': '2023-02-01', 'open': 113.55, 'high': 123.87, 'low': 103.23, 'close': 118.71, 'volume': 10322.58}, {'date': '2023-02-02', 'open': 113.44, 'high': 123.75, 'low': 103.12, 'close': 118.59, 'volume': 10312.5}, {'date': '2023-02-03', 'open': 113.33, 'high': 123.64, 'low': 103.03, 'close': 118.48, 'volume': 10303.03}, {'date': '2023-02-04', 'open': 113.24, 'high': 123.53, 'low': 102.94, 'close': 118.38, 'volume': 10294.12}, {'date': '2023-02-05', 'open': 113.14, 'high': 123.43, 'low': 102.86, 'close': 118.29, 'volume': 10285.71}, {'date': '2023-02-06', 'open': 113.06, 'high': 123.33, 'low': 102.78, 'close': 118.19, 'volume': 10277.78}, {'date': '2023-02-07', 'open': 112.97, 'high': 123.24, 'low': 102.7, 'close': 118.11, 'volume': 10270.27}, {'date': '2023-02-08', 'open': 112.89, 'high': 123.16, 'low': 102.63, 'close': 118.03, 'volume': 10263.16}, {'date': '2023-02-09', 'open': 112.82, 'high': 123.08, 'low': 102.56, 'close': 117.95, 'volume': 10256.41}, {'date': '2023-02-10', 'open': 112.75, 'high': 123.0, 'low': 102.5, 'close': 117.87, 'volume': 10250.0}, {'date': '2023-02-11', 'open': 112.68, 'high': 122.93, 'low': 102.44, 'close': 117.8, 'volume': 10243.9}, {'date': '2023-02-12', 'open': 112.62, 'high': 122.86, 'low': 102.38, 'close': 117.74, 'volume': 10238.1}, {'date': '2023-02-13', 'open': 112.56, 'high': 122.79, 'low': 102.33, 'close': 117.67, 'volume': 10232.56}, {'date': '2023-02-14', 'open': 112.5, 'high': 122.73, 'low': 102.27, 'close': 117.61, 'volume': 10227.27}, {'date': '2023-02-15', 'open': 112.44, 'high': 122.67, 'low': 102.22, 'close': 117.56, 'volume': 10222.22}, {'date': '2023-02-16', 'open': 112.39, 'high': 122.61, 'low': 102.17, 'close': 117.5, 'volume': 10217.39}, {'date': '2023-02-17', 'open': 112.34, 'high': 122.55, 'low': 102.13, 'close': 117.45, 'volume': 10212.77}, {'date': '2023-02-18', 'open': 112.29, 'high': 122.5, 'low': 102.08, 'close': 117.4, 'volume': 10208.33}, {'date': '2023-02-19', 'open': 112.24, 'high': 122.45, 'low': 102.04, 'close': 117.35, 'volume': 10204.08}, {'date': '2023-02-20', 'open': 112.2, 'high': 122.4, 'low': 102.0, 'close': 117.3, 'volume': 10200.0}, {'date': '2023-02-21', 'open': 112.16, 'high': 122.35, 'low': 101.96, 'close': 117.25, 'volume': 10196.08}, {'date': '2023-02-22', 'open': 112.12, 'high': 122.31, 'low': 101.92, 'close': 117.21, 'volume': 10192.31}, {'date': '2023-02-23', 'open': 112.08, 'high': 122.26, 'low': 101.89, 'close': 117.17, 'volume': 10188.68}, {'date': '2023-02-24', 'open': 112.04, 'high': 122.22, 'low': 101.85, 'close': 117.13, 'volume': 10185.19}, {'date': '2023-02-25', 'open': 112.0, 'high': 122.18, 'low': 101.82, 'close': 117.09, 'volume': 10181.82}, {'date': '2023-02-26', 'open': 111.96, 'high': 122.14, 'low': 101.79, 'close': 117.05, 'volume': 10178.57}, {'date': '2023-02-27', 'open': 111.93, 'high': 122.11, 'low': 101.75, 'close': 117.02, 'volume': 10175.44}, {'date': '2023-02-28', 'open': 111.9, 'high': 122.07, 'low': 101.72, 'close': 116.98, 'volume': 10172.41}, {'date': '2023-03-01', 'open': 111.86, 'high': 122.03, 'low': 101.69, 'close': 116.95, 'volume': 10169.49}, {'date': '2023-03-02', 'open': 111.83, 'high': 122.0, 'low': 101.67, 'close': 116.92, 'volume': 10166.67}, {'date': '2023-03-03', 'open': 111.8, 'high': 121.97, 'low': 101.64, 'close': 116.89, 'volume': 10163.93}, {'date': '2023-03-04', 'open': 111.77, 'high': 121.94, 'low': 101.61, 'close': 116.85, 'volume': 10161.29}, {'date': '2023-03-05', 'open': 111.75, 'high': 121.9, 'low': 101.59, 'close': 116.83, 'volume': 10158.73}, {'date': '2023-03-06', 'open': 111.72, 'high': 121.88, 'low': 101.56, 'close': 116.8, 'volume': 10156.25}, {'date': '2023-03-07', 'open': 111.69, 'high': 121.85, 'low': 101.54, 'close': 116.77, 'volume': 10153.85}, {'date': '2023-03-08', 'open': 111.67, 'high': 121.82, 'low': 101.52, 'close': 116.74, 'volume': 10151.52}, {'date': '2023-03-09', 'open': 111.64, 'high': 121.79, 'low': 101.49, 'close': 116.72, 'volume': 10149.25}, {'date': '2023-03-10', 'open': 111.62, 'high': 121.76, 'low': 101.47, 'close': 116.69, 'volume': 10147.06}, {'date': '2023-03-11', 'open': 111.59, 'high': 121.74, 'low': 101.45, 'close': 116.67, 'volume': 10144.93}, {'date': '2023-03-12', 'open': 111.57, 'high': 121.71, 'low': 101.43, 'close': 116.64, 'volume': 10142.86}, {'date': '2023-03-13', 'open': 111.55, 'high': 121.69, 'low': 101.41, 'close': 116.62, 'volume': 10140.85}, {'date': '2023-03-14', 'open': 111.53, 'high': 121.67, 'low': 101.39, 'close': 116.6, 'volume': 10138.89}, {'date': '2023-03-15', 'open': 111.51, 'high': 121.64, 'low': 101.37, 'close': 116.58, 'volume': 10136.99}, {'date': '2023-03-16', 'open': 111.49, 'high': 121.62, 'low': 101.35, 'close': 116.55, 'volume': 10135.14}, {'date': '2023-03-17', 'open': 111.47, 'high': 121.6, 'low': 101.33, 'close': 116.53, 'volume': 10133.33}, {'date': '2023-03-18', 'open': 111.45, 'high': 121.58, 'low': 101.32, 'close': 116.51, 'volume': 10131.58}, {'date': '2023-03-19', 'open': 111.43, 'high': 121.56, 'low': 101.3, 'close': 116.49, 'volume': 10129.87}, {'date': '2023-03-20', 'open': 111.41, 'high': 121.54, 'low': 101.28, 'close': 116.47, 'volume': 10128.21}, {'date': '2023-03-21', 'open': 111.39, 'high': 121.52, 'low': 101.27, 'close': 116.46, 'volume': 10126.58}, {'date': '2023-03-22', 'open': 111.38, 'high': 121.5, 'low': 101.25, 'close': 116.44, 'volume': 10125.0}, {'date': '2023-03-23', 'open': 111.36, 'high': 121.48, 'low': 101.23, 'close': 116.42, 'volume': 10123.46}, {'date': '2023-03-24', 'open': 111.34, 'high': 121.46, 'low': 101.22, 'close': 116.4, 'volume': 10121.95}, {'date': '2023-03-25', 'open': 111.33, 'high': 121.45, 'low': 101.2, 'close': 116.39, 'volume': 10120.48}, {'date': '2023-03-26', 'open': 111.31, 'high': 121.43, 'low': 101.19, 'close': 116.37, 'volume': 10119.05}, {'date': '2023-03-27', 'open': 111.29, 'high': 121.41, 'low': 101.18, 'close': 116.35, 'volume': 10117.65}, {'date': '2023-03-28', 'open': 111.28, 'high': 121.4, 'low': 101.16, 'close': 116.34, 'volume': 10116.28}, {'date': '2023-03-29', 'open': 111.26, 'high': 121.38, 'low': 101.15, 'close': 116.32, 'volume': 10114.94}, {'date': '2023-03-30', 'open': 111.25, 'high': 121.36, 'low': 101.14, 'close': 116.31, 'volume': 10113.64}, {'date': '2023-03-31', 'open': 111.24, 'high': 121.35, 'low': 101.12, 'close': 116.29, 'volume': 10112.36}, {'date': '2023-04-01', 'open': 111.22, 'high': 121.33, 'low': 101.11, 'close': 116.28, 'volume': 10111.11}, {'date': '2023-04-02', 'open': 111.21, 'high': 121.32, 'low': 101.1, 'close': 116.26, 'volume': 10109.89}, {'date': '2023-04-03', 'open': 111.2, 'high': 121.3, 'low': 101.09, 'close': 116.25, 'volume': 10108.7}, {'date': '2023-04-04', 'open': 111.18, 'high': 121.29, 'low': 101.08, 'close': 116.24, 'volume': 10107.53}, {'date': '2023-04-05', 'open': 111.17, 'high': 121.28, 'low': 101.06, 'close': 116.22, 'volume': 10106.38}, {'date': '2023-04-06', 'open': 111.16, 'high': 121.26, 'low': 101.05, 'close': 116.21, 'volume': 10105.26}, {'date': '2023-04-07', 'open': 111.15, 'high': 121.25, 'low': 101.04, 'close': 116.2, 'volume': 10104.17}, {'date': '2023-04-08', 'open': 111.13, 'high': 121.24, 'low': 101.03, 'close': 116.19, 'volume': 10103.09}, {'date': '2023-04-09', 'open': 111.12, 'high': 121.22, 'low': 101.02, 'close': 116.17, 'volume': 10102.04}, {'date': '2023-04-10', 'open': 111.11, 'high': 121.21, 'low': 101.01, 'close': 116.16, 'volume': 10101.01}, {'date': '2023-04-11', 'open': 111.1, 'high': 121.2, 'low': 101.0, 'close': 116.15, 'volume': 10100.0}, {'date': '2023-04-12', 'open': 111.09, 'high': 121.19, 'low': 100.99, 'close': 116.14, 'volume': 10099.01}, {'date': '2023-04-13', 'open': 111.08, 'high': 121.18, 'low': 100.98, 'close': 116.13, 'volume': 10098.04}, {'date': '2023-04-14', 'open': 111.07, 'high': 121.17, 'low': 100.97, 'close': 116.12, 'volume': 10097.09}, {'date': '2023-04-15', 'open': 111.06, 'high': 121.15, 'low': 100.96, 'close': 116.11, 'volume': 10096.15}, {'date': '2023-04-16', 'open': 111.05, 'high': 121.14, 'low': 100.95, 'close': 116.1, 'volume': 10095.24}, {'date': '2023-04-17', 'open': 111.04, 'high': 121.13, 'low': 100.94, 'close': 116.08, 'volume': 10094.34}, {'date': '2023-04-18', 'open': 111.03, 'high': 121.12, 'low': 100.93, 'close': 116.07, 'volume': 10093.46}, {'date': '2023-04-19', 'open': 111.02, 'high': 121.11, 'low': 100.93, 'close': 116.06, 'volume': 10092.59}, {'date': '2023-04-20', 'open': 111.01, 'high': 121.1, 'low': 100.92, 'close': 116.06, 'volume': 10091.74}, {'date': '2023-04-21', 'open': 111.0, 'high': 121.09, 'low': 100.91, 'close': 116.05, 'volume': 10090.91}, {'date': '2023-04-22', 'open': 110.99, 'high': 121.08, 'low': 100.9, 'close': 116.04, 'volume': 10090.09}, {'date': '2023-04-23', 'open': 110.98, 'high': 121.07, 'low': 100.89, 'close': 116.03, 'volume': 10089.29}, {'date': '2023-04-24', 'open': 110.97, 'high': 121.06, 'low': 100.88, 'close': 116.02, 'volume': 10088.5}, {'date': '2023-04-25', 'open': 110.96, 'high': 121.05, 'low': 100.88, 'close': 116.01, 'volume': 10087.72}, {'date': '2023-04-26', 'open': 110.96, 'high': 121.04, 'low': 100.87, 'close': 116.0, 'volume': 10086.96}, {'date': '2023-04-27', 'open': 110.95, 'high': 121.03, 'low': 100.86, 'close': 115.99, 'volume': 10086.21}, {'date': '2023-04-28', 'open': 110.94, 'high': 121.03, 'low': 100.85, 'close': 115.98, 'volume': 10085.47}, {'date': '2023-04-29', 'open': 110.93, 'high': 121.02, 'low': 100.85, 'close': 115.97, 'volume': 10084.75}, {'date': '2023-04-30', 'open': 110.92, 'high': 121.01, 'low': 100.84, 'close': 115.97, 'volume': 10084.03}, {'date': '2023-05-01', 'open': 110.92, 'high': 121.0, 'low': 100.83, 'close': 115.96, 'volume': 10083.33}, {'date': '2023-05-02', 'open': 110.91, 'high': 120.99, 'low': 100.83, 'close': 115.95, 'volume': 10082.64}, {'date': '2023-05-03', 'open': 110.9, 'high': 120.98, 'low': 100.82, 'close': 115.94, 'volume': 10081.97}, {'date': '2023-05-04', 'open': 110.89, 'high': 120.98, 'low': 100.81, 'close': 115.93, 'volume': 10081.3}, {'date': '2023-05-05', 'open': 110.89, 'high': 120.97, 'low': 100.81, 'close': 115.93, 'volume': 10080.65}, {'date': '2023-05-06', 'open': 110.88, 'high': 120.96, 'low': 100.8, 'close': 115.92, 'volume': 10080.0}, {'date': '2023-05-07', 'open': 110.87, 'high': 120.95, 'low': 100.79, 'close': 115.91, 'volume': 10079.37}, {'date': '2023-05-08', 'open': 110.87, 'high': 120.94, 'low': 100.79, 'close': 115.91, 'volume': 10078.74}, {'date': '2023-05-09', 'open': 110.86, 'high': 120.94, 'low': 100.78, 'close': 115.9, 'volume': 10078.12}, {'date': '2023-05-10', 'open': 110.85, 'high': 120.93, 'low': 100.78, 'close': 115.89, 'volume': 10077.52}, {'date': '2023-05-11', 'open': 110.85, 'high': 120.92, 'low': 100.77, 'close': 115.88, 'volume': 10076.92}, {'date': '2023-05-12', 'open': 110.84, 'high': 120.92, 'low': 100.76, 'close': 115.88, 'volume': 10076.34}, {'date': '2023-05-13', 'open': 110.83, 'high': 120.91, 'low': 100.76, 'close': 115.87, 'volume': 10075.76}, {'date': '2023-05-14', 'open': 110.83, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10075.19}, {'date': '2023-05-15', 'open': 110.82, 'high': 120.9, 'low': 100.75, 'close': 115.86, 'volume': 10074.63}, {'date': '2023-05-16', 'open': 110.81, 'high': 120.89, 'low': 100.74, 'close': 115.85, 'volume': 10074.07}, {'date': '2023-05-17', 'open': 110.81, 'high': 120.88, 'low': 100.74, 'close': 115.85, 'volume': 10073.53}, {'date': '2023-05-18', 'open': 110.8, 'high': 120.88, 'low': 100.73, 'close': 115.84, 'volume': 10072.99}, {'date': '2023-05-19', 'open': 110.8, 'high': 120.87, 'low': 100.72, 'close': 115.83, 'volume': 10072.46}, {'date': '2023-05-20', 'open': 110.79, 'high': 120.86, 'low': 100.72, 'close': 115.83, 'volume': 10071.94}, {'date': '2023-05-21', 'open': 110.79, 'high': 120.86, 'low': 100.71, 'close': 115.82, 'volume': 10071.43}, {'date': '2023-05-22', 'open': 110.78, 'high': 120.85, 'low': 100.71, 'close': 115.82, 'volume': 10070.92}, {'date': '2023-05-23', 'open': 110.77, 'high': 120.85, 'low': 100.7, 'close': 115.81, 'volume': 10070.42}, {'date': '2023-05-24', 'open': 110.77, 'high': 120.84, 'low': 100.7, 'close': 115.8, 'volume': 10069.93}, {'date': '2023-05-25', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.8, 'volume': 10069.44}, {'date': '2023-05-26', 'open': 110.76, 'high': 120.83, 'low': 100.69, 'close': 115.79, 'volume': 10068.97}, {'date': '2023-05-27', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.79, 'volume': 10068.49}, {'date': '2023-05-28', 'open': 110.75, 'high': 120.82, 'low': 100.68, 'close': 115.78, 'volume': 10068.03}, {'date': '2023-05-29', 'open': 110.74, 'high': 120.81, 'low': 100.68, 'close': 115.78, 'volume': 10067.57}, {'date': '2023-05-30', 'open': 110.74, 'high': 120.81, 'low': 100.67, 'close': 115.77, 'volume': 10067.11}, {'date': '2023-05-31', 'open': 110.73, 'high': 120.8, 'low': 100.67, 'close': 115.77, 'volume': 10066.67}, {'date': '2023-06-01', 'open': 110.73, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10066.23}, {'date': '2023-06-02', 'open': 110.72, 'high': 120.79, 'low': 100.66, 'close': 115.76, 'volume': 10065.79}, {'date': '2023-06-03', 'open': 110.72, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10065.36}, {'date': '2023-06-04', 'open': 110.71, 'high': 120.78, 'low': 100.65, 'close': 115.75, 'volume': 10064.94}, {'date': '2023-06-05', 'open': 110.71, 'high': 120.77, 'low': 100.65, 'close': 115.74, 'volume': 10064.52}, {'date': '2023-06-06', 'open': 110.71, 'high': 120.77, 'low': 100.64, 'close': 115.74, 'volume': 10064.1}, {'date': '2023-06-07', 'open': 110.7, 'high': 120.76, 'low': 100.64, 'close': 115.73, 'volume': 10063.69}, {'date': '2023-06-08', 'open': 110.7, 'high': 120.76, 'low': 100.63, 'close': 115.73, 'volume': 10063.29}, {'date': '2023-06-09', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.89}, {'date': '2023-06-10', 'open': 110.69, 'high': 120.75, 'low': 100.63, 'close': 115.72, 'volume': 10062.5}, {'date': '2023-06-11', 'open': 110.68, 'high': 120.75, 'low': 100.62, 'close': 115.71, 'volume': 10062.11}, {'date': '2023-06-12', 'open': 110.68, 'high': 120.74, 'low': 100.62, 'close': 115.71, 'volume': 10061.73}, {'date': '2023-06-13', 'open': 110.67, 'high': 120.74, 'low': 100.61, 'close': 115.71, 'volume': 10061.35}, {'date': '2023-06-14', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.98}, {'date': '2023-06-15', 'open': 110.67, 'high': 120.73, 'low': 100.61, 'close': 115.7, 'volume': 10060.61}, {'date': '2023-06-16', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10060.24}, {'date': '2023-06-17', 'open': 110.66, 'high': 120.72, 'low': 100.6, 'close': 115.69, 'volume': 10059.88}, {'date': '2023-06-18', 'open': 110.65, 'high': 120.71, 'low': 100.6, 'close': 115.68, 'volume': 10059.52}, {'date': '2023-06-19', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10059.17}, {'date': '2023-06-20', 'open': 110.65, 'high': 120.71, 'low': 100.59, 'close': 115.68, 'volume': 10058.82}, {'date': '2023-06-21', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.48}, {'date': '2023-06-22', 'open': 110.64, 'high': 120.7, 'low': 100.58, 'close': 115.67, 'volume': 10058.14}, {'date': '2023-06-23', 'open': 110.64, 'high': 120.69, 'low': 100.58, 'close': 115.66, 'volume': 10057.8}, {'date': '2023-06-24', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.47}, {'date': '2023-06-25', 'open': 110.63, 'high': 120.69, 'low': 100.57, 'close': 115.66, 'volume': 10057.14}, {'date': '2023-06-26', 'open': 110.62, 'high': 120.68, 'low': 100.57, 'close': 115.65, 'volume': 10056.82}, {'date': '2023-06-27', 'open': 110.62, 'high': 120.68, 'low': 100.56, 'close': 115.65, 'volume': 10056.5}, {'date': '2023-06-28', 'open': 110.62, 'high': 120.67, 'low': 100.56, 'close': 115.65, 'volume': 10056.18}, {'date': '2023-06-29', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.87}, {'date': '2023-06-30', 'open': 110.61, 'high': 120.67, 'low': 100.56, 'close': 115.64, 'volume': 10055.56}, {'date': '2023-07-01', 'open': 110.61, 'high': 120.66, 'low': 100.55, 'close': 115.64, 'volume': 10055.25}, {'date': '2023-07-02', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.95}, {'date': '2023-07-03', 'open': 110.6, 'high': 120.66, 'low': 100.55, 'close': 115.63, 'volume': 10054.64}, {'date': '2023-07-04', 'open': 110.6, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.35}, {'date': '2023-07-05', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10054.05}, {'date': '2023-07-06', 'open': 110.59, 'high': 120.65, 'low': 100.54, 'close': 115.62, 'volume': 10053.76}, {'date': '2023-07-07', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.48}, {'date': '2023-07-08', 'open': 110.59, 'high': 120.64, 'low': 100.53, 'close': 115.61, 'volume': 10053.19}, {'date': '2023-07-09', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.91}, {'date': '2023-07-10', 'open': 110.58, 'high': 120.63, 'low': 100.53, 'close': 115.61, 'volume': 10052.63}, {'date': '2023-07-11', 'open': 110.58, 'high': 120.63, 'low': 100.52, 'close': 115.6, 'volume': 10052.36}, {'date': '2023-07-12', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10052.08}, {'date': '2023-07-13', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.6, 'volume': 10051.81}, {'date': '2023-07-14', 'open': 110.57, 'high': 120.62, 'low': 100.52, 'close': 115.59, 'volume': 10051.55}, {'date': '2023-07-15', 'open': 110.56, 'high': 120.62, 'low': 100.51, 'close': 115.59, 'volume': 10051.28}, {'date': '2023-07-16', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.59, 'volume': 10051.02}, {'date': '2023-07-17', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.76}, {'date': '2023-07-18', 'open': 110.56, 'high': 120.61, 'low': 100.51, 'close': 115.58, 'volume': 10050.51}, {'date': '2023-07-19', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.58, 'volume': 10050.25}, {'date': '2023-07-20', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10050.0}, {'date': '2023-07-21', 'open': 110.55, 'high': 120.6, 'low': 100.5, 'close': 115.57, 'volume': 10049.75}, {'date': '2023-07-22', 'open': 110.54, 'high': 120.59, 'low': 100.5, 'close': 115.57, 'volume': 10049.5}, {'date': '2023-07-23', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.57, 'volume': 10049.26}, {'date': '2023-07-24', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10049.02}, {'date': '2023-07-25', 'open': 110.54, 'high': 120.59, 'low': 100.49, 'close': 115.56, 'volume': 10048.78}, {'date': '2023-07-26', 'open': 110.53, 'high': 120.58, 'low': 100.49, 'close': 115.56, 'volume': 10048.54}, {'date': '2023-07-27', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.56, 'volume': 10048.31}, {'date': '2023-07-28', 'open': 110.53, 'high': 120.58, 'low': 100.48, 'close': 115.55, 'volume': 10048.08}, {'date': '2023-07-29', 'open': 110.53, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.85}, {'date': '2023-07-30', 'open': 110.52, 'high': 120.57, 'low': 100.48, 'close': 115.55, 'volume': 10047.62}, {'date': '2023-07-31', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.55, 'volume': 10047.39}, {'date': '2023-08-01', 'open': 110.52, 'high': 120.57, 'low': 100.47, 'close': 115.54, 'volume': 10047.17}, {'date': '2023-08-02', 'open': 110.52, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.95}, {'date': '2023-08-03', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.54, 'volume': 10046.73}, {'date': '2023-08-04', 'open': 110.51, 'high': 120.56, 'low': 100.47, 'close': 115.53, 'volume': 10046.51}, {'date': '2023-08-05', 'open': 110.51, 'high': 120.56, 'low': 100.46, 'close': 115.53, 'volume': 10046.3}, {'date': '2023-08-06', 'open': 110.51, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10046.08}, {'date': '2023-08-07', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.87}, {'date': '2023-08-08', 'open': 110.5, 'high': 120.55, 'low': 100.46, 'close': 115.53, 'volume': 10045.66}, {'date': '2023-08-09', 'open': 110.5, 'high': 120.55, 'low': 100.45, 'close': 115.52, 'volume': 10045.45}, {'date': '2023-08-10', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.25}, {'date': '2023-08-11', 'open': 110.5, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10045.05}, {'date': '2023-08-12', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.52, 'volume': 10044.84}, {'date': '2023-08-13', 'open': 110.49, 'high': 120.54, 'low': 100.45, 'close': 115.51, 'volume': 10044.64}, {'date': '2023-08-14', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.44}, {'date': '2023-08-15', 'open': 110.49, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.25}, {'date': '2023-08-16', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.51, 'volume': 10044.05}, {'date': '2023-08-17', 'open': 110.48, 'high': 120.53, 'low': 100.44, 'close': 115.5, 'volume': 10043.86}, {'date': '2023-08-18', 'open': 110.48, 'high': 120.52, 'low': 100.44, 'close': 115.5, 'volume': 10043.67}, {'date': '2023-08-19', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.48}, {'date': '2023-08-20', 'open': 110.48, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.29}, {'date': '2023-08-21', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.5, 'volume': 10043.1}, {'date': '2023-08-22', 'open': 110.47, 'high': 120.52, 'low': 100.43, 'close': 115.49, 'volume': 10042.92}, {'date': '2023-08-23', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.74}, {'date': '2023-08-24', 'open': 110.47, 'high': 120.51, 'low': 100.43, 'close': 115.49, 'volume': 10042.55}, {'date': '2023-08-25', 'open': 110.47, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.37}, {'date': '2023-08-26', 'open': 110.46, 'high': 120.51, 'low': 100.42, 'close': 115.49, 'volume': 10042.19}, {'date': '2023-08-27', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10042.02}, {'date': '2023-08-28', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.84}, {'date': '2023-08-29', 'open': 110.46, 'high': 120.5, 'low': 100.42, 'close': 115.48, 'volume': 10041.67}, {'date': '2023-08-30', 'open': 110.46, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.49}, {'date': '2023-08-31', 'open': 110.45, 'high': 120.5, 'low': 100.41, 'close': 115.48, 'volume': 10041.32}, {'date': '2023-09-01', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10041.15}, {'date': '2023-09-02', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.98}, {'date': '2023-09-03', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.82}, {'date': '2023-09-04', 'open': 110.45, 'high': 120.49, 'low': 100.41, 'close': 115.47, 'volume': 10040.65}, {'date': '2023-09-05', 'open': 110.45, 'high': 120.49, 'low': 100.4, 'close': 115.47, 'volume': 10040.49}, {'date': '2023-09-06', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.32}, {'date': '2023-09-07', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.16}, {'date': '2023-09-08', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10040.0}, {'date': '2023-09-09', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.84}, {'date': '2023-09-10', 'open': 110.44, 'high': 120.48, 'low': 100.4, 'close': 115.46, 'volume': 10039.68}, {'date': '2023-09-11', 'open': 110.43, 'high': 120.47, 'low': 100.4, 'close': 115.45, 'volume': 10039.53}, {'date': '2023-09-12', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.37}, {'date': '2023-09-13', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.22}, {'date': '2023-09-14', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10039.06}, {'date': '2023-09-15', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.91}, {'date': '2023-09-16', 'open': 110.43, 'high': 120.47, 'low': 100.39, 'close': 115.45, 'volume': 10038.76}, {'date': '2023-09-17', 'open': 110.42, 'high': 120.46, 'low': 100.39, 'close': 115.44, 'volume': 10038.61}, {'date': '2023-09-18', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.46}, {'date': '2023-09-19', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.31}, {'date': '2023-09-20', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.17}, {'date': '2023-09-21', 'open': 110.42, 'high': 120.46, 'low': 100.38, 'close': 115.44, 'volume': 10038.02}, {'date': '2023-09-22', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.44, 'volume': 10037.88}, {'date': '2023-09-23', 'open': 110.42, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.74}, {'date': '2023-09-24', 'open': 110.41, 'high': 120.45, 'low': 100.38, 'close': 115.43, 'volume': 10037.59}, {'date': '2023-09-25', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.45}, {'date': '2023-09-26', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.31}, {'date': '2023-09-27', 'open': 110.41, 'high': 120.45, 'low': 100.37, 'close': 115.43, 'volume': 10037.17}, {'date': '2023-09-28', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.43, 'volume': 10037.04}, {'date': '2023-09-29', 'open': 110.41, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.9}, {'date': '2023-09-30', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.76}, {'date': '2023-10-01', 'open': 110.4, 'high': 120.44, 'low': 100.37, 'close': 115.42, 'volume': 10036.63}, {'date': '2023-10-02', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.5}, {'date': '2023-10-03', 'open': 110.4, 'high': 120.44, 'low': 100.36, 'close': 115.42, 'volume': 10036.36}, {'date': '2023-10-04', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.23}, {'date': '2023-10-05', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.42, 'volume': 10036.1}, {'date': '2023-10-06', 'open': 110.4, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.97}, {'date': '2023-10-07', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.84}, {'date': '2023-10-08', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.71}, {'date': '2023-10-09', 'open': 110.39, 'high': 120.43, 'low': 100.36, 'close': 115.41, 'volume': 10035.59}, {'date': '2023-10-10', 'open': 110.39, 'high': 120.43, 'low': 100.35, 'close': 115.41, 'volume': 10035.46}, {'date': '2023-10-11', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.41, 'volume': 10035.34}, {'date': '2023-10-12', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.21}, {'date': '2023-10-13', 'open': 110.39, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10035.09}, {'date': '2023-10-14', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.97}, {'date': '2023-10-15', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.84}, {'date': '2023-10-16', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.72}, {'date': '2023-10-17', 'open': 110.38, 'high': 120.42, 'low': 100.35, 'close': 115.4, 'volume': 10034.6}, {'date': '2023-10-18', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.48}, {'date': '2023-10-19', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.4, 'volume': 10034.36}, {'date': '2023-10-20', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.25}, {'date': '2023-10-21', 'open': 110.38, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.13}, {'date': '2023-10-22', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10034.01}, {'date': '2023-10-23', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.9}, {'date': '2023-10-24', 'open': 110.37, 'high': 120.41, 'low': 100.34, 'close': 115.39, 'volume': 10033.78}, {'date': '2023-10-25', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.67}, {'date': '2023-10-26', 'open': 110.37, 'high': 120.4, 'low': 100.34, 'close': 115.39, 'volume': 10033.56}, {'date': '2023-10-27', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.44}, {'date': '2023-10-28', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.33}, {'date': '2023-10-29', 'open': 110.37, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.22}, {'date': '2023-10-30', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.11}, {'date': '2023-10-31', 'open': 110.36, 'high': 120.4, 'low': 100.33, 'close': 115.38, 'volume': 10033.0}, {'date': '2023-11-01', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.89}, {'date': '2023-11-02', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.79}, {'date': '2023-11-03', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.38, 'volume': 10032.68}, {'date': '2023-11-04', 'open': 110.36, 'high': 120.39, 'low': 100.33, 'close': 115.37, 'volume': 10032.57}, {'date': '2023-11-05', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.47}, {'date': '2023-11-06', 'open': 110.36, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.36}, {'date': '2023-11-07', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.26}, {'date': '2023-11-08', 'open': 110.35, 'high': 120.39, 'low': 100.32, 'close': 115.37, 'volume': 10032.15}, {'date': '2023-11-09', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10032.05}, {'date': '2023-11-10', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.95}, {'date': '2023-11-11', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.85}, {'date': '2023-11-12', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.37, 'volume': 10031.75}, {'date': '2023-11-13', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.65}, {'date': '2023-11-14', 'open': 110.35, 'high': 120.38, 'low': 100.32, 'close': 115.36, 'volume': 10031.55}, {'date': '2023-11-15', 'open': 110.35, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.45}, {'date': '2023-11-16', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.35}, {'date': '2023-11-17', 'open': 110.34, 'high': 120.38, 'low': 100.31, 'close': 115.36, 'volume': 10031.25}, {'date': '2023-11-18', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.15}, {'date': '2023-11-19', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10031.06}, {'date': '2023-11-20', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.36, 'volume': 10030.96}, {'date': '2023-11-21', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.86}, {'date': '2023-11-22', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.77}, {'date': '2023-11-23', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.67}, {'date': '2023-11-24', 'open': 110.34, 'high': 120.37, 'low': 100.31, 'close': 115.35, 'volume': 10030.58}, {'date': '2023-11-25', 'open': 110.34, 'high': 120.37, 'low': 100.3, 'close': 115.35, 'volume': 10030.49}, {'date': '2023-11-26', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.4}, {'date': '2023-11-27', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.3}, {'date': '2023-11-28', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.21}, {'date': '2023-11-29', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.12}, {'date': '2023-11-30', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.35, 'volume': 10030.03}, {'date': '2023-12-01', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.94}, {'date': '2023-12-02', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.85}, {'date': '2023-12-03', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.76}, {'date': '2023-12-04', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.67}, {'date': '2023-12-05', 'open': 110.33, 'high': 120.36, 'low': 100.3, 'close': 115.34, 'volume': 10029.59}, {'date': '2023-12-06', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.5}, {'date': '2023-12-07', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.41}, {'date': '2023-12-08', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.33}, {'date': '2023-12-09', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.24}, {'date': '2023-12-10', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.34, 'volume': 10029.15}, {'date': '2023-12-11', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10029.07}, {'date': '2023-12-12', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.99}, {'date': '2023-12-13', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.9}, {'date': '2023-12-14', 'open': 110.32, 'high': 120.35, 'low': 100.29, 'close': 115.33, 'volume': 10028.82}, {'date': '2023-12-15', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.74}, {'date': '2023-12-16', 'open': 110.32, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.65}, {'date': '2023-12-17', 'open': 110.31, 'high': 120.34, 'low': 100.29, 'close': 115.33, 'volume': 10028.57}, {'date': '2023-12-18', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.49}, {'date': '2023-12-19', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.41}, {'date': '2023-12-20', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.33, 'volume': 10028.33}, {'date': '2023-12-21', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.25}, {'date': '2023-12-22', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.17}, {'date': '2023-12-23', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.09}, {'date': '2023-12-24', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10028.01}, {'date': '2023-12-25', 'open': 110.31, 'high': 120.34, 'low': 100.28, 'close': 115.32, 'volume': 10027.93}, {'date': '2023-12-26', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.86}, {'date': '2023-12-27', 'open': 110.31, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.78}, {'date': '2023-12-28', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.7}, {'date': '2023-12-29', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.62}, {'date': '2023-12-30', 'open': 110.3, 'high': 120.33, 'low': 100.28, 'close': 115.32, 'volume': 10027.55}, {'date': '2023-12-31', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.47}, {'date': '2024-01-01', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.32, 'volume': 10027.4}, {'date': '2024-01-02', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.32}, {'date': '2024-01-03', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.25}, {'date': '2024-01-04', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.17}, {'date': '2024-01-05', 'open': 110.3, 'high': 120.33, 'low': 100.27, 'close': 115.31, 'volume': 10027.1}, {'date': '2024-01-06', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10027.03}, {'date': '2024-01-07', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.95}, {'date': '2024-01-08', 'open': 110.3, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.88}, {'date': '2024-01-09', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.81}, {'date': '2024-01-10', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.74}, {'date': '2024-01-11', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.67}, {'date': '2024-01-12', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.6}, {'date': '2024-01-13', 'open': 110.29, 'high': 120.32, 'low': 100.27, 'close': 115.31, 'volume': 10026.53}, {'date': '2024-01-14', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.46}, {'date': '2024-01-15', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.39}, {'date': '2024-01-16', 'open': 110.29, 'high': 120.32, 'low': 100.26, 'close': 115.3, 'volume': 10026.32}, {'date': '2024-01-17', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.25}, {'date': '2024-01-18', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.18}, {'date': '2024-01-19', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.11}, {'date': '2024-01-20', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10026.04}, {'date': '2024-01-21', 'open': 110.29, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.97}, {'date': '2024-01-22', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.91}, {'date': '2024-01-23', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.84}, {'date': '2024-01-24', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.77}, {'date': '2024-01-25', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.3, 'volume': 10025.71}, {'date': '2024-01-26', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.64}, {'date': '2024-01-27', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.58}, {'date': '2024-01-28', 'open': 110.28, 'high': 120.31, 'low': 100.26, 'close': 115.29, 'volume': 10025.51}, {'date': '2024-01-29', 'open': 110.28, 'high': 120.31, 'low': 100.25, 'close': 115.29, 'volume': 10025.45}, {'date': '2024-01-30', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.38}, {'date': '2024-01-31', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.32}, {'date': '2024-02-01', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.25}, {'date': '2024-02-02', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.19}, {'date': '2024-02-03', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.13}, {'date': '2024-02-04', 'open': 110.28, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.06}, {'date': '2024-02-05', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10025.0}, {'date': '2024-02-06', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.94}, {'date': '2024-02-07', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.88}, {'date': '2024-02-08', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.29, 'volume': 10024.81}, {'date': '2024-02-09', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.75}, {'date': '2024-02-10', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.69}, {'date': '2024-02-11', 'open': 110.27, 'high': 120.3, 'low': 100.25, 'close': 115.28, 'volume': 10024.63}, {'date': '2024-02-12', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.57}, {'date': '2024-02-13', 'open': 110.27, 'high': 120.29, 'low': 100.25, 'close': 115.28, 'volume': 10024.51}, {'date': '2024-02-14', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.45}, {'date': '2024-02-15', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.39}, {'date': '2024-02-16', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.33}, {'date': '2024-02-17', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.27}, {'date': '2024-02-18', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.21}, {'date': '2024-02-19', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.15}, {'date': '2024-02-20', 'open': 110.27, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.1}, {'date': '2024-02-21', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10024.04}, {'date': '2024-02-22', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.98}, {'date': '2024-02-23', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.28, 'volume': 10023.92}, {'date': '2024-02-24', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.87}, {'date': '2024-02-25', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.81}, {'date': '2024-02-26', 'open': 110.26, 'high': 120.29, 'low': 100.24, 'close': 115.27, 'volume': 10023.75}, {'date': '2024-02-27', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.7}, {'date': '2024-02-28', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.64}, {'date': '2024-02-29', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.58}, {'date': '2024-03-01', 'open': 110.26, 'high': 120.28, 'low': 100.24, 'close': 115.27, 'volume': 10023.53}, {'date': '2024-03-02', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.47}, {'date': '2024-03-03', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.42}, {'date': '2024-03-04', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.36}, {'date': '2024-03-05', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.31}, {'date': '2024-03-06', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.26}, {'date': '2024-03-07', 'open': 110.26, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.2}, {'date': '2024-03-08', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.15}, {'date': '2024-03-09', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.27, 'volume': 10023.09}, {'date': '2024-03-10', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10023.04}, {'date': '2024-03-11', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.99}, {'date': '2024-03-12', 'open': 110.25, 'high': 120.28, 'low': 100.23, 'close': 115.26, 'volume': 10022.94}, {'date': '2024-03-13', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.88}, {'date': '2024-03-14', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.83}, {'date': '2024-03-15', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.78}, {'date': '2024-03-16', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.73}, {'date': '2024-03-17', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.68}, {'date': '2024-03-18', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.62}, {'date': '2024-03-19', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.57}, {'date': '2024-03-20', 'open': 110.25, 'high': 120.27, 'low': 100.23, 'close': 115.26, 'volume': 10022.52}, {'date': '2024-03-21', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.47}, {'date': '2024-03-22', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.42}, {'date': '2024-03-23', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.37}, {'date': '2024-03-24', 'open': 110.25, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.32}, {'date': '2024-03-25', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.27}, {'date': '2024-03-26', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.26, 'volume': 10022.22}, {'date': '2024-03-27', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.17}, {'date': '2024-03-28', 'open': 110.24, 'high': 120.27, 'low': 100.22, 'close': 115.25, 'volume': 10022.12}, {'date': '2024-03-29', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.08}, {'date': '2024-03-30', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10022.03}, {'date': '2024-03-31', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.98}, {'date': '2024-04-01', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.93}, {'date': '2024-04-02', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.88}, {'date': '2024-04-03', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.83}, {'date': '2024-04-04', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.79}, {'date': '2024-04-05', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.74}, {'date': '2024-04-06', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.69}, {'date': '2024-04-07', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.65}, {'date': '2024-04-08', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.6}, {'date': '2024-04-09', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.55}, {'date': '2024-04-10', 'open': 110.24, 'high': 120.26, 'low': 100.22, 'close': 115.25, 'volume': 10021.51}, {'date': '2024-04-11', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.46}, {'date': '2024-04-12', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.41}, {'date': '2024-04-13', 'open': 110.24, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.37}, {'date': '2024-04-14', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.25, 'volume': 10021.32}, {'date': '2024-04-15', 'open': 110.23, 'high': 120.26, 'low': 100.21, 'close': 115.24, 'volume': 10021.28}, {'date': '2024-04-16', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.23}, {'date': '2024-04-17', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.19}, {'date': '2024-04-18', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.14}, {'date': '2024-04-19', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.1}, {'date': '2024-04-20', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.05}, {'date': '2024-04-21', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10021.01}, {'date': '2024-04-22', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.96}, {'date': '2024-04-23', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.92}, {'date': '2024-04-24', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.88}, {'date': '2024-04-25', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.83}, {'date': '2024-04-26', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.79}, {'date': '2024-04-27', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.75}, {'date': '2024-04-28', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.7}, {'date': '2024-04-29', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.66}, {'date': '2024-04-30', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.62}, {'date': '2024-05-01', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.58}, {'date': '2024-05-02', 'open': 110.23, 'high': 120.25, 'low': 100.21, 'close': 115.24, 'volume': 10020.53}, {'date': '2024-05-03', 'open': 110.23, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.49}, {'date': '2024-05-04', 'open': 110.22, 'high': 120.25, 'low': 100.2, 'close': 115.24, 'volume': 10020.45}, {'date': '2024-05-05', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.41}, {'date': '2024-05-06', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.37}, {'date': '2024-05-07', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.33}, {'date': '2024-05-08', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.28}, {'date': '2024-05-09', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.24}, {'date': '2024-05-10', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.2}, {'date': '2024-05-11', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.16}, {'date': '2024-05-12', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.12}, {'date': '2024-05-13', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.08}, {'date': '2024-05-14', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.04}, {'date': '2024-05-15', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10020.0}, {'date': '2024-05-16', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.96}, {'date': '2024-05-17', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.92}, {'date': '2024-05-18', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.88}, {'date': '2024-05-19', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.84}, {'date': '2024-05-20', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.8}, {'date': '2024-05-21', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.76}, {'date': '2024-05-22', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.72}, {'date': '2024-05-23', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.69}, {'date': '2024-05-24', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.65}, {'date': '2024-05-25', 'open': 110.22, 'high': 120.24, 'low': 100.2, 'close': 115.23, 'volume': 10019.61}, {'date': '2024-05-26', 'open': 110.22, 'high': 120.23, 'low': 100.2, 'close': 115.23, 'volume': 10019.57}, {'date': '2024-05-27', 'open': 110.21, 'high': 120.23, 'low': 100.2, 'close': 115.22, 'volume': 10019.53}, {'date': '2024-05-28', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.49}, {'date': '2024-05-29', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.46}, {'date': '2024-05-30', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.42}, {'date': '2024-05-31', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.38}, {'date': '2024-06-01', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.34}, {'date': '2024-06-02', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.31}, {'date': '2024-06-03', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.27}, {'date': '2024-06-04', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.23}, {'date': '2024-06-05', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.19}, {'date': '2024-06-06', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.16}, {'date': '2024-06-07', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.12}, {'date': '2024-06-08', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.08}, {'date': '2024-06-09', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.05}, {'date': '2024-06-10', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10019.01}, {'date': '2024-06-11', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.98}, {'date': '2024-06-12', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.94}, {'date': '2024-06-13', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.9}, {'date': '2024-06-14', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.87}, {'date': '2024-06-15', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.83}, {'date': '2024-06-16', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.8}, {'date': '2024-06-17', 'open': 110.21, 'high': 120.23, 'low': 100.19, 'close': 115.22, 'volume': 10018.76}, {'date': '2024-06-18', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.22, 'volume': 10018.73}, {'date': '2024-06-19', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.69}, {'date': '2024-06-20', 'open': 110.21, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.66}, {'date': '2024-06-21', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.62}, {'date': '2024-06-22', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.59}, {'date': '2024-06-23', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.55}, {'date': '2024-06-24', 'open': 110.2, 'high': 120.22, 'low': 100.19, 'close': 115.21, 'volume': 10018.52}, {'date': '2024-06-25', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.48}, {'date': '2024-06-26', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.45}, {'date': '2024-06-27', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.42}, {'date': '2024-06-28', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.38}, {'date': '2024-06-29', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.35}, {'date': '2024-06-30', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.32}, {'date': '2024-07-01', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.28}, {'date': '2024-07-02', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.25}, {'date': '2024-07-03', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.21}, {'date': '2024-07-04', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.18}, {'date': '2024-07-05', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.15}, {'date': '2024-07-06', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.12}, {'date': '2024-07-07', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.08}, {'date': '2024-07-08', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.05}, {'date': '2024-07-09', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10018.02}, {'date': '2024-07-10', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.99}, {'date': '2024-07-11', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.95}, {'date': '2024-07-12', 'open': 110.2, 'high': 120.22, 'low': 100.18, 'close': 115.21, 'volume': 10017.92}, {'date': '2024-07-13', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.89}, {'date': '2024-07-14', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.21, 'volume': 10017.86}, {'date': '2024-07-15', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.83}, {'date': '2024-07-16', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.79}, {'date': '2024-07-17', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.76}, {'date': '2024-07-18', 'open': 110.2, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.73}, {'date': '2024-07-19', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.7}, {'date': '2024-07-20', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.67}, {'date': '2024-07-21', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.64}, {'date': '2024-07-22', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.61}, {'date': '2024-07-23', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.57}, {'date': '2024-07-24', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.54}, {'date': '2024-07-25', 'open': 110.19, 'high': 120.21, 'low': 100.18, 'close': 115.2, 'volume': 10017.51}, {'date': '2024-07-26', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.48}, {'date': '2024-07-27', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.45}, {'date': '2024-07-28', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.42}, {'date': '2024-07-29', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.39}, {'date': '2024-07-30', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.36}, {'date': '2024-07-31', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.33}, {'date': '2024-08-01', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.3}, {'date': '2024-08-02', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.27}, {'date': '2024-08-03', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.24}, {'date': '2024-08-04', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.21}, {'date': '2024-08-05', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.18}, {'date': '2024-08-06', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.15}, {'date': '2024-08-07', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.12}, {'date': '2024-08-08', 'open': 110.19, 'high': 120.21, 'low': 100.17, 'close': 115.2, 'volume': 10017.09}, {'date': '2024-08-09', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.06}, {'date': '2024-08-10', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.04}, {'date': '2024-08-11', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10017.01}, {'date': '2024-08-12', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.2, 'volume': 10016.98}, {'date': '2024-08-13', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.95}, {'date': '2024-08-14', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.92}, {'date': '2024-08-15', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.89}, {'date': '2024-08-16', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.86}, {'date': '2024-08-17', 'open': 110.19, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.84}, {'date': '2024-08-18', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.81}, {'date': '2024-08-19', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.78}, {'date': '2024-08-20', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.75}, {'date': '2024-08-21', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.72}, {'date': '2024-08-22', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.69}, {'date': '2024-08-23', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.67}, {'date': '2024-08-24', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.64}, {'date': '2024-08-25', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.61}, {'date': '2024-08-26', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.58}, {'date': '2024-08-27', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.56}, {'date': '2024-08-28', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.53}, {'date': '2024-08-29', 'open': 110.18, 'high': 120.2, 'low': 100.17, 'close': 115.19, 'volume': 10016.5}, {'date': '2024-08-30', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.47}, {'date': '2024-08-31', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.45}, {'date': '2024-09-01', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.42}, {'date': '2024-09-02', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.39}, {'date': '2024-09-03', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.37}, {'date': '2024-09-04', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.34}, {'date': '2024-09-05', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.31}, {'date': '2024-09-06', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.29}, {'date': '2024-09-07', 'open': 110.18, 'high': 120.2, 'low': 100.16, 'close': 115.19, 'volume': 10016.26}, {'date': '2024-09-08', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.23}, {'date': '2024-09-09', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.21}, {'date': '2024-09-10', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.18}, {'date': '2024-09-11', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.16}, {'date': '2024-09-12', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.13}, {'date': '2024-09-13', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.19, 'volume': 10016.1}, {'date': '2024-09-14', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.08}, {'date': '2024-09-15', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.05}, {'date': '2024-09-16', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.03}, {'date': '2024-09-17', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10016.0}, {'date': '2024-09-18', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.97}, {'date': '2024-09-19', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.95}, {'date': '2024-09-20', 'open': 110.18, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.92}, {'date': '2024-09-21', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.9}, {'date': '2024-09-22', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.87}, {'date': '2024-09-23', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.85}, {'date': '2024-09-24', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.82}, {'date': '2024-09-25', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.8}, {'date': '2024-09-26', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.77}, {'date': '2024-09-27', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.75}, {'date': '2024-09-28', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.72}, {'date': '2024-09-29', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.7}, {'date': '2024-09-30', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.67}, {'date': '2024-10-01', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.65}, {'date': '2024-10-02', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.62}, {'date': '2024-10-03', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.6}, {'date': '2024-10-04', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.58}, {'date': '2024-10-05', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.55}, {'date': '2024-10-06', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.53}, {'date': '2024-10-07', 'open': 110.17, 'high': 120.19, 'low': 100.16, 'close': 115.18, 'volume': 10015.5}, {'date': '2024-10-08', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.48}, {'date': '2024-10-09', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.46}, {'date': '2024-10-10', 'open': 110.17, 'high': 120.19, 'low': 100.15, 'close': 115.18, 'volume': 10015.43}, {'date': '2024-10-11', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.41}, {'date': '2024-10-12', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.38}, {'date': '2024-10-13', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.36}, {'date': '2024-10-14', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.34}, {'date': '2024-10-15', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.31}, {'date': '2024-10-16', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.29}, {'date': '2024-10-17', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.27}, {'date': '2024-10-18', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.24}, {'date': '2024-10-19', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.18, 'volume': 10015.22}, {'date': '2024-10-20', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.2}, {'date': '2024-10-21', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.17}, {'date': '2024-10-22', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.15}, {'date': '2024-10-23', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.13}, {'date': '2024-10-24', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.11}, {'date': '2024-10-25', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.08}, {'date': '2024-10-26', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.06}, {'date': '2024-10-27', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.04}, {'date': '2024-10-28', 'open': 110.17, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10015.02}, {'date': '2024-10-29', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.99}, {'date': '2024-10-30', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.97}, {'date': '2024-10-31', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.95}, {'date': '2024-11-01', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.93}, {'date': '2024-11-02', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.9}, {'date': '2024-11-03', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.88}, {'date': '2024-11-04', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.86}, {'date': '2024-11-05', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.84}, {'date': '2024-11-06', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.81}, {'date': '2024-11-07', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.79}, {'date': '2024-11-08', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.77}, {'date': '2024-11-09', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.75}, {'date': '2024-11-10', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.73}, {'date': '2024-11-11', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.71}, {'date': '2024-11-12', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.68}, {'date': '2024-11-13', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.66}, {'date': '2024-11-14', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.64}, {'date': '2024-11-15', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.62}, {'date': '2024-11-16', 'open': 110.16, 'high': 120.18, 'low': 100.15, 'close': 115.17, 'volume': 10014.6}, {'date': '2024-11-17', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.58}, {'date': '2024-11-18', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.56}, {'date': '2024-11-19', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.53}, {'date': '2024-11-20', 'open': 110.16, 'high': 120.17, 'low': 100.15, 'close': 115.17, 'volume': 10014.51}, {'date': '2024-11-21', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.49}, {'date': '2024-11-22', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.47}, {'date': '2024-11-23', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.45}, {'date': '2024-11-24', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.43}, {'date': '2024-11-25', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.41}, {'date': '2024-11-26', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.39}, {'date': '2024-11-27', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.17, 'volume': 10014.37}, {'date': '2024-11-28', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.35}, {'date': '2024-11-29', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.33}, {'date': '2024-11-30', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.31}, {'date': '2024-12-01', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.29}, {'date': '2024-12-02', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.27}, {'date': '2024-12-03', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.25}, {'date': '2024-12-04', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.22}, {'date': '2024-12-05', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.2}, {'date': '2024-12-06', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.18}, {'date': '2024-12-07', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.16}, {'date': '2024-12-08', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.14}, {'date': '2024-12-09', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.12}, {'date': '2024-12-10', 'open': 110.16, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.1}, {'date': '2024-12-11', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.08}, {'date': '2024-12-12', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.06}, {'date': '2024-12-13', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.04}, {'date': '2024-12-14', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.03}, {'date': '2024-12-15', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10014.01}, {'date': '2024-12-16', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.99}, {'date': '2024-12-17', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.97}, {'date': '2024-12-18', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.95}, {'date': '2024-12-19', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.93}, {'date': '2024-12-20', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.91}, {'date': '2024-12-21', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.89}, {'date': '2024-12-22', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.87}, {'date': '2024-12-23', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.85}, {'date': '2024-12-24', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.83}, {'date': '2024-12-25', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.81}, {'date': '2024-12-26', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.79}, {'date': '2024-12-27', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.77}, {'date': '2024-12-28', 'open': 110.15, 'high': 120.17, 'low': 100.14, 'close': 115.16, 'volume': 10013.76}, {'date': '2024-12-29', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.74}, {'date': '2024-12-30', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.72}, {'date': '2024-12-31', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.7}, {'date': '2025-01-01', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.68}, {'date': '2025-01-02', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.66}, {'date': '2025-01-03', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.64}, {'date': '2025-01-04', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.62}, {'date': '2025-01-05', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.61}, {'date': '2025-01-06', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.59}, {'date': '2025-01-07', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.57}, {'date': '2025-01-08', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.55}, {'date': '2025-01-09', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.53}, {'date': '2025-01-10', 'open': 110.15, 'high': 120.16, 'low': 100.14, 'close': 115.16, 'volume': 10013.51}, {'date': '2025-01-11', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.16, 'volume': 10013.5}, {'date': '2025-01-12', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.48}, {'date': '2025-01-13', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.46}, {'date': '2025-01-14', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.44}, {'date': '2025-01-15', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.42}, {'date': '2025-01-16', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.4}, {'date': '2025-01-17', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.39}, {'date': '2025-01-18', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.37}, {'date': '2025-01-19', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.35}, {'date': '2025-01-20', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.33}, {'date': '2025-01-21', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.32}, {'date': '2025-01-22', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.3}, {'date': '2025-01-23', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.28}, {'date': '2025-01-24', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.26}, {'date': '2025-01-25', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.25}, {'date': '2025-01-26', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.23}, {'date': '2025-01-27', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.21}, {'date': '2025-01-28', 'open': 110.15, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.19}, {'date': '2025-01-29', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.18}, {'date': '2025-01-30', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.16}, {'date': '2025-01-31', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.14}, {'date': '2025-02-01', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.12}, {'date': '2025-02-02', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.11}, {'date': '2025-02-03', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.09}, {'date': '2025-02-04', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.07}, {'date': '2025-02-05', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.05}, {'date': '2025-02-06', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.04}, {'date': '2025-02-07', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.02}, {'date': '2025-02-08', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10013.0}, {'date': '2025-02-09', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.99}, {'date': '2025-02-10', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.97}, {'date': '2025-02-11', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.95}, {'date': '2025-02-12', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.94}, {'date': '2025-02-13', 'open': 110.14, 'high': 120.16, 'low': 100.13, 'close': 115.15, 'volume': 10012.92}, {'date': '2025-02-14', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.9}, {'date': '2025-02-15', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.89}, {'date': '2025-02-16', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.87}, {'date': '2025-02-17', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.85}, {'date': '2025-02-18', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.84}, {'date': '2025-02-19', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.82}, {'date': '2025-02-20', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.8}, {'date': '2025-02-21', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.79}, {'date': '2025-02-22', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.77}, {'date': '2025-02-23', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.76}, {'date': '2025-02-24', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.74}, {'date': '2025-02-25', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.72}, {'date': '2025-02-26', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.71}, {'date': '2025-02-27', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.69}, {'date': '2025-02-28', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.67}, {'date': '2025-03-01', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.66}, {'date': '2025-03-02', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.64}, {'date': '2025-03-03', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.63}, {'date': '2025-03-04', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.15, 'volume': 10012.61}, {'date': '2025-03-05', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.59}, {'date': '2025-03-06', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.58}, {'date': '2025-03-07', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.56}, {'date': '2025-03-08', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.55}, {'date': '2025-03-09', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.53}, {'date': '2025-03-10', 'open': 110.14, 'high': 120.15, 'low': 100.13, 'close': 115.14, 'volume': 10012.52}, {'date': '2025-03-11', 'open': 110.14, 'high': 120.15, 'low': 100.12, 'close': 115.14, 'volume': 10012.5}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Target time series (index column plus the target column).", + "default": null, + "optional": false + }, + { + "name": "factors_data", + "type": "list[Data]", + "description": "Factor matrix (index column plus one column per factor).", + "default": null, + "optional": false + }, + { + "name": "periods", + "type": "list[str]", + "description": "Named look-back periods to attribute over, anchored to the latest factor observation.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Name of the column in `data` to attribute across the factors.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Name of the index column shared by `data` and `factors_data`.", + "default": "date", + "optional": true + }, + { + "name": "risk_free_column", + "type": "str", + "description": "Optional name of the risk-free rate column in `factors_data`. When provided, the target is converted to excess returns before attribution and the column is dropped from the factor set.", + "default": null, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[ReturnAttributionData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "period", + "type": "str", + "description": "Look-back period the attribution was run over.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "factor", + "type": "str", + "description": "Factor name, 'Alpha' for the intercept contribution, or 'Residual' for the unexplained residual.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "contribution", + "type": "float", + "description": "Component's contribution to the period's total target return: for a factor, beta_i * sum_t factor_i; for 'Alpha', alpha * n; for 'Residual', sum of regression residuals. The three sum to the period's total target return.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "share", + "type": "float", + "description": "Component's contribution divided by the total target return over the period. Shares sum to 1.0 unless the total return is zero.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/sma": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Simple Moving Average over the trailing window.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# 50-bar SMA on TSLA close.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nsma = obb.technical.sma(data=data, length=50)\nobb.technical.sma(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to smooth.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Window length in bars.", + "default": 50, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset, positive shifts forward.", + "default": 0, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[SmaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "sma", + "type": "float", + "description": "Simple moving average of ``target`` over ``length`` bars.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/ema": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Exponential Moving Average with recency-weighted smoothing.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.ema(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to smooth, by default ``\"close\"``.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "EMA period.", + "default": 50, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[EmaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "ema", + "type": "float", + "description": "Exponential moving average of ``target``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/hma": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Alan Hull's lag-reduced moving average.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.hma(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=4)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to smooth, by default ``\"close\"``.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "HMA period.", + "default": 50, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[HmaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "hma", + "type": "float", + "description": "Hull moving average \u2014 lag-reduced WMA blend.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/wma": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the linearly-Weighted Moving Average.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.wma(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to smooth, by default ``\"close\"``.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "WMA period.", + "default": 50, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[WmaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "wma", + "type": "float", + "description": "Linearly-weighted moving average of ``target``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/zlma": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Zero-Lag Exponential Moving Average of Ehlers and Way.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.zlma(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to smooth, by default ``\"close\"``.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "ZLMA period.", + "default": 50, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[ZlmaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "zlma", + "type": "float", + "description": "Zero-Lag EMA of ``target`` \u2014 Ehlers/Way de-lagged EMA.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/tema": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Patrick Mulloy's Triple Exponential Moving Average.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.tema(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=3)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to smooth, by default ``\"close\"``.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "TEMA period.", + "default": 10, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[TemaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "tema", + "type": "float", + "description": "Triple EMA \u2014 ``3*EMA - 3*EMA(EMA) + EMA(EMA(EMA))``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/dema": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Patrick Mulloy's Double Exponential Moving Average.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.dema(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=3)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to smooth, by default ``\"close\"``.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "DEMA period.", + "default": 10, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[DemaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "dema", + "type": "float", + "description": "Double EMA \u2014 ``2*EMA - EMA(EMA)``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/kama": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Perry Kaufman's Adaptive Moving Average.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.kama(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=3)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to smooth, by default ``\"close\"``.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Efficiency-ratio lookback.", + "default": 10, + "optional": true + }, + { + "name": "fast", + "type": "int", + "description": "Fastest EMA period bound.", + "default": 2, + "optional": true + }, + { + "name": "slow", + "type": "int", + "description": "Slowest EMA period bound.", + "default": 30, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[KamaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kama", + "type": "float", + "description": "Kaufman adaptive moving average of ``target``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/frama": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate John Ehlers' Fractal Adaptive Moving Average.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.frama(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], window=4)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series with ``high`` and ``low`` columns.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "window", + "type": "int", + "description": "Window length \u2014 must be even.", + "default": 10, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[FramaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "frama", + "type": "float", + "description": "Fractal adaptive moving average \u2014 Ehlers (2005).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/vwma": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Volume Weighted Moving Average.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.vwma(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=3)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series with ``close`` and ``volume``.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "VWMA period.", + "default": 10, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[VwmaData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "vwma", + "type": "float", + "description": "Volume weighted moving average of close.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/bbands": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Bollinger Bands around a moving-average centreline.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.bbands(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to smooth and band, by default ``\"close\"``.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Moving-average period.", + "default": 20, + "optional": true + }, + { + "name": "std", + "type": "float", + "description": "Standard-deviation multiplier.", + "default": 2.0, + "optional": true + }, + { + "name": "mamode", + "type": "Literal['sma', 'ema', 'wma', 'rma']", + "description": "Type of moving average for the middle band.", + "default": "sma", + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[BbandsData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "lower", + "type": "float", + "description": "Lower band \u2014 middle minus ``std`` deviations.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "middle", + "type": "float", + "description": "Center moving average.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "upper", + "type": "float", + "description": "Upper band \u2014 middle plus ``std`` deviations.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "bandwidth", + "type": "float", + "description": "``(upper - lower) / middle`` \u2014 width relative to the centre.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "percent", + "type": "float", + "description": "``(price - lower) / (upper - lower)`` \u2014 position within the band.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/donchian": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Donchian Channel \u2014 trailing high/low price envelope.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.donchian(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], lower_length=1, upper_length=3)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series with high/low columns.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "lower_length", + "type": "int", + "description": "Lookback for the lower band.", + "default": 20, + "optional": true + }, + { + "name": "upper_length", + "type": "int", + "description": "Lookback for the upper band.", + "default": 20, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset; positive values shift the series forward, by default 0.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[DonchianData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "lower", + "type": "float", + "description": "Trailing min over ``lower_length`` bars.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "middle", + "type": "float", + "description": "Midpoint between upper and lower bands.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "upper", + "type": "float", + "description": "Trailing max over ``upper_length`` bars.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/kc": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Keltner Channel \u2014 an ATR-scaled volatility envelope.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.kc(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Centerline MA period.", + "default": 20, + "optional": true + }, + { + "name": "scalar", + "type": "float", + "description": "ATR multiplier for the bands.", + "default": 2.0, + "optional": true + }, + { + "name": "mamode", + "type": "Literal['ema', 'sma']", + "description": "Centreline moving-average type.", + "default": "ema", + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Output offset in bars, by default 0.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[KcData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "lower", + "type": "float", + "description": "Centerline minus ``scalar * ATR``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "middle", + "type": "float", + "description": "Centreline moving average.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "upper", + "type": "float", + "description": "Centerline plus ``scalar * ATR``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/ichimoku": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Ichimoku Kinko Hyo cloud \u2014 a five-component trend overlay.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.ichimoku(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "conversion", + "type": "int", + "description": "Tenkan-sen lookback.", + "default": 9, + "optional": true + }, + { + "name": "base", + "type": "int", + "description": "Kijun-sen lookback.", + "default": 26, + "optional": true + }, + { + "name": "lagging", + "type": "int", + "description": "Senkou Span B lookback.", + "default": 52, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Forward projection for the cloud.", + "default": 26, + "optional": true + }, + { + "name": "lookahead", + "type": "bool", + "description": "If ``True``, emit the Chikou Span (look-ahead). Off by default to prevent data leakage.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[IchimokuData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "tenkan_sen", + "type": "float", + "description": "Conversion line.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kijun_sen", + "type": "float", + "description": "Base line.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "senkou_a", + "type": "float", + "description": "Leading span A.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "senkou_b", + "type": "float", + "description": "Leading span B.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "chikou_span", + "type": "float", + "description": "Lagging span. Always ``None`` when ``lookahead=False``.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/supertrend": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Supertrend ATR-banded trailing-stop overlay.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.supertrend(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "ATR lookback.", + "default": 7, + "optional": true + }, + { + "name": "multiplier", + "type": "float", + "description": "ATR multiplier for the bands.", + "default": 3.0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[SupertrendData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "supertrend", + "type": "float", + "description": "Current trailing-stop value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "direction", + "type": "None", + "description": "``1`` when the trend is up, ``-1`` when down. ``None`` during warm-up.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "long_band", + "type": "float", + "description": "Long-side band \u2014 populated only when the trend is up.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "short_band", + "type": "float", + "description": "Short-side band \u2014 populated only when the trend is down.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/rsi": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Relative Strength Index (RSI), Wilder's momentum oscillator.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# RSI(14) on daily TSLA closes.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nrsi = obb.technical.rsi(data=data, length=14)\nobb.technical.rsi(length=2, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Target column to apply RSI to.", + "default": "close", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Lookback window in bars.", + "default": 14, + "optional": true + }, + { + "name": "scalar", + "type": "float", + "description": "Output scaling. ``100`` produces the conventional 0\u2013100 range.", + "default": 100.0, + "optional": true + }, + { + "name": "drift", + "type": "int", + "description": "Difference period for momentum.", + "default": 1, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RsiData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "rsi", + "type": "float", + "description": "RSI value over the trailing ``length`` bars on ``target``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/stoch": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Stochastic Oscillator (%K and %D).", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Stochastic oscillator on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nstoch = obb.technical.stoch(data=data)\nobb.technical.stoch(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "fast_k_period", + "type": "int", + "description": "Fast %K lookback.", + "default": 14, + "optional": true + }, + { + "name": "slow_d_period", + "type": "int", + "description": "Slow %D smoothing.", + "default": 3, + "optional": true + }, + { + "name": "slow_k_period", + "type": "int", + "description": "Slow %K smoothing.", + "default": 3, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[StochData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "k", + "type": "float", + "description": "%K \u2014 current close vs. recent range.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "d", + "type": "float", + "description": "%D \u2014 moving average of %K.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/cci": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Commodity Channel Index (CCI), Lambert's mean-reversion oscillator.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# CCI(14) on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\ncci = obb.technical.cci(data=data, length=14)\nobb.technical.cci(length=2, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Lookback window in bars.", + "default": 14, + "optional": true + }, + { + "name": "scalar", + "type": "float", + "description": "Mean-deviation scaling factor (Lambert's original constant).", + "default": 0.015, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[CciData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "cci", + "type": "float", + "description": "CCI value over the trailing ``length`` bars.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/fisher": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Ehlers' Fisher Transform of price with a signal line.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Fisher Transform on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nfisher = obb.technical.fisher(data=data, length=14, signal=1)\nobb.technical.fisher(length=2, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series (uses high + low).", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Fisher transform window.", + "default": 14, + "optional": true + }, + { + "name": "signal", + "type": "int", + "description": "Lag for the signal line.", + "default": 1, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[FisherData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "fisher", + "type": "float", + "description": "Fisher Transform value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "signal", + "type": "float", + "description": "Lagged Fisher signal line.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/cg": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Ehlers' Center of Gravity oscillator.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Center of Gravity on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\ncg = obb.technical.cg(data=data, length=14)\nobb.technical.cg(length=2, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series (uses high/low/close median).", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Lookback window in bars.", + "default": 14, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[CgData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "cg", + "type": "float", + "description": "Center of Gravity value.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/williams_r": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Williams %R, a momentum oscillator inverted from fast stochastic %K.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Williams %R on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nwr = obb.technical.williams_r(data=data, length=14)\nobb.technical.williams_r(length=14, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Lookback window in bars.", + "default": 14, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[WilliamsRData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "williams_r", + "type": "float", + "description": "Williams %R \u2014 close relative to the trailing high/low range (\u2212100 to 0).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/mfi": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Money Flow Index (MFI), a volume-weighted RSI.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# MFI(14) on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nmfi = obb.technical.mfi(data=data, length=14)\nobb.technical.mfi(length=14, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLCV price series \u2014 ``volume`` is required.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Lookback window in bars.", + "default": 14, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[MfiData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "mfi", + "type": "float", + "description": "Money Flow Index \u2014 volume-weighted RSI on typical price (0\u2013100).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/trix": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate TRIX \u2014 the rate of change of a triple-smoothed EMA \u2014 with signal.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# TRIX(30, 9) on daily TSLA closes.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\ntrix = obb.technical.trix(data=data, length=30, signal=9)\nobb.technical.trix(length=5, signal=3, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Target column to apply TRIX to.", + "default": "close", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Triple-EMA window.", + "default": 30, + "optional": true + }, + { + "name": "signal", + "type": "int", + "description": "Signal line EMA length.", + "default": 9, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[TrixData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "trix", + "type": "float", + "description": "1-day ROC of a triple-EMA of ``target``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "signal", + "type": "float", + "description": "EMA of TRIX, length ``signal``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/ultimate_oscillator": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Larry Williams' Ultimate Oscillator across three timeframes.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Ultimate Oscillator on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nuo = obb.technical.ultimate_oscillator(data=data)\nobb.technical.ultimate_oscillator(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "fast", + "type": "int", + "description": "Short-period lookback.", + "default": 7, + "optional": true + }, + { + "name": "medium", + "type": "int", + "description": "Medium-period lookback.", + "default": 14, + "optional": true + }, + { + "name": "slow", + "type": "int", + "description": "Long-period lookback.", + "default": 28, + "optional": true + }, + { + "name": "fast_weight", + "type": "float", + "description": "Weight applied to the fast period.", + "default": 4.0, + "optional": true + }, + { + "name": "medium_weight", + "type": "float", + "description": "Weight applied to the medium period.", + "default": 2.0, + "optional": true + }, + { + "name": "slow_weight", + "type": "float", + "description": "Weight applied to the slow period.", + "default": 1.0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[UltimateOscillatorData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "ultimate_oscillator", + "type": "float", + "description": "Weighted blend of buying pressure over three timeframes (0\u2013100).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/awesome_oscillator": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Bill Williams' Awesome Oscillator as a momentum histogram.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Awesome Oscillator on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nao = obb.technical.awesome_oscillator(data=data)\nobb.technical.awesome_oscillator(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series \u2014 uses median of high/low.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "fast", + "type": "int", + "description": "Short SMA window on median price.", + "default": 5, + "optional": true + }, + { + "name": "slow", + "type": "int", + "description": "Long SMA window on median price.", + "default": 34, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[AwesomeOscillatorData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "awesome_oscillator", + "type": "float", + "description": "Fast SMA minus slow SMA of median price.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/macd": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Moving Average Convergence Divergence (MACD) oscillator.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# MACD(12,26,9) on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nmacd = obb.technical.macd(data=data, fast=12, slow=26, signal=9)\n# MACD with short windows for mock data.\nobb.technical.macd(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], fast=2, slow=3, signal=1)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column to compute MACD on.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "fast", + "type": "int", + "description": "Fast EMA window.", + "default": 12, + "optional": true + }, + { + "name": "slow", + "type": "int", + "description": "Slow EMA window.", + "default": 26, + "optional": true + }, + { + "name": "signal", + "type": "int", + "description": "Signal-line EMA window.", + "default": 9, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[MacdData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "macd", + "type": "float", + "description": "MACD line \u2014 fast EMA minus slow EMA.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "signal", + "type": "float", + "description": "Signal line \u2014 EMA of MACD.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "histogram", + "type": "float", + "description": "MACD minus signal.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/adx": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Wilder's Average Directional Index (ADX), a trend-strength gauge.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# ADX(14) on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nadx = obb.technical.adx(data=data, length=14)\nobb.technical.adx(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "ADX lookback window.", + "default": 14, + "optional": true + }, + { + "name": "scalar", + "type": "float", + "description": "Output magnification factor.", + "default": 100.0, + "optional": true + }, + { + "name": "drift", + "type": "int", + "description": "Difference period for directional movement.", + "default": 1, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[AdxData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adx", + "type": "float", + "description": "Average Directional Index \u2014 trend-strength score.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/di": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Directional Indicators (+DI, -DI) and raw DX from Wilder's system.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# +DI / -DI / DX(14) on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\ndi = obb.technical.di(data=data, length=14)\nobb.technical.di(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Lookback window for +DI / -DI.", + "default": 14, + "optional": true + }, + { + "name": "scalar", + "type": "float", + "description": "Output magnification factor.", + "default": 100.0, + "optional": true + }, + { + "name": "drift", + "type": "int", + "description": "Difference period for directional movement.", + "default": 1, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[DiData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "plus_di", + "type": "float", + "description": "Positive Directional Indicator (+DI).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "minus_di", + "type": "float", + "description": "Negative Directional Indicator (-DI).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "dx", + "type": "float", + "description": "Directional Index \u2014 |+DI - -DI| / (+DI + -DI) * scalar.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/aroon": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Aroon Up, Aroon Down, and Aroon Oscillator series.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Aroon(25) on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\naroon = obb.technical.aroon(data=data, length=25)\nobb.technical.aroon(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Aroon lookback window.", + "default": 25, + "optional": true + }, + { + "name": "scalar", + "type": "float", + "description": "Output magnification factor.", + "default": 100.0, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[AroonData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "aroon_up", + "type": "float", + "description": "Bars since the highest high, scaled.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "aroon_down", + "type": "float", + "description": "Bars since the lowest low, scaled.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "aroon_oscillator", + "type": "float", + "description": "``aroon_up`` minus ``aroon_down``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/choppiness": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Choppiness Index, a trending-vs-sideways regime classifier.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Choppiness(14) on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nchop = obb.technical.choppiness(data=data, length=14)\nobb.technical.choppiness(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Choppiness lookback window.", + "default": 14, + "optional": true + }, + { + "name": "atr_length", + "type": "int", + "description": "ATR window used inside choppiness.", + "default": 1, + "optional": true + }, + { + "name": "scalar", + "type": "float", + "description": "Output magnification factor.", + "default": 100.0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[ChoppinessData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "choppiness", + "type": "float", + "description": "Choppiness Index \u2014 high values mean sideways action.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/obv": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate On-Balance Volume (OBV), a cumulative volume-flow indicator.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# OBV on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nobv = obb.technical.obv(data=data)\nobb.technical.obv(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input OHLCV price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Periods to offset the result.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[ObvData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "obv", + "type": "float", + "description": "On-Balance Volume cumulative total.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/ad": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Accumulation/Distribution line, a volume-flow indicator.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Accumulation/Distribution line on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nad = obb.technical.ad(data=data)\nobb.technical.ad(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input OHLCV price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Periods to offset the result.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[AdData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "ad", + "type": "float", + "description": "Accumulation/Distribution cumulative line.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/adosc": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Chaikin Accumulation/Distribution Oscillator.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Chaikin Oscillator on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nadosc = obb.technical.adosc(data=data, fast=3, slow=10)\nobb.technical.adosc(fast=2, slow=4, data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input OHLCV price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "fast", + "type": "int", + "description": "Fast EMA window.", + "default": 3, + "optional": true + }, + { + "name": "slow", + "type": "int", + "description": "Slow EMA window.", + "default": 10, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Periods to offset the result.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[AdoscData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adosc", + "type": "float", + "description": "Chaikin Accumulation/Distribution Oscillator.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/vwap": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Volume-Weighted Average Price (VWAP), anchored at a chosen boundary.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Daily-anchored VWAP on intraday TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nvwap = obb.technical.vwap(data=data, anchor='D')\nobb.technical.vwap(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input OHLCV price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "anchor", + "type": "str", + "description": "Resample anchor \u2014 pandas offset alias such as ``D``, ``W``, ``ME``. Determines the VWAP reset boundary.", + "default": "D", + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Periods to offset the result.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[VwapData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "vwap", + "type": "float", + "description": "Volume-weighted average price.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/realized_volatility": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate annualised rolling realised volatility under a chosen estimator.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Yang-Zhang realised volatility on daily SPY.\ndata = obb.equity.price.historical(symbol='SPY', start_date='2022-01-01', provider='yfinance').results\nrv = obb.technical.realized_volatility(data=data, model='yang_zhang', window=30)\nobb.technical.realized_volatility(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], model='parkinson')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input OHLC(V) price series.", + "default": null, + "optional": false + }, + { + "name": "model", + "type": "Literal['std', 'parkinson', 'garman_klass', 'hodges_tompkins', 'rogers_satchell', 'yang_zhang']", + "description": "Volatility estimator. ``yang_zhang`` is the lowest-error blend; ``parkinson`` and ``rogers_satchell`` are range-based and drift-invariant; ``hodges_tompkins`` is bias-corrected close-to-close.", + "default": "yang_zhang", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "window", + "type": "int", + "description": "Rolling window length in bars.", + "default": 30, + "optional": true + }, + { + "name": "trading_periods", + "type": "Annotated[int, Gt(gt=0)]", + "description": "Annualisation factor. ``None`` resolves to 365 when ``is_crypto`` is true, otherwise 252.", + "default": null, + "optional": true + }, + { + "name": "is_crypto", + "type": "bool", + "description": "When true and ``trading_periods`` is unset, annualise over 365 instead of 252.", + "default": false, + "optional": true + }, + { + "name": "clean", + "type": "bool", + "description": "Drop the leading warm-up rows where the rolling window is incomplete.", + "default": true, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RealizedVolatilityData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "volatility", + "type": "float", + "description": "Annualised realised volatility per the chosen ``model`` over the trailing ``window`` bars. ``None`` during warm-up when ``clean=False``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "model", + "type": "Literal['std', 'parkinson', 'garman_klass', 'hodges_tompkins', 'rogers_satchell', 'yang_zhang']", + "description": "Echoed on every row so the response is self-describing.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "window", + "type": "int", + "description": "Window used to produce this row.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "trading_periods", + "type": "int", + "description": "Annualisation factor used to produce this row.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/realized_volatility_compare": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute realised volatility under every requested estimator, side by side.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.realized_volatility_compare(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input OHLC price series \u2014 every column required.", + "default": null, + "optional": false + }, + { + "name": "models", + "type": "list[Literal['std', 'parkinson', 'garman_klass', 'hodges_tompkins', 'rogers_satchell', 'yang_zhang']]", + "description": "Estimators to compute. Defaults to all six.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "window", + "type": "int", + "description": "Rolling window length in bars, by default 30.", + "default": 30, + "optional": true + }, + { + "name": "trading_periods", + "type": "Annotated[int, Gt(gt=0)]", + "description": "Annualisation factor. When unset, resolves to 365 if ``is_crypto`` is ``True``, otherwise 252.", + "default": null, + "optional": true + }, + { + "name": "is_crypto", + "type": "bool", + "description": "When ``True`` and ``trading_periods`` is unset, annualise over 365 instead of 252, by default ``False``.", + "default": false, + "optional": true + }, + { + "name": "clean", + "type": "bool", + "description": "Drop rows where ANY requested model has not yet warmed up.", + "default": true, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RealizedVolatilityCompareData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "std", + "type": "float", + "description": "Close-to-close standard-deviation volatility.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "parkinson", + "type": "float", + "description": "Parkinson range-based volatility.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "garman_klass", + "type": "float", + "description": "Garman-Klass OHLC volatility.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "hodges_tompkins", + "type": "float", + "description": "Bias-corrected close-to-close volatility.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "rogers_satchell", + "type": "float", + "description": "Rogers-Satchell drift-invariant volatility.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "yang_zhang", + "type": "float", + "description": "Yang-Zhang minimum-variance volatility.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/cones": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Build a volatility-cone snapshot summarising realised volatility by window.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Get volatility cones for the past two years of SPY.\ndata = obb.equity.price.historical(symbol='SPY', start_date='2022-01-01', provider='yfinance').results\ncones = obb.technical.cones(data=data, model='yang_zhang')\nobb.technical.cones(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "lower_q", + "type": "float", + "description": "Lower quantile for the cone band (0\u20131, exclusive of 1).", + "default": 0.25, + "optional": true + }, + { + "name": "upper_q", + "type": "float", + "description": "Upper quantile for the cone band (0\u20131, exclusive of 1).", + "default": 0.75, + "optional": true + }, + { + "name": "model", + "type": "Literal['std', 'parkinson', 'garman_klass', 'hodges_tompkins', 'rogers_satchell', 'yang_zhang']", + "description": "Estimator applied to each rolling window, by default ``\"std\"``.", + "default": "std", + "optional": true + }, + { + "name": "is_crypto", + "type": "bool", + "description": "When ``True`` and ``trading_periods`` is unset, annualise over 365 instead of 252, by default ``False``.", + "default": false, + "optional": true + }, + { + "name": "trading_periods", + "type": "Annotated[int, Gt(gt=0)]", + "description": "Annualisation factor. When unset, resolves to 365 if ``is_crypto`` is ``True``, otherwise 252.", + "default": null, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[ConesData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "window", + "type": "int", + "description": "Rolling window length in bars.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "realized", + "type": "float", + "description": "Most-recent annualised volatility.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "min", + "type": "float", + "description": "Historical minimum of annualised volatility at this window.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "lower", + "type": "float", + "description": "Lower-quantile cone band.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "median", + "type": "float", + "description": "Historical median of annualised volatility at this window.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "upper", + "type": "float", + "description": "Upper-quantile cone band.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "max", + "type": "float", + "description": "Historical maximum of annualised volatility at this window.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/atr": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate the Average True Range (ATR), Wilder's volatility measure.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# ATR(14) on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\natr = obb.technical.atr(data=data, length=14)\nobb.technical.atr(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], length=14)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "ATR lookback window.", + "default": 14, + "optional": true + }, + { + "name": "mamode", + "type": "Literal['sma', 'ema', 'wma', 'rma']", + "description": "Smoothing applied to true-range. ``rma`` is the Wilder default.", + "default": "rma", + "optional": true + }, + { + "name": "drift", + "type": "int", + "description": "Difference period for true-range.", + "default": 1, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Shift the output series by this many bars, by default 0.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[AtrData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "atr", + "type": "float", + "description": "Average true range over the trailing ``length`` bars.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/fib": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute Fibonacci retracement levels between a swing high and swing low.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Fibonacci retracement on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\nfib = obb.technical.fib(data=data, period=120)\nobb.technical.fib(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC(V) price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "close_column", + "type": "Literal['close', 'adj_close']", + "description": "Column used for high/low detection.", + "default": "close", + "optional": true + }, + { + "name": "period", + "type": "int", + "description": "Lookback in bars for retracement.", + "default": 120, + "optional": true + }, + { + "name": "start_date", + "type": "str", + "description": "Explicit retracement start date.", + "default": null, + "optional": true + }, + { + "name": "end_date", + "type": "str", + "description": "Explicit retracement end date.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[FibData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "level", + "type": "str", + "description": "Retracement percentage label, e.g. ``38.2%``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Price at the retracement level.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/demark": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute Tom DeMark sequential up- and down-count exhaustion indicators.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# DeMark sequential on daily TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\ndemark = obb.technical.demark(data=data)\nobb.technical.demark(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Column to evaluate.", + "default": "close", + "optional": true + }, + { + "name": "show_all", + "type": "bool", + "description": "When True show counts 1-13; False shows only 6-9.", + "default": true, + "optional": true + }, + { + "name": "asint", + "type": "bool", + "description": "Cast counts to integers and fill NaN with 0.", + "default": true, + "optional": true + }, + { + "name": "offset", + "type": "int", + "description": "Periods to offset the result.", + "default": 0, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[DemarkData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "up", + "type": "float | int", + "description": "Upward sequential count.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "down", + "type": "float | int", + "description": "Downward sequential count.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/pivot_points": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute pivot-point support and resistance bands under multiple methods.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Classic daily pivot points on TSLA.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='fmp').results\npivots = obb.technical.pivot_points(data=data, method='classic', anchor='day')\nobb.technical.pivot_points(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``, by default ``\"date\"``.", + "default": "date", + "optional": true + }, + { + "name": "method", + "type": "Literal['classic', 'fibonacci', 'woodie', 'camarilla', 'demark']", + "description": "Pivot-point family. ``camarilla`` is the only one that fills r4/s4.", + "default": "classic", + "optional": true + }, + { + "name": "anchor", + "type": "Literal['day', 'week', 'month']", + "description": "Resample anchor for the pivot calculation.", + "default": "day", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[PivotPointsData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Period start date of the resampled pivot.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "pivot", + "type": "float", + "description": "Central pivot price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "r1", + "type": "float", + "description": "First resistance band above the pivot.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "r2", + "type": "float", + "description": "Second resistance band above the pivot.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "r3", + "type": "float", + "description": "Third resistance band above the pivot.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "r4", + "type": "float", + "description": "Only populated for camarilla.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "s1", + "type": "float", + "description": "First support band below the pivot.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "s2", + "type": "float", + "description": "Second support band below the pivot.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "s3", + "type": "float", + "description": "Third support band below the pivot.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "s4", + "type": "float", + "description": "Only populated for camarilla.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/clenow": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate Clenow Volatility-Adjusted Momentum on log prices.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Clenow momentum on TSLA over the trailing 90 days.\ndata = obb.equity.price.historical(symbol='TSLA', start_date='2023-01-01', provider='yfinance').results\nout = obb.technical.clenow(data=data, period=90)\nobb.technical.clenow(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], period=2)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Column to regress.", + "default": "close", + "optional": true + }, + { + "name": "period", + "type": "int", + "description": "Lookback window for the log-price regression.", + "default": 90, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[ClenowData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "predicted", + "type": "float", + "description": "Best-fit log-linear value at this date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "r2", + "type": "float", + "description": "R-squared of the regression fit on log prices.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "coefficient", + "type": "float", + "description": "Annualised slope of the log-price regression.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "annualized_coefficient", + "type": "float", + "description": "``coefficient * r2`` - Clenow's momentum factor.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/drawdown": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute cumulative return, running peak, drawdown, and underwater duration.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.drawdown(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Column to evaluate drawdowns on.", + "default": "close", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[DrawdownData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "cumulative_return", + "type": "float", + "description": "Cumulative return from the first observation.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "running_peak", + "type": "float", + "description": "Running maximum of ``cumulative_return``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "drawdown", + "type": "float", + "description": "Percentage drop from the running peak (negative or zero).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "drawdown_duration_days", + "type": "int", + "description": "Consecutive observations since the most recent peak.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/returns_stats": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Calculate distributional and risk-adjusted statistics on simple returns.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.returns_stats(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Column to evaluate returns on.", + "default": "close", + "optional": true + }, + { + "name": "frequency", + "type": "Literal['daily', 'weekly', 'monthly', 'quarterly', 'annual']", + "description": "Annualisation frequency for Sharpe/Sortino/Calmar/risk-free.", + "default": "daily", + "optional": true + }, + { + "name": "risk_free_rate", + "type": "float", + "description": "Annualised risk-free rate used for Sharpe and Sortino.", + "default": 0.0, + "optional": true + }, + { + "name": "window", + "type": "Annotated[int, Gt(gt=0)]", + "description": "Rolling window. ``None`` returns a single summary row.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[ReturnsStatsData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Window end date, or ``None`` for the summary row.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "mean_return", + "type": "float", + "description": "Arithmetic mean of simple returns over the window.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "std_return", + "type": "float", + "description": "Sample standard deviation of returns (ddof=1).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "skew", + "type": "float", + "description": "Skewness of the return distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kurtosis", + "type": "float", + "description": "Excess kurtosis of the return distribution.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "sharpe", + "type": "float", + "description": "Annualised Sharpe ratio of excess returns.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "sortino", + "type": "float", + "description": "Annualised Sortino ratio using downside deviation.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "calmar", + "type": "float", + "description": "Annualised return divided by the absolute value of max drawdown.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "max_drawdown", + "type": "float", + "description": "Worst peak-to-trough drawdown over the window (negative).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "var_95", + "type": "float", + "description": "5th-percentile return - historical Value-at-Risk at 95%.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "cvar_95", + "type": "float", + "description": "Mean of returns at or below ``var_95`` - historical Conditional VaR.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/stationarity": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Test a series for stationarity using ADF, KPSS, or both with a combined verdict.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.stationarity(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Column to test for stationarity.", + "default": "close", + "optional": true + }, + { + "name": "test", + "type": "Literal['adf', 'kpss', 'both']", + "description": "Which test(s) to run.", + "default": "both", + "optional": true + }, + { + "name": "regression", + "type": "Literal['c', 'ct', 'ctt', 'n']", + "description": "Regression component: constant, trend, quadratic-trend, or none.", + "default": "c", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[StationarityData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "adf_statistic", + "type": "float", + "description": "Augmented Dickey-Fuller test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_pvalue", + "type": "float", + "description": "ADF p-value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_critical_1pct", + "type": "float", + "description": "ADF critical value at the 1% significance level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_critical_5pct", + "type": "float", + "description": "ADF critical value at the 5% significance level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_critical_10pct", + "type": "float", + "description": "ADF critical value at the 10% significance level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adf_verdict", + "type": "None", + "description": "ADF verdict at 5%, or ``\"skipped\"`` when the test was not run.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kpss_statistic", + "type": "float", + "description": "Kwiatkowski-Phillips-Schmidt-Shin test statistic.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kpss_pvalue", + "type": "float", + "description": "KPSS p-value.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kpss_critical_1pct", + "type": "float", + "description": "KPSS critical value at the 1% significance level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kpss_critical_5pct", + "type": "float", + "description": "KPSS critical value at the 5% significance level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kpss_critical_10pct", + "type": "float", + "description": "KPSS critical value at the 10% significance level.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kpss_verdict", + "type": "None", + "description": "KPSS verdict at 5%, or ``\"skipped\"`` when the test was not run.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "overall_verdict", + "type": "Literal['stationary', 'non_stationary', 'trend_stationary', 'inconclusive']", + "description": "Combined verdict from the available ADF and KPSS results.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/hurst": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Estimate the Hurst exponent via Rescaled-Range analysis or DFA.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.hurst(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Column to evaluate.", + "default": "close", + "optional": true + }, + { + "name": "method", + "type": "Literal['rs', 'dfa']", + "description": "``rs`` = Rescaled Range; ``dfa`` = Detrended Fluctuation.", + "default": "rs", + "optional": true + }, + { + "name": "min_lag", + "type": "int", + "description": "Smallest window length.", + "default": 2, + "optional": true + }, + { + "name": "max_lag", + "type": "int", + "description": "Largest window length (exclusive).", + "default": 100, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[HurstData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "hurst_exponent", + "type": "float", + "description": "Estimated Hurst exponent in ``[0, 1]``. ``None`` when the fit could not be computed.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "interpretation", + "type": "Literal['trending', 'mean_reverting', 'random_walk']", + "description": "Qualitative classification: ``trending`` for H > 0.55, ``mean_reverting`` for H < 0.45, otherwise ``random_walk``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "confidence", + "type": "float", + "description": "R-squared of the log-log fit used to estimate the exponent.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/autocorrelation": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute autocorrelation (ACF) and partial autocorrelation (PACF) with bands.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.autocorrelation(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Column to evaluate.", + "default": "close", + "optional": true + }, + { + "name": "use_returns", + "type": "bool", + "description": "Difference and percent-change before computing.", + "default": true, + "optional": true + }, + { + "name": "max_lag", + "type": "int", + "description": "Largest lag to compute.", + "default": 40, + "optional": true + }, + { + "name": "method", + "type": "Literal['acf', 'pacf', 'both']", + "description": "Which functions to compute.", + "default": "both", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[AutocorrelationData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "lag", + "type": "int", + "description": "Lag in bars (0 is the series with itself, by definition 1.0 for ACF).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "acf", + "type": "float", + "description": "Sample autocorrelation at this lag, or ``None`` when ACF was skipped.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "pacf", + "type": "float", + "description": "Sample partial autocorrelation at this lag, or ``None`` when PACF was skipped.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "acf_confidence_lower", + "type": "float", + "description": "Lower bound of the 95% confidence band for the ACF estimate.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "acf_confidence_upper", + "type": "float", + "description": "Upper bound of the 95% confidence band for the ACF estimate.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "significant", + "type": "bool", + "description": "``True`` when the ACF estimate at this lag (other than lag 0) lies outside the 95% confidence band.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/relative_rotation": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute the Relative Rotation Graph (RRG) for a basket against a benchmark.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Relative Strength Ratio and Momentum for a basket of symbols against a benchmark.\ndata = obb.equity.price.historical(symbol='AAPL,MSFT,GOOGL,META,AMZN,TSLA,SPY', start_date='2022-01-01', provider='yfinance').results\nrr = obb.technical.relative_rotation(data=data, benchmark='SPY')\nobb.technical.relative_rotation(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], benchmark='SPY')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "The data to be used for the relative rotation calculations. This should be the multi-symbol output from the 'equity.price.historical' endpoint, or similar, at a daily interval. Or a pivot table with the 'date' column as the index, the symbols as the columns, and the 'study' as the values. It is recommended to use the 'equity.price.historical' endpoint to get the data, and feed the results as-is.", + "default": null, + "optional": false + }, + { + "name": "benchmark", + "type": "str", + "description": "The symbol to be used as the benchmark.", + "default": null, + "optional": false + }, + { + "name": "study", + "type": "Literal['price', 'volume', 'volatility']", + "description": "The data point for the calculations. If 'price', the closing price will be used. If 'volatility', the standard deviation of the closing price will be used. If 'data' is supplied as a pivot table, the 'study' will assume the values are the closing price and 'volume' will be ignored.", + "default": "price", + "optional": true + }, + { + "name": "long_period", + "type": "int", + "description": "The length of the long period for momentum calculation, by default is 252. Adjust this value, to 365, when supplying assets such as crypto.", + "default": 252, + "optional": true + }, + { + "name": "short_period", + "type": "int", + "description": "The length of the short period for momentum calculation, by default is 21. Adjust this value, to 30, when supplying assets such as crypto.", + "default": 21, + "optional": true + }, + { + "name": "window", + "type": "int", + "description": "The length of window for the standard deviation calculation, by default is 21. Adjust this value, to 30, when supplying assets such as crypto.", + "default": 21, + "optional": true + }, + { + "name": "trading_periods", + "type": "int", + "description": "The number of trading periods per year, for the standard deviation calculation, by default is 252. Adjust this value, to 365, when supplying assets such as crypto.", + "default": 252, + "optional": true + }, + { + "name": "chart_params", + "type": "dict[str, Any]", + "description": "Additional parameters to pass when `chart=True` and the `openbb-charting` extension is installed. Parameters can be passed again to redraw the chart using the charting.to_chart() method of the response.\n\n ChartParams\n -----------\n date: Optional[str]\n A target end date within the data, by default is the last date in the data.\n show_tails: bool\n Show the tails on the chart, by default is True.\n tail_periods: Optional[int]\n Number of periods to show in the tails, by default is 16.\n tail_interval: Literal['day', 'week', 'month']\n Interval to show the tails, by default is 'week'.\n title: Optional[str]\n Title of the chart.", + "default": null, + "optional": true + }, + { + "name": "chart", + "type": "bool", + "description": "Whether to create a chart or not, by default False.", + "default": false, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "RelativeRotationData", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbols", + "type": "list[str]", + "description": "The symbols that are being compared against the benchmark.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "benchmark", + "type": "str", + "description": "The benchmark symbol, as entered by the user.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "study", + "type": "Literal['price', 'volume', 'volatility']", + "description": "The data point for the study, as entered by the user.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "long_period", + "type": "int", + "description": "The length of the long period for momentum calculation, as entered by the user.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "short_period", + "type": "int", + "description": "The length of the short period for momentum calculation, as entered by the user.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "window", + "type": "int", + "description": "The length of window for the standard deviation calculation, as entered by the user.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "trading_periods", + "type": "int", + "description": "The number of trading periods per year, for the standard deviation calculation, as entered by the user.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "start_date", + "type": "str", + "description": "The start date of the data after adjusting the length of the data for the calculations.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "str", + "description": "The end date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbols_data", + "type": "list[Data]", + "description": "The data representing the selected 'study' for each symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "benchmark_data", + "type": "list[Data]", + "description": "The data representing the selected 'study' for the benchmark.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "rs_ratios", + "type": "list[Data]", + "description": "The normalized relative strength ratios data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "rs_momentum", + "type": "list[Data]", + "description": "The normalized relative strength momentum data.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/signals/crossovers": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Detect bullish and bearish moving-average crossover events.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# 20/50 SMA crossover events on daily SPY.\ndata = obb.equity.price.historical(symbol='SPY', start_date='2022-01-01', provider='yfinance').results\nevents = obb.technical.crossovers(data=data, fast_length=20, slow_length=50)\nobb.technical.signals.crossovers(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input price series.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Column on which to compute moving averages.", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "fast_length", + "type": "int", + "description": "Lookback for the fast moving average.", + "default": 20, + "optional": true + }, + { + "name": "slow_length", + "type": "int", + "description": "Lookback for the slow moving average.", + "default": 50, + "optional": true + }, + { + "name": "mamode", + "type": "Literal['sma', 'ema', 'wma', 'hma', 'zlma']", + "description": "Moving-average flavour. ``sma`` is the default; ``ema``, ``wma``, ``hma`` and ``zlma`` are also supported.", + "default": "sma", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[CrossoverEvent]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Date of the crossover bar.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "direction", + "type": "Literal['bullish', 'bearish']", + "description": "``bullish`` when fast crosses above slow, ``bearish`` when fast crosses below.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "fast_value", + "type": "float", + "description": "Fast moving-average value at the crossover bar.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "slow_value", + "type": "float", + "description": "Slow moving-average value at the crossover bar.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Target-column price at the crossover bar.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "distance", + "type": "float", + "description": "``fast_value - slow_value``. Positive for bullish, negative for bearish crossovers.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/signals/oscillator_signals": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Label every bar with its oscillator regime and emit crossing events.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.signals.oscillator_signals(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], indicator='rsi', length=14)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input OHLC(V) price series.", + "default": null, + "optional": false + }, + { + "name": "indicator", + "type": "Literal['rsi', 'mfi', 'stoch', 'williams_r', 'cci']", + "description": "Which oscillator to evaluate against its threshold bands.", + "default": null, + "optional": false + }, + { + "name": "target", + "type": "str", + "description": "Price column used for oscillators that take a single series (rsi, cci).", + "default": "close", + "optional": true + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Lookback window for the oscillator.", + "default": 14, + "optional": true + }, + { + "name": "overbought_threshold", + "type": "float", + "description": "Upper band. ``None`` resolves to the indicator default (rsi 70, mfi 80, stoch 80, williams_r -20, cci 100).", + "default": null, + "optional": true + }, + { + "name": "oversold_threshold", + "type": "float", + "description": "Lower band. ``None`` resolves to the indicator default (rsi 30, mfi 20, stoch 20, williams_r -80, cci -100).", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[OscillatorSignal]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "float", + "description": "Oscillator value on this bar.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "regime", + "type": "Literal['overbought', 'oversold', 'neutral']", + "description": "Region of the oscillator on this bar.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "crossed_into_overbought", + "type": "bool", + "description": "True on the bar the regime first becomes overbought.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "crossed_into_oversold", + "type": "bool", + "description": "True on the bar the regime first becomes oversold.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "crossed_out_of_overbought", + "type": "bool", + "description": "True on the bar the regime leaves overbought (to neutral or oversold).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "crossed_out_of_oversold", + "type": "bool", + "description": "True on the bar the regime leaves oversold (to neutral or overbought).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/signals/divergences": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Detect regular and hidden divergences between price swings and an oscillator.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# RSI divergences on daily SPY.\ndata = obb.equity.price.historical(symbol='SPY', start_date='2022-01-01', provider='yfinance').results\nevents = obb.technical.divergences(data=data, indicator='rsi')\nobb.technical.signals.divergences(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], indicator='rsi')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "indicator", + "type": "Literal['rsi', 'macd', 'stoch', 'cci']", + "description": "Oscillator paired with price for divergence detection.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "indicator_length", + "type": "int", + "description": "Lookback length used inside the oscillator.", + "default": 14, + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Price column used for swings.", + "default": "close", + "optional": true + }, + { + "name": "lookback", + "type": "int", + "description": "Number of recent bars in which to search for swing pairs.", + "default": 60, + "optional": true + }, + { + "name": "min_swing_distance", + "type": "int", + "description": "Minimum number of bars separating two swing points. Also doubles as the half-window for local-extreme detection.", + "default": 5, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[DivergenceEvent]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "confirmation_date", + "type": "date | datetime | str", + "description": "Bar of the later (confirming) swing.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "prior_swing_date", + "type": "date | datetime | str", + "description": "Bar of the earlier swing in the pair.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "kind", + "type": "Literal['regular_bullish', 'hidden_bullish', 'regular_bearish', 'hidden_bearish']", + "description": "``regular_bullish`` / ``regular_bearish`` flag reversal signals; ``hidden_*`` flag trend-continuation signals.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "price_at_prior", + "type": "float", + "description": "Target-column price at ``prior_swing_date``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "price_at_confirmation", + "type": "float", + "description": "Target-column price at ``confirmation_date``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "indicator_at_prior", + "type": "float", + "description": "Oscillator value at ``prior_swing_date``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "indicator_at_confirmation", + "type": "float", + "description": "Oscillator value at ``confirmation_date``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "strength", + "type": "float", + "description": "0-1 score combining the relative price and indicator displacements. Higher means a wider, more emphatic divergence.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/signals/breakouts": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Detect channel breakouts where price closes beyond a rolling envelope.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.technical.signals.breakouts(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}], method='donchian', length=20)\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "method", + "type": "Literal['donchian', 'bollinger']", + "description": "Channel definition: Donchian (rolling high/low) or Bollinger (mean +/- ``band_std`` * stdev).", + "default": "donchian", + "optional": true + }, + { + "name": "length", + "type": "int", + "description": "Lookback window for the channel.", + "default": 20, + "optional": true + }, + { + "name": "band_std", + "type": "Annotated[float, Gt(gt=0)]", + "description": "Bollinger band multiplier. Ignored for ``donchian``. ``None`` resolves to 2.0 when ``method='bollinger'``.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[BreakoutEvent]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Bar on which the breakout occurred.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "direction", + "type": "Literal['upside', 'downside']", + "description": "Which band was crossed.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float", + "description": "Close price on the breakout bar.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "band", + "type": "float", + "description": "Previous bar's band level that was breached.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "magnitude", + "type": "float", + "description": "Signed distance: ``price - band``. Positive for upside, negative for downside.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "bars_in_range", + "type": "int", + "description": "Bars since the most recent breakout (in either direction).", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/signals/candlestick_patterns": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Scan OHLC bars for classical candlestick patterns and emit one row per match.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Scan all supported patterns on daily SPY.\ndata = obb.equity.price.historical(symbol='SPY', start_date='2022-01-01', provider='yfinance').results\npatterns = obb.technical.candlestick_patterns(data=data)\nobb.technical.signals.candlestick_patterns(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "patterns", + "type": "list[str]", + "description": "Subset of patterns to scan for. ``None`` runs every entry in ``SUPPORTED_PATTERNS``.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[PatternEvent]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Date of the pattern bar.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "pattern", + "type": "str", + "description": "Pattern name, lower-snake-case.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "direction", + "type": "Literal['bullish', 'bearish', 'neutral']", + "description": "Bias implied by the pattern: ``bullish``, ``bearish`` or ``neutral``.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "confidence", + "type": "float", + "description": "Heuristic 0-1 score derived from body/shadow ratios. Higher means the bar matches the textbook pattern more cleanly.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/signals/regime": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Label each bar with a market regime from ADX and Choppiness Index pairs.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Daily regime labelling on SPY.\ndata = obb.equity.price.historical(symbol='SPY', start_date='2022-01-01', provider='yfinance').results\nregime = obb.technical.regime(data=data)\nobb.technical.signals.regime(data=[{'date': '2023-01-02', 'open': 110.0, 'high': 120.0, 'low': 100.0, 'close': 115.0, 'volume': 10000.0}, {'date': '2023-01-03', 'open': 165.0, 'high': 180.0, 'low': 150.0, 'close': 172.5, 'volume': 15000.0}, {'date': '2023-01-04', 'open': 146.67, 'high': 160.0, 'low': 133.33, 'close': 153.33, 'volume': 13333.33}, {'date': '2023-01-05', 'open': 137.5, 'high': 150.0, 'low': 125.0, 'close': 143.75, 'volume': 12500.0}, {'date': '2023-01-06', 'open': 132.0, 'high': 144.0, 'low': 120.0, 'close': 138.0, 'volume': 12000.0}])\n```\n\n", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "OHLC price series.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "adx_length", + "type": "int", + "description": "ADX lookback window.", + "default": 14, + "optional": true + }, + { + "name": "choppiness_length", + "type": "int", + "description": "Choppiness Index lookback window.", + "default": 14, + "optional": true + }, + { + "name": "adx_trend_threshold", + "type": "float", + "description": "ADX above this threshold marks a trending bar. Half this value is the cutoff for a weak trend.", + "default": 25.0, + "optional": true + }, + { + "name": "choppiness_range_threshold", + "type": "float", + "description": "Choppiness above this threshold marks a ranging bar. The default 61.8 is a common Fibonacci-derived cutoff.", + "default": 61.8, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[RegimeData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "adx", + "type": "float", + "description": "ADX reading at this bar. ``None`` during warm-up.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "choppiness", + "type": "float", + "description": "Choppiness Index reading at this bar. ``None`` during warm-up.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "regime", + "type": "Literal['strong_trend', 'weak_trend', 'ranging', 'transition']", + "description": "Classified regime for this bar.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "regime_changed", + "type": "bool", + "description": "``True`` when this bar's regime differs from the previous bar's.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/multi": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Run multiple indicators on a single price series and merge them by date.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Input OHLC(V) series.", + "default": null, + "optional": false + }, + { + "name": "indicators", + "type": "list[MultiIndicatorRequest]", + "description": "Indicators to compute, in arbitrary order.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Target column forwarded to indicators that accept one.", + "default": "close", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[MultiResultRow]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "values", + "type": "dict[str, float | None]", + "description": "Indicator outputs for this date keyed as ``\".\"``. Numeric columns only \u2014 non-numeric outputs are skipped.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/correlation": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute rolling pairwise correlation of returns across symbols.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Long-format multi-symbol price data with a ``symbol`` column.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Column to compute correlation on.", + "default": "close", + "optional": true + }, + { + "name": "window", + "type": "int", + "description": "Rolling window length in bars.", + "default": 60, + "optional": true + }, + { + "name": "method", + "type": "Literal['pearson', 'spearman', 'kendall']", + "description": "Correlation method passed through to ``pandas.DataFrame.corr``.", + "default": "pearson", + "optional": true + }, + { + "name": "pairs", + "type": "list[tuple[str, str]]", + "description": "Optional explicit list of ``(symbol_a, symbol_b)`` pairs. Defaults to all unique pairs.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[CorrelationData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | datetime | str", + "description": "Observation date.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbol_a", + "type": "str", + "description": "First symbol of the pair.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbol_b", + "type": "str", + "description": "Second symbol of the pair.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "correlation", + "type": "float", + "description": "Rolling correlation between ``symbol_a`` and ``symbol_b``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/correlation_matrix": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Build a point-in-time correlation matrix across every symbol in the input.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Long-format multi-symbol price data with a ``symbol`` column.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Column to correlate.", + "default": "close", + "optional": true + }, + { + "name": "window", + "type": "Annotated[int, Gt(gt=0)]", + "description": "If set, restrict correlation to the trailing ``window`` rows before ``as_of_date``. ``None`` uses every available row.", + "default": null, + "optional": true + }, + { + "name": "method", + "type": "Literal['pearson', 'spearman', 'kendall']", + "description": "Correlation method, by default ``\"pearson\"``.", + "default": "pearson", + "optional": true + }, + { + "name": "as_of_date", + "type": "date | datetime | str", + "description": "Anchor date for the snapshot. ``None`` uses the last available row in the input.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[CorrelationMatrixData]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "as_of_date", + "type": "date | datetime | str", + "description": "Anchor date used for the snapshot.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbols", + "type": "list[str]", + "description": "Symbols, in matrix order.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "matrix", + "type": "list[list[float]]", + "description": "Correlation values.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/screen": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Filter a multi-symbol basket by indicator-driven conditions.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "data", + "type": "Data | DataFrame | Series | dict | list | list['DataFrame'] | list['Series'] | list[Data] | ndarray", + "description": "Long-format multi-symbol data with a ``symbol`` column.", + "default": null, + "optional": false + }, + { + "name": "conditions", + "type": "list[ScreenCondition]", + "description": "Predicates to evaluate against each symbol's indicators.", + "default": null, + "optional": false + }, + { + "name": "index", + "type": "str", + "description": "Index column name in ``data``.", + "default": "date", + "optional": true + }, + { + "name": "target", + "type": "str", + "description": "Default target column.", + "default": "close", + "optional": true + }, + { + "name": "combine", + "type": "Literal['and', 'or']", + "description": "``and`` returns symbols matching every condition; ``or`` returns symbols matching at least one.", + "default": "and", + "optional": true + }, + { + "name": "as_of_date", + "type": "date | datetime | str", + "description": "Anchor date. Defaults to the latest date per symbol when unset.", + "default": null, + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "list[ScreenMatch]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol that matched.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "as_of_date", + "type": "date | datetime | str", + "description": "Date used for evaluation.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "matched_conditions", + "type": "int", + "description": "How many conditions fired.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "values", + "type": "dict[str, float]", + "description": "Latest indicator values that contributed to the match, keyed by ``\".\"``.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/technical/indicators": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Return the catalogue of registered technical indicators.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "category", + "type": "None", + "description": "Filter to a single family, or ``all`` to return everything.", + "default": "all", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "IndicatorsResponse", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "indicators", + "type": "list[IndicatorEntry]", + "description": "Catalogue entries matching the requested category.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "", + "openapi_extra": { + "model": "" + } + }, + "/yfinance/search": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Search Yahoo Finance for symbols, optionally narrowing by asset type.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.search(query='apple', provider='yfinance')\n# Narrow the search to a single asset type.\nobb.yfinance.search(query='growth', asset_type='mutualfund', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "query", + "type": "str | None", + "description": "Search query.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "asset_type", + "type": "Literal['all', 'equity', 'etf', 'index', 'mutualfund', 'future', 'currency', 'crypto'] | None", + "description": "Restrict the search to a single asset type. Empty searches all.", + "default": "all", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "limit", + "type": "int | None", + "description": "The maximum number of results to return.", + "default": 30, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfSymbolSearch", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the asset.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "description", + "type": "str | None", + "description": "A short description of the asset.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange", + "type": "str | None", + "description": "The exchange the asset trades on.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "asset_type", + "type": "str | None", + "description": "The type of asset (equity, etf, index, etc.).", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfSymbolSearch", + "openapi_extra": { + "widget_config": { + "name": "Yahoo Finance Symbol Search", + "description": "Search Yahoo Finance for symbols; click a symbol in the table to change the grouped widgets' symbol.", + "type": "table", + "category": "Equity", + "subCategory": "Search", + "widgetId": "yfinance_search_obb", + "gridData": { + "w": 14, + "h": 11 + }, + "data": { + "table": { + "columnsDefs": [ + { + "field": "symbol", + "headerName": "Symbol", + "renderFn": "cellOnClick", + "renderFnParams": { + "actionType": "groupBy", + "groupByParamName": "symbol" + } + }, + { + "field": "name", + "headerName": "Name" + }, + { + "field": "exchange", + "headerName": "Exchange" + }, + { + "field": "asset_type", + "headerName": "Type" + } + ] + } + } + }, + "model": "YfSymbolSearch" + } + }, + "/yfinance/industry_choices": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Return Yahoo industry choices for a sector to drive cascading dropdowns.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "sector", + "type": "str", + "description": "", + "default": null, + "optional": true + }, + { + "name": "is_workspace", + "type": "bool", + "description": "", + "default": false, + "optional": true + }, + { + "name": "provider", + "type": "str", + "description": "", + "default": "yfinance", + "optional": true + } + ] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "exclude": true + }, + "model": "" + } + }, + "/yfinance/news": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Search Yahoo Finance news by free-text query or symbol.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.news(query='federal reserve', provider='yfinance')\nobb.yfinance.news(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "query", + "type": "str | None", + "description": "Free-text news search query.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str | None", + "description": "A ticker symbol to fetch news for.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "limit", + "type": "int | None", + "description": "The maximum number of results to return.", + "default": 20, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "fetch_body", + "type": "bool | None", + "description": "Fetch the full article body for each result. Disable for a faster response with only the headline and excerpt.", + "default": true, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfNews", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "title", + "type": "str", + "description": "The title of the article.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "date", + "type": "datetime | None | str", + "description": "The date the article was published.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "author", + "type": "str | None", + "description": "The publisher/author of the article.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "excerpt", + "type": "str | None", + "description": "A short preview of the article.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "body", + "type": "str | None", + "description": "The full article body, as markdown text.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "url", + "type": "str | None", + "description": "The URL to the article.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "symbols", + "type": "str | None", + "description": "Comma-separated symbols related to the article.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfNews", + "openapi_extra": { + "widget_config": { + "name": "Yahoo Finance News", + "description": "Yahoo Finance news with the full article body.", + "type": "newsfeed", + "category": "News", + "widgetId": "yfinance_news_obb", + "gridData": { + "w": 12, + "h": 10 + } + }, + "model": "YfNews" + } + }, + "/yfinance/private_companies": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get private-company markets from Yahoo Finance (Crunchbase data).", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.private_companies(category='highest_valuation', provider='yfinance')\nobb.yfinance.private_companies(category='most_funded', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "category", + "type": "Literal['highest_valuation', '52_week_gainers', 'recently_funded', 'most_funded'] | None", + "description": "The private-companies market to retrieve.", + "default": "highest_valuation", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "limit", + "type": "int | None", + "description": "The maximum number of companies to return.", + "default": 100, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfPrivateCompanies", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The Yahoo private-company symbol (e.g. ANTH.PVT).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The company name.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "valuation", + "type": "float | None", + "description": "The latest implied valuation.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "funding_to_date", + "type": "float | None", + "description": "The total funding raised to date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "latest_amount_raised", + "type": "float | None", + "description": "The amount raised in the latest funding round.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "latest_funding_date", + "type": "date | None", + "description": "The date of the latest funding round.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "latest_share_class", + "type": "str | None", + "description": "The latest share class issued.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "lead_investor", + "type": "str | None", + "description": "The lead investor of the latest round.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "total_funding_rounds", + "type": "int | None", + "description": "The total number of funding rounds.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "change_52_week", + "type": "float | None", + "description": "The 52-week change in the indexed price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 1 + } + }, + { + "name": "price", + "type": "float | None", + "description": "The latest indexed private-company price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "employees", + "type": "int | None", + "description": "The number of full-time employees.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "date_founded", + "type": "date | None", + "description": "The date the company was founded.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sector", + "type": "str | None", + "description": "The company sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "industry", + "type": "str | None", + "description": "The company industry.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "city", + "type": "str | None", + "description": "The headquarters city.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "state", + "type": "str | None", + "description": "The headquarters state.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "country", + "type": "str | None", + "description": "The headquarters country.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "website", + "type": "str | None", + "description": "The company website.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "lead_executives", + "type": "str | None", + "description": "Comma-separated lead executives and titles.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "summary", + "type": "str | None", + "description": "A short business summary of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfPrivateCompanies", + "openapi_extra": { + "widget_config": { + "name": "Yahoo Finance Private Companies", + "description": "Private-company markets from Yahoo Finance (Crunchbase data): valuation, funding, investors and more.", + "type": "table", + "category": "Equity", + "subCategory": "Private Companies", + "widgetId": "yfinance_private_companies_obb", + "gridData": { + "w": 40, + "h": 15 + }, + "data": { + "table": { + "columnsDefs": [ + { + "field": "name", + "headerName": "Company", + "pinned": "left", + "width": 170 + }, + { + "field": "valuation", + "headerName": "Valuation", + "cellDataType": "number" + }, + { + "field": "funding_to_date", + "headerName": "Funding to Date", + "cellDataType": "number" + }, + { + "field": "latest_amount_raised", + "headerName": "Latest Round", + "cellDataType": "number" + }, + { + "field": "latest_funding_date", + "headerName": "Last Funded" + }, + { + "field": "lead_investor", + "headerName": "Lead Investor" + }, + { + "field": "total_funding_rounds", + "headerName": "Rounds", + "cellDataType": "number" + }, + { + "field": "change_52_week", + "headerName": "52W Change %", + "cellDataType": "number" + }, + { + "field": "sector", + "headerName": "Sector" + }, + { + "field": "employees", + "headerName": "Employees", + "cellDataType": "number" + }, + { + "field": "date_founded", + "headerName": "Founded" + }, + { + "field": "website", + "headerName": "Website" + } + ] + } + } + }, + "model": "YfPrivateCompanies" + } + }, + "/yfinance/company_calendar": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the upcoming earnings and dividend dates for a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.company_calendar(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfCompanyCalendar", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol to get data for.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "earnings_date", + "type": "date | None", + "description": "The next earnings report date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ex_dividend_date", + "type": "date | None", + "description": "The next ex-dividend date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_date", + "type": "date | None", + "description": "The next dividend payment date.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_high", + "type": "float | None", + "description": "The high end of the EPS estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_low", + "type": "float | None", + "description": "The low end of the EPS estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "earnings_average", + "type": "float | None", + "description": "The average EPS estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "revenue_high", + "type": "float | None", + "description": "The high end of the revenue estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "revenue_low", + "type": "float | None", + "description": "The low end of the revenue estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "revenue_average", + "type": "float | None", + "description": "The average revenue estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfCompanyCalendar", + "openapi_extra": { + "model": "YfCompanyCalendar" + } + }, + "/yfinance/funds/info": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the profile and operations overview for a mutual fund or ETF.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.funds.info(symbol='VTSAX', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The fund or ETF ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfFundInfo", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The fund or ETF ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "category", + "type": "str | None", + "description": "Morningstar category.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "family", + "type": "str | None", + "description": "The fund family.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "legal_type", + "type": "str | None", + "description": "The legal type of fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "description", + "type": "str | None", + "description": "A description of the fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "annual_report_expense_ratio", + "type": "float | None", + "description": "Annual report expense ratio.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "annual_report_expense_ratio_category", + "type": "float | None", + "description": "Category average annual report expense ratio.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "annual_holdings_turnover", + "type": "float | None", + "description": "Annual holdings turnover.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "annual_holdings_turnover_category", + "type": "float | None", + "description": "Category average annual holdings turnover.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "total_net_assets", + "type": "float | None", + "description": "Total net assets.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_to_earnings", + "type": "float | None", + "description": "Portfolio weighted average price-to-earnings ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_to_book", + "type": "float | None", + "description": "Portfolio weighted average price-to-book ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_to_sales", + "type": "float | None", + "description": "Portfolio weighted average price-to-sales ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "price_to_cashflow", + "type": "float | None", + "description": "Portfolio weighted average price-to-cashflow ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "median_market_cap", + "type": "float | None", + "description": "Median market capitalization of holdings.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "three_year_earnings_growth", + "type": "float | None", + "description": "Three year earnings growth of holdings.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bond_duration", + "type": "float | None", + "description": "Average bond duration, in years.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bond_maturity", + "type": "float | None", + "description": "Average bond maturity, in years.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bond_credit_quality", + "type": "str | None", + "description": "Average bond credit quality.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfFundInfo", + "openapi_extra": { + "model": "YfFundInfo" + } + }, + "/yfinance/funds/holdings": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the top holdings for a mutual fund or ETF.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.funds.holdings(symbol='VTSAX', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The fund or ETF ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfFundHoldings", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The holding's ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the holding.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "weight", + "type": "float | None", + "description": "The weight of the holding in the fund.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "fund", + "type": "str | None", + "description": "The parent fund or ETF ticker symbol.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfFundHoldings", + "openapi_extra": { + "model": "YfFundHoldings" + } + }, + "/yfinance/funds/allocation": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the asset-class, sector and bond-rating allocation for a fund.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.funds.allocation(symbol='VTSAX', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The fund or ETF ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + }, + { + "name": "breakdown", + "type": "Literal['all', 'asset_class', 'sector', 'bond_rating'] | None", + "description": "The allocation breakdown to return.", + "default": "all", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfFundAllocation", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The fund or ETF ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "breakdown", + "type": "str", + "description": "The breakdown type (asset_class, sector, or bond_rating).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "category", + "type": "str", + "description": "The allocation category name.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "weight", + "type": "float | None", + "description": "The weight of the category.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + } + ] + }, + "model": "YfFundAllocation", + "openapi_extra": { + "model": "YfFundAllocation" + } + }, + "/yfinance/funds/historical": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical NAV/price data for a mutual fund or ETF.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.funds.historical(symbol='VTSAX', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The fund or ETF ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "interval", + "type": "Literal['1d', '5d', '1W', '1M', '1Q'] | None", + "description": "Time interval of the data to return.", + "default": "1d", + "optional": true, + "choices": [ + "1d", + "5d", + "1W", + "1M", + "1Q" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfFundHistorical", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "open", + "type": "float | None", + "description": "The open price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float | None", + "description": "The high price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float | None", + "description": "The low price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close", + "type": "float | None", + "description": "The close price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str | None", + "description": "The ticker symbol, when multiple are requested.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfFundHistorical", + "openapi_extra": { + "model": "YfFundHistorical" + } + }, + "/yfinance/funds/performance": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get return performance and category comparisons for a fund.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.funds.performance(symbol='VTSAX', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The fund or ETF ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfFundPerformance", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The fund or ETF ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "measure", + "type": "str", + "description": "The measure: 'trailing_return' or 'annual_return'.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "str", + "description": "A trailing window (1m, 3m, 6m, ytd, 1y, 3y, 5y, 10y) or a calendar year for annual returns.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "cumulative_return", + "type": "float | None", + "description": "Total return over the period.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "annualized_return", + "type": "float | None", + "description": "Annualized return over the period, for trailing windows of one year or more.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + } + ] + }, + "model": "YfFundPerformance", + "openapi_extra": { + "model": "YfFundPerformance" + } + }, + "/yfinance/funds/ratings": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get Morningstar ratings and the style box for a fund.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.funds.ratings(symbol='VTSAX', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The fund or ETF ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfFundRatings", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The fund or ETF ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "morningstar_overall_rating", + "type": "int | None", + "description": "Morningstar overall rating (1-5 stars).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "morningstar_risk_rating", + "type": "int | None", + "description": "Morningstar risk rating (1-5).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "category", + "type": "str | None", + "description": "Morningstar category.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "family", + "type": "str | None", + "description": "The fund family.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "legal_type", + "type": "str | None", + "description": "The legal type of fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "style_box", + "type": "int | None", + "description": "Morningstar style-box cell position (1-9), numbered left-to-right then top-to-bottom.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "style_box_type", + "type": "str | None", + "description": "The style-box type ('equity' or 'fixed_income').", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "style_box_label", + "type": "str | None", + "description": "Human-readable style-box label.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "style_box_size", + "type": "str | None", + "description": "Equity style box market-cap row (Large, Mid, Small).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "style_box_investment_style", + "type": "str | None", + "description": "Equity style box investment-style column (Value, Blend, Growth).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "style_box_credit_quality", + "type": "str | None", + "description": "Fixed-income style box credit-quality row (High, Medium, Low).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "style_box_interest_rate_sensitivity", + "type": "str | None", + "description": "Fixed-income style box interest-rate-sensitivity column (Limited, Moderate, Extensive).", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "style_box_url", + "type": "str | None", + "description": "URL to the Morningstar style-box image.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfFundRatings", + "openapi_extra": { + "model": "YfFundRatings" + } + }, + "/yfinance/funds/risk": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Compute volatility, beta, Sharpe ratio and drawdown for a fund.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.funds.risk(symbol='VTSAX', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The fund or ETF ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "benchmark", + "type": "str | None", + "description": "The benchmark symbol used to compute beta.", + "default": "^GSPC", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "risk_free_rate", + "type": "float | None", + "description": "The annual risk-free rate used for the Sharpe ratio, as a decimal.", + "default": 0.0, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfFundRisk", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The fund or ETF ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the analysis window.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the analysis window.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volatility", + "type": "float | None", + "description": "Annualized volatility of daily returns.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "beta", + "type": "float | None", + "description": "Beta versus the benchmark.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "annualized_return", + "type": "float | None", + "description": "Annualized return over the window.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sharpe_ratio", + "type": "float | None", + "description": "The Sharpe ratio.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "max_drawdown", + "type": "float | None", + "description": "The maximum drawdown over the window.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "benchmark", + "type": "str | None", + "description": "The benchmark used to compute beta.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfFundRisk", + "openapi_extra": { + "model": "YfFundRisk" + } + }, + "/yfinance/estimates/recommendations": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the analyst recommendation trends for a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.estimates.recommendations(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfAnalystRecommendations", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "str", + "description": "The recommendation-trend period (e.g. 0m, -1m, -2m, -3m).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "strong_buy", + "type": "int | None", + "description": "Number of strong buy recommendations.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "buy", + "type": "int | None", + "description": "Number of buy recommendations.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "hold", + "type": "int | None", + "description": "Number of hold recommendations.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sell", + "type": "int | None", + "description": "Number of sell recommendations.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "strong_sell", + "type": "int | None", + "description": "Number of strong sell recommendations.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfAnalystRecommendations", + "openapi_extra": { + "model": "YfAnalystRecommendations" + } + }, + "/yfinance/estimates/earnings": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the forward earnings-per-share estimates for a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.estimates.earnings(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfEarningsEstimates", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "str", + "description": "The estimate period label (e.g. 0q, +1q, 0y, +1y).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "mean", + "type": "float | None", + "description": "Mean estimated EPS.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float | None", + "description": "Low estimated EPS.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float | None", + "description": "High estimated EPS.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "number_of_analysts", + "type": "int | None", + "description": "Number of analysts providing estimates.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "year_ago_eps", + "type": "float | None", + "description": "EPS a year ago.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "growth", + "type": "float | None", + "description": "Estimated EPS growth.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "The currency of the values.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfEarningsEstimates", + "openapi_extra": { + "model": "YfEarningsEstimates" + } + }, + "/yfinance/estimates/revenue": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the forward revenue estimates for a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.estimates.revenue(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfRevenueEstimates", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "str", + "description": "The estimate period (e.g. 0q, +1q, 0y, +1y).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "mean", + "type": "float | None", + "description": "The mean revenue estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float | None", + "description": "The low revenue estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float | None", + "description": "The high revenue estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "number_of_analysts", + "type": "int | None", + "description": "The number of analysts providing estimates.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "year_ago_revenue", + "type": "float | None", + "description": "The revenue from the year-ago period.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "growth", + "type": "float | None", + "description": "The estimated revenue growth.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "The currency of the values.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfRevenueEstimates", + "openapi_extra": { + "model": "YfRevenueEstimates" + } + }, + "/yfinance/estimates/eps_trend": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the trend of EPS estimates over time for a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.estimates.eps_trend(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfEpsTrend", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "str", + "description": "The estimate period (0q, +1q, 0y, +1y).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "current", + "type": "float | None", + "description": "The current EPS estimate.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "days_ago_7", + "type": "float | None", + "description": "The EPS estimate 7 days ago.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "days_ago_30", + "type": "float | None", + "description": "The EPS estimate 30 days ago.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "days_ago_60", + "type": "float | None", + "description": "The EPS estimate 60 days ago.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "days_ago_90", + "type": "float | None", + "description": "The EPS estimate 90 days ago.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "The currency of the estimates.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfEpsTrend", + "openapi_extra": { + "model": "YfEpsTrend" + } + }, + "/yfinance/estimates/eps_revisions": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the number of upward and downward EPS estimate revisions.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.estimates.eps_revisions(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfEpsRevisions", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "str", + "description": "The estimate period (e.g. 0q, +1q, 0y, +1y).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "up_last_7_days", + "type": "int | None", + "description": "Number of upward revisions over the last 7 days.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "up_last_30_days", + "type": "int | None", + "description": "Number of upward revisions over the last 30 days.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "down_last_7_days", + "type": "int | None", + "description": "Number of downward revisions over the last 7 days.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "down_last_30_days", + "type": "int | None", + "description": "Number of downward revisions over the last 30 days.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "The currency of the estimates.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfEpsRevisions", + "openapi_extra": { + "model": "YfEpsRevisions" + } + }, + "/yfinance/estimates/growth": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the growth estimates for a company versus its index.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.estimates.growth(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfGrowthEstimates", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "period", + "type": "str", + "description": "The estimate period (e.g. 0q, +1q, 0y, +1y, LTG).", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "stock_trend", + "type": "float | None", + "description": "The growth estimate trend for the stock.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "index_trend", + "type": "float | None", + "description": "The growth estimate trend for the index.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfGrowthEstimates", + "openapi_extra": { + "model": "YfGrowthEstimates" + } + }, + "/yfinance/ownership/institutional": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the top institutional holders of a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.ownership.institutional(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfInstitutionalHolders", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "date_reported", + "type": "date | None", + "description": "The date the position was reported.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "holder", + "type": "str | None", + "description": "The name of the holder.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "percent_held", + "type": "float | None", + "description": "The percent of shares outstanding held by the holder.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "shares", + "type": "int | None", + "description": "The number of shares held by the holder.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "float | None", + "description": "The value of the shares held by the holder.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "percent_change", + "type": "float | None", + "description": "The change in the percent of shares held since the prior period.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + } + ] + }, + "model": "YfInstitutionalHolders", + "openapi_extra": { + "model": "YfInstitutionalHolders" + } + }, + "/yfinance/ownership/mutual_fund": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the top mutual-fund holders of a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.ownership.mutual_fund(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfMutualFundHolders", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "date_reported", + "type": "date | None", + "description": "The date of the holdings report.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "holder", + "type": "str | None", + "description": "The name of the holder.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "percent_held", + "type": "float | None", + "description": "The percent of shares outstanding held by the holder.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "shares", + "type": "int | None", + "description": "The number of shares held by the holder.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "value", + "type": "float | None", + "description": "The value of the shares held by the holder.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "percent_change", + "type": "float | None", + "description": "The change in percent of shares held by the holder.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + } + ] + }, + "model": "YfMutualFundHolders", + "openapi_extra": { + "model": "YfMutualFundHolders" + } + }, + "/yfinance/ownership/major": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the major-holders breakdown for a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.ownership.major(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfMajorHolders", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "insiders_percent_held", + "type": "float | None", + "description": "The percent of shares held by insiders.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "institutions_percent_held", + "type": "float | None", + "description": "The percent of shares held by institutions.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "institutions_float_percent_held", + "type": "float | None", + "description": "The percent of float held by institutions.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "institutions_count", + "type": "int | None", + "description": "The number of institutions holding shares.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfMajorHolders", + "openapi_extra": { + "model": "YfMajorHolders" + } + }, + "/yfinance/ownership/insider_purchases": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the six-month insider-purchase summary for a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.ownership.insider_purchases(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfInsiderPurchases", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "category", + "type": "str", + "description": "The insider purchases category.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "shares", + "type": "float | None", + "description": "The number of shares.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "transactions", + "type": "float | None", + "description": "The number of transactions.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfInsiderPurchases", + "openapi_extra": { + "model": "YfInsiderPurchases" + } + }, + "/yfinance/ownership/insider_roster": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the insider roster (current insider holders) for a company.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.ownership.insider_roster(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "The ticker symbol. Multiple items allowed.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": true, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfInsiderRoster", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the insider.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "position", + "type": "str | None", + "description": "The position of the insider.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "url", + "type": "str | None", + "description": "The URL of the insider.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "most_recent_transaction", + "type": "str | None", + "description": "The most recent transaction of the insider.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "latest_transaction_date", + "type": "date | None", + "description": "The latest transaction date of the insider.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "shares_owned_directly", + "type": "float | None", + "description": "The number of shares owned directly by the insider.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "position_direct_date", + "type": "date | None", + "description": "The date of the direct position.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfInsiderRoster", + "openapi_extra": { + "model": "YfInsiderRoster" + } + }, + "/yfinance/sectors/overview": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get an overview of a market sector.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.sectors.overview(sector='technology', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str | None", + "description": "The Yahoo sector key. Leave as 'Market (All Sectors)' for the market-level breakdown across every sector.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "options": [ + { + "label": "Market (All Sectors)", + "value": "" + }, + { + "label": "Basic Materials", + "value": "basic-materials" + }, + { + "label": "Communication Services", + "value": "communication-services" + }, + { + "label": "Consumer Cyclical", + "value": "consumer-cyclical" + }, + { + "label": "Consumer Defensive", + "value": "consumer-defensive" + }, + { + "label": "Energy", + "value": "energy" + }, + { + "label": "Financial Services", + "value": "financial-services" + }, + { + "label": "Healthcare", + "value": "healthcare" + }, + { + "label": "Industrials", + "value": "industrials" + }, + { + "label": "Real Estate", + "value": "real-estate" + }, + { + "label": "Technology", + "value": "technology" + }, + { + "label": "Utilities", + "value": "utilities" + } + ], + "value": "" + } + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfSectorOverview", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str", + "description": "The Yahoo sector key/slug.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str | None", + "description": "The symbol of the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "market_cap", + "type": "float | None", + "description": "The market cap of the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "market_weight", + "type": "float | None", + "description": "The market weight of the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "companies_count", + "type": "int | None", + "description": "The number of companies in the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "industries_count", + "type": "int | None", + "description": "The number of industries in the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "employee_count", + "type": "int | None", + "description": "The number of employees in the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "description", + "type": "str | None", + "description": "The description of the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfSectorOverview", + "openapi_extra": { + "model": "YfSectorOverview" + } + }, + "/yfinance/sectors/top_companies": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the top companies in a market sector.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.sectors.top_companies(sector='technology', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str | None", + "description": "The Yahoo sector key. Leave as 'Market (All Sectors)' for the top companies across every sector.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "options": [ + { + "label": "Market (All Sectors)", + "value": "" + }, + { + "label": "Basic Materials", + "value": "basic-materials" + }, + { + "label": "Communication Services", + "value": "communication-services" + }, + { + "label": "Consumer Cyclical", + "value": "consumer-cyclical" + }, + { + "label": "Consumer Defensive", + "value": "consumer-defensive" + }, + { + "label": "Energy", + "value": "energy" + }, + { + "label": "Financial Services", + "value": "financial-services" + }, + { + "label": "Healthcare", + "value": "healthcare" + }, + { + "label": "Industrials", + "value": "industrials" + }, + { + "label": "Real Estate", + "value": "real-estate" + }, + { + "label": "Technology", + "value": "technology" + }, + { + "label": "Utilities", + "value": "utilities" + } + ], + "value": "" + } + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfSectorTopCompanies", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str", + "description": "The Yahoo sector key/slug.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol of the company.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "rating", + "type": "str | None", + "description": "The analyst rating for the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "market_weight", + "type": "float | None", + "description": "The market weight of the company within the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfSectorTopCompanies", + "openapi_extra": { + "model": "YfSectorTopCompanies" + } + }, + "/yfinance/sectors/top_funds": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the top ETFs or mutual funds in a market sector.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.sectors.top_funds(sector='technology', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str | None", + "description": "The Yahoo sector key. Leave as 'Market (All Sectors)' for the top funds across every sector.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "options": [ + { + "label": "Market (All Sectors)", + "value": "" + }, + { + "label": "Basic Materials", + "value": "basic-materials" + }, + { + "label": "Communication Services", + "value": "communication-services" + }, + { + "label": "Consumer Cyclical", + "value": "consumer-cyclical" + }, + { + "label": "Consumer Defensive", + "value": "consumer-defensive" + }, + { + "label": "Energy", + "value": "energy" + }, + { + "label": "Financial Services", + "value": "financial-services" + }, + { + "label": "Healthcare", + "value": "healthcare" + }, + { + "label": "Industrials", + "value": "industrials" + }, + { + "label": "Real Estate", + "value": "real-estate" + }, + { + "label": "Technology", + "value": "technology" + }, + { + "label": "Utilities", + "value": "utilities" + } + ], + "value": "" + } + } + }, + { + "name": "fund_type", + "type": "Literal['etf', 'mutualfund'] | None", + "description": "Whether to return top ETFs or top mutual funds.", + "default": "etf", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "options": [ + { + "label": "ETF", + "value": "etf" + }, + { + "label": "Mutual Fund", + "value": "mutualfund" + } + ], + "value": "etf" + } + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfSectorTopFunds", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str", + "description": "The Yahoo sector key/slug.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "fund_type", + "type": "str", + "description": "The type of fund, ETF or mutual fund.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol of the fund.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfSectorTopFunds", + "openapi_extra": { + "model": "YfSectorTopFunds" + } + }, + "/yfinance/sectors/industries": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the industries within a market sector.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.sectors.industries(sector='technology', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str | None", + "description": "The Yahoo sector key. Leave as 'Market (All Sectors)' to list the industries across every sector.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "options": [ + { + "label": "Market (All Sectors)", + "value": "" + }, + { + "label": "Basic Materials", + "value": "basic-materials" + }, + { + "label": "Communication Services", + "value": "communication-services" + }, + { + "label": "Consumer Cyclical", + "value": "consumer-cyclical" + }, + { + "label": "Consumer Defensive", + "value": "consumer-defensive" + }, + { + "label": "Energy", + "value": "energy" + }, + { + "label": "Financial Services", + "value": "financial-services" + }, + { + "label": "Healthcare", + "value": "healthcare" + }, + { + "label": "Industrials", + "value": "industrials" + }, + { + "label": "Real Estate", + "value": "real-estate" + }, + { + "label": "Technology", + "value": "technology" + }, + { + "label": "Utilities", + "value": "utilities" + } + ], + "value": "" + } + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfSectorIndustries", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str", + "description": "The Yahoo sector key/slug.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "key", + "type": "str", + "description": "The Yahoo industry key.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the industry.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str | None", + "description": "The symbol representing the industry.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "market_weight", + "type": "float | None", + "description": "The market weight of the industry within the sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfSectorIndustries", + "openapi_extra": { + "model": "YfSectorIndustries" + } + }, + "/yfinance/industry/overview": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get an overview of an industry.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.industry.overview(industry='software-infrastructure', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str | None", + "description": "The Yahoo sector key that populates the Industry choices.", + "default": "technology", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "options": [ + { + "label": "Market (All Sectors)", + "value": "" + }, + { + "label": "Basic Materials", + "value": "basic-materials" + }, + { + "label": "Communication Services", + "value": "communication-services" + }, + { + "label": "Consumer Cyclical", + "value": "consumer-cyclical" + }, + { + "label": "Consumer Defensive", + "value": "consumer-defensive" + }, + { + "label": "Energy", + "value": "energy" + }, + { + "label": "Financial Services", + "value": "financial-services" + }, + { + "label": "Healthcare", + "value": "healthcare" + }, + { + "label": "Industrials", + "value": "industrials" + }, + { + "label": "Real Estate", + "value": "real-estate" + }, + { + "label": "Technology", + "value": "technology" + }, + { + "label": "Utilities", + "value": "utilities" + } + ], + "value": "technology" + } + } + }, + { + "name": "industry", + "type": "str | None", + "description": "The Yahoo industry key. Choices cascade from the selected sector; leave empty to load every industry in the sector.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "type": "endpoint", + "optionsEndpoint": "/api/v1/yfinance/industry_choices", + "optionsParams": { + "sector": "$sector" + } + } + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfIndustryOverview", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "industry", + "type": "str", + "description": "The Yahoo industry key/slug.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the industry.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str | None", + "description": "The symbol representing the industry.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sector", + "type": "str | None", + "description": "The sector key the industry belongs to.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "sector_name", + "type": "str | None", + "description": "The name of the sector the industry belongs to.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "market_cap", + "type": "float | None", + "description": "The total market capitalization of the industry.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "market_weight", + "type": "float | None", + "description": "The market weight of the industry within its sector.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "companies_count", + "type": "int | None", + "description": "The number of companies in the industry.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "employee_count", + "type": "int | None", + "description": "The total number of employees in the industry.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "description", + "type": "str | None", + "description": "A description of the industry.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfIndustryOverview", + "openapi_extra": { + "model": "YfIndustryOverview" + } + }, + "/yfinance/industry/top_performing": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the top-performing companies in an industry.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.industry.top_performing(industry='software-infrastructure', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str | None", + "description": "The Yahoo sector key that populates the Industry choices.", + "default": "technology", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "options": [ + { + "label": "Market (All Sectors)", + "value": "" + }, + { + "label": "Basic Materials", + "value": "basic-materials" + }, + { + "label": "Communication Services", + "value": "communication-services" + }, + { + "label": "Consumer Cyclical", + "value": "consumer-cyclical" + }, + { + "label": "Consumer Defensive", + "value": "consumer-defensive" + }, + { + "label": "Energy", + "value": "energy" + }, + { + "label": "Financial Services", + "value": "financial-services" + }, + { + "label": "Healthcare", + "value": "healthcare" + }, + { + "label": "Industrials", + "value": "industrials" + }, + { + "label": "Real Estate", + "value": "real-estate" + }, + { + "label": "Technology", + "value": "technology" + }, + { + "label": "Utilities", + "value": "utilities" + } + ], + "value": "technology" + } + } + }, + { + "name": "industry", + "type": "str | None", + "description": "The Yahoo industry key. Choices cascade from the selected sector; leave empty to load every industry in the sector.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "type": "endpoint", + "optionsEndpoint": "/api/v1/yfinance/industry_choices", + "optionsParams": { + "sector": "$sector" + } + } + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfIndustryTopPerforming", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "industry", + "type": "str", + "description": "The Yahoo industry key/slug.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol of the company.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ytd_return", + "type": "float | None", + "description": "The year-to-date return of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "last_price", + "type": "float | None", + "description": "The last price of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "target_price", + "type": "float | None", + "description": "The target price of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfIndustryTopPerforming", + "openapi_extra": { + "model": "YfIndustryTopPerforming" + } + }, + "/yfinance/industry/top_growth": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the top-growth companies in an industry.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.industry.top_growth(industry='software-infrastructure', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [ + { + "name": "sector", + "type": "str | None", + "description": "The Yahoo sector key that populates the Industry choices.", + "default": "technology", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "options": [ + { + "label": "Market (All Sectors)", + "value": "" + }, + { + "label": "Basic Materials", + "value": "basic-materials" + }, + { + "label": "Communication Services", + "value": "communication-services" + }, + { + "label": "Consumer Cyclical", + "value": "consumer-cyclical" + }, + { + "label": "Consumer Defensive", + "value": "consumer-defensive" + }, + { + "label": "Energy", + "value": "energy" + }, + { + "label": "Financial Services", + "value": "financial-services" + }, + { + "label": "Healthcare", + "value": "healthcare" + }, + { + "label": "Industrials", + "value": "industrials" + }, + { + "label": "Real Estate", + "value": "real-estate" + }, + { + "label": "Technology", + "value": "technology" + }, + { + "label": "Utilities", + "value": "utilities" + } + ], + "value": "technology" + } + } + }, + { + "name": "industry", + "type": "str | None", + "description": "The Yahoo industry key. Choices cascade from the selected sector; leave empty to load every industry in the sector.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "x-widget_config": { + "type": "endpoint", + "optionsEndpoint": "/api/v1/yfinance/industry_choices", + "optionsParams": { + "sector": "$sector" + } + } + } + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfIndustryTopGrowth", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [], + "yfinance": [ + { + "name": "industry", + "type": "str", + "description": "The Yahoo industry key/slug.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbol", + "type": "str", + "description": "The ticker symbol of the company.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "The name of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ytd_return", + "type": "float | None", + "description": "The year-to-date return of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "growth_estimate", + "type": "float | None", + "description": "The growth estimate of the company.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfIndustryTopGrowth", + "openapi_extra": { + "model": "YfIndustryTopGrowth" + } + }, + "/yfinance/etf/info": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get ETF information overview.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.etf.info(symbol='SPY', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. (ETF) Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfEtfInfo", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data. (ETF)", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "Name of the ETF.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "issuer", + "type": "str | None", + "description": "Issuer of the ETF.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "domicile", + "type": "str | None", + "description": "Domicile of the ETF.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "website", + "type": "str | None", + "description": "Website of the ETF.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "description", + "type": "str | None", + "description": "Description of the fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "inception_date", + "type": "date | None", + "description": "Inception date of the ETF.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "fund_type", + "type": "str | None", + "description": "The legal type of fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "fund_family", + "type": "str | None", + "description": "The fund family.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "category", + "type": "str | None", + "description": "The fund category.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange", + "type": "str | None", + "description": "The exchange the fund is listed on.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange_timezone", + "type": "str | None", + "description": "The timezone of the exchange.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "The currency in which the fund is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "nav_price", + "type": "float | None", + "description": "The net asset value per unit of the fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "total_assets", + "type": "int | None", + "description": "The total value of assets held by the fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "trailing_pe", + "type": "float | None", + "description": "The trailing twelve month P/E ratio of the fund's assets.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield", + "type": "float | None", + "description": "The dividend yield of the fund, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "dividend_rate_ttm", + "type": "float | None", + "description": "The trailing twelve month annual dividend rate of the fund, in currency units.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend_yield_ttm", + "type": "float | None", + "description": "The trailing twelve month annual dividend yield of the fund, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "year_high", + "type": "float | None", + "description": "The fifty-two week high price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "year_low", + "type": "float | None", + "description": "The fifty-two week low price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ma_50d", + "type": "float | None", + "description": "50-day moving average price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ma_200d", + "type": "float | None", + "description": "200-day moving average price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "return_ytd", + "type": "float | None", + "description": "The year-to-date return of the fund, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "return_3y_avg", + "type": "float | None", + "description": "The three year average return of the fund, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "return_5y_avg", + "type": "float | None", + "description": "The five year average return of the fund, as a normalized percent.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "beta_3y_avg", + "type": "float | None", + "description": "The three year average beta of the fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volume_avg", + "type": "float | None", + "description": "The average daily trading volume of the fund.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volume_avg_10d", + "type": "float | None", + "description": "The average daily trading volume of the fund over the past ten days.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bid", + "type": "float | None", + "description": "The current bid price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bid_size", + "type": "float | None", + "description": "The current bid size.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ask", + "type": "float | None", + "description": "The current ask price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ask_size", + "type": "float | None", + "description": "The current ask size.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "open", + "type": "float | None", + "description": "The open price of the most recent trading session.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float | None", + "description": "The highest price of the most recent trading session.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float | None", + "description": "The lowest price of the most recent trading session.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume of the most recent trading session.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "prev_close", + "type": "float | None", + "description": "The previous closing price.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfEtfInfo", + "openapi_extra": { + "model": "YfEtfInfo" + } + }, + "/yfinance/etf/historical": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical price data for an ETF.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.etf.historical(symbol='SPY', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "interval", + "type": "Literal['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1W', '1M', '1Q'] | None", + "description": "Time interval of the data to return.", + "default": "1d", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "extended_hours", + "type": "bool | None", + "description": "Include Pre and Post market data.", + "default": false, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "include_actions", + "type": "bool | None", + "description": "Include dividends and stock splits in results.", + "default": true, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "adjustment", + "type": "Literal['splits_only', 'splits_and_dividends'] | None", + "description": "The adjustment factor to apply. Default is splits only.", + "default": "splits_only", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfEtfHistorical", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "open", + "type": "float", + "description": "The open price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float", + "description": "The high price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float", + "description": "The low price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "close", + "type": "float", + "description": "The close price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "float | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "vwap", + "type": "float | None", + "description": "Volume Weighted Average Price over the period.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "split_ratio", + "type": "float | None", + "description": "Ratio of the equity split, if a split occurred.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "dividend", + "type": "float | None", + "description": "Dividend amount (split-adjusted), if a dividend was paid.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfEtfHistorical", + "openapi_extra": { + "model": "YfEtfHistorical" + } + }, + "/yfinance/derivatives/options/chains": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the complete options chain for a symbol.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.derivatives.options.chains(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol to get data for.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfOptionsChains", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "underlying_symbol", + "type": "list[str | None]", + "description": "Underlying symbol for the option.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "underlying_price", + "type": "list[float | None]", + "description": "Price of the underlying stock.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "contract_symbol", + "type": "list[str]", + "description": "Contract symbol for the option.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "eod_date", + "type": "list[date | None]", + "description": "Date for which the options chains are returned.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "expiration", + "type": "list[date]", + "description": "Expiration date of the contract.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "dte", + "type": "list[int | None]", + "description": "Days to expiration of the contract.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "strike", + "type": "list[float]", + "description": "Strike price of the contract.", + "default": null, + "optional": false, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "option_type", + "type": "list[str]", + "description": "Call or Put.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "contract_size", + "type": "list[int | float | None]", + "description": "Number of underlying units per contract.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "open_interest", + "type": "list[int | float | None]", + "description": "Open interest on the contract.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "list[int | float | None]", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "theoretical_price", + "type": "list[float | None]", + "description": "Theoretical value of the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "last_trade_price", + "type": "list[float | None]", + "description": "Last trade price of the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "last_trade_size", + "type": "list[int | float | None]", + "description": "Last trade size of the option.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "last_trade_time", + "type": "list[datetime | None]", + "description": "The timestamp of the last trade.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "tick", + "type": "list[str | None]", + "description": "Whether the last tick was up or down in price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bid", + "type": "list[float | None]", + "description": "Current bid price for the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "bid_size", + "type": "list[int | float | None]", + "description": "Bid size for the option.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bid_time", + "type": "list[datetime | None]", + "description": "The timestamp of the bid price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "bid_exchange", + "type": "list[str | None]", + "description": "The exchange of the bid price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ask", + "type": "list[float | None]", + "description": "Current ask price for the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ask_size", + "type": "list[int | float | None]", + "description": "Ask size for the option.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ask_time", + "type": "list[datetime | None]", + "description": "The timestamp of the ask price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "ask_exchange", + "type": "list[str | None]", + "description": "The exchange of the ask price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "mark", + "type": "list[float | None]", + "description": "The mid-price between the latest bid and ask.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "open", + "type": "list[float | None]", + "description": "The open price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "open_bid", + "type": "list[float | None]", + "description": "The opening bid price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "open_ask", + "type": "list[float | None]", + "description": "The opening ask price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "high", + "type": "list[float | None]", + "description": "The high price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "bid_high", + "type": "list[float | None]", + "description": "The highest bid price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ask_high", + "type": "list[float | None]", + "description": "The highest ask price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "low", + "type": "list[float | None]", + "description": "The low price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "bid_low", + "type": "list[float | None]", + "description": "The lowest bid price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "ask_low", + "type": "list[float | None]", + "description": "The lowest ask price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "close", + "type": "list[float | None]", + "description": "The close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "close_size", + "type": "list[int | float | None]", + "description": "The closing trade size for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close_time", + "type": "list[datetime | None]", + "description": "The time of the closing price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close_bid", + "type": "list[float | None]", + "description": "The closing bid price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "close_bid_size", + "type": "list[int | float | None]", + "description": "The closing bid size for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close_bid_time", + "type": "list[datetime | None]", + "description": "The time of the bid closing price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close_ask", + "type": "list[float | None]", + "description": "The closing ask price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close_ask_size", + "type": "list[int | float | None]", + "description": "The closing ask size for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close_ask_time", + "type": "list[datetime | None]", + "description": "The time of the ask closing price for the option that day.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "prev_close", + "type": "list[float | None]", + "description": "The previous close price.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + }, + { + "name": "change", + "type": "list[float | None]", + "description": "The change in the price of the option.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "change_percent", + "type": "list[float | None]", + "description": "Change, in normalized percentage points, of the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "percent", + "x-frontend_multiply": 100 + } + }, + { + "name": "implied_volatility", + "type": "list[float | None]", + "description": "Implied volatility of the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "decimal" + } + }, + { + "name": "delta", + "type": "list[float | None]", + "description": "Delta of the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "decimal" + } + }, + { + "name": "gamma", + "type": "list[float | None]", + "description": "Gamma of the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "decimal" + } + }, + { + "name": "theta", + "type": "list[float | None]", + "description": "Theta of the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "decimal" + } + }, + { + "name": "vega", + "type": "list[float | None]", + "description": "Vega of the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "decimal" + } + }, + { + "name": "rho", + "type": "list[float | None]", + "description": "Rho of the option.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "decimal" + } + } + ], + "yfinance": [ + { + "name": "in_the_money", + "type": "list[bool | None]", + "description": "Whether the option is in the money.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "list[str | None]", + "description": "Currency of the option.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfOptionsChains", + "openapi_extra": { + "model": "YfOptionsChains" + } + }, + "/yfinance/derivatives/futures/historical": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical price data for a futures contract.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.derivatives.futures.historical(symbol='ES', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "expiration", + "type": "str | None", + "description": "Future expiry date with format YYYY-MM", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "interval", + "type": "Literal['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1W', '1M', '1Q'] | None", + "description": "Time interval of the data to return.", + "default": "1d", + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfFuturesHistorical", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "datetime | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "open", + "type": "float | None", + "description": "The open price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float | None", + "description": "The high price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float | None", + "description": "The low price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close", + "type": "float", + "description": "The close price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "float | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "model": "YfFuturesHistorical", + "openapi_extra": { + "model": "YfFuturesHistorical" + } + }, + "/yfinance/derivatives/futures/curve": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the futures curve for a symbol.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.derivatives.futures.curve(symbol='ES', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol to get data for.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "date", + "type": "date | None | list[date | None]", + "description": "A specific date to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfFuturesCurve", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | None | str", + "description": "The date of the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "expiration", + "type": "str", + "description": "Futures expiration month.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "price", + "type": "float | None", + "description": "The price of the futures contract.", + "default": null, + "optional": true, + "json_schema_extra": { + "x-unit_measurement": "currency" + } + } + ], + "yfinance": [] + }, + "model": "YfFuturesCurve", + "openapi_extra": { + "model": "YfFuturesCurve" + } + }, + "/yfinance/index/available": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the list of available indices.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.index.available(provider='yfinance')\n```\n\n", + "parameters": { + "standard": [], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfAvailableIndices", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "name", + "type": "str | None", + "description": "", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "exchange", + "type": "str | None", + "description": "Stock exchange where the index is listed.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "currency", + "type": "str | None", + "description": "Currency the index is traded in.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "symbol", + "type": "str", + "description": "Symbol for the index.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "code", + "type": "str", + "description": "ID code for keying the index in the OpenBB Terminal.", + "default": null, + "optional": false, + "json_schema_extra": {} + } + ] + }, + "model": "YfAvailableIndices", + "openapi_extra": { + "model": "YfAvailableIndices" + } + }, + "/yfinance/index/price/historical": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical price data for an index.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.index.price.historical(symbol='^GSPC', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "interval", + "type": "Literal['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1W', '1M', '1Q'] | None", + "description": "Time interval of the data to return.", + "default": "1d", + "optional": true, + "choices": [ + "1m", + "2m", + "5m", + "15m", + "30m", + "60m", + "90m", + "1h", + "1d", + "5d", + "1W", + "1M", + "1Q" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfIndexHistorical", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "symbol", + "type": "str | None", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "open", + "type": "float | None", + "description": "The open price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float | None", + "description": "The high price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float | None", + "description": "The low price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close", + "type": "float | None", + "description": "The close price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "int | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "model": "YfIndexHistorical", + "openapi_extra": { + "model": "YfIndexHistorical" + } + }, + "/yfinance/crypto/price/historical": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical price data for a cryptocurrency pair.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.crypto.price.historical(symbol='BTC-USD', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "interval", + "type": "Literal['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1W', '1M', '1Q'] | None", + "description": "Time interval of the data to return.", + "default": "1d", + "optional": true, + "choices": [ + "1m", + "2m", + "5m", + "15m", + "30m", + "60m", + "90m", + "1h", + "1d", + "5d", + "1W", + "1M", + "1Q" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfCryptoHistorical", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "open", + "type": "float | None", + "description": "The open price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float | None", + "description": "The high price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float | None", + "description": "The low price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close", + "type": "float", + "description": "The close price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "float | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "vwap", + "type": "float | None", + "description": "Volume Weighted Average Price over the period.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "model": "YfCryptoHistorical", + "openapi_extra": { + "model": "YfCryptoHistorical" + } + }, + "/yfinance/currency/price/historical": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get historical price data for a currency pair.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.currency.price.historical(symbol='EURUSD=X', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | list[str]", + "description": "Symbol to get data for. Can use CURR1-CURR2 or CURR1CURR2 format. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": false, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "interval", + "type": "Literal['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1W', '1M', '1Q'] | None", + "description": "Time interval of the data to return.", + "default": "1d", + "optional": true, + "choices": [ + "1m", + "2m", + "5m", + "15m", + "30m", + "60m", + "90m", + "1h", + "1d", + "5d", + "1W", + "1M", + "1Q" + ], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfCurrencyHistorical", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "date | str", + "description": "The date of the data.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "open", + "type": "float | None", + "description": "The open price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "high", + "type": "float | None", + "description": "The high price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "low", + "type": "float | None", + "description": "The low price.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "close", + "type": "float", + "description": "The close price.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "volume", + "type": "float | None", + "description": "The trading volume.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "vwap", + "type": "float | None", + "description": "Volume Weighted Average Price over the period.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "model": "YfCurrencyHistorical", + "openapi_extra": { + "model": "YfCurrencyHistorical" + } + }, + "/yfinance/news/company": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get company news for one or more symbols.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\nobb.yfinance.news.company(symbol='AAPL', provider='yfinance')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str | None | list[str | None]", + "description": "Symbol to get data for. Multiple items allowed for provider(s): yfinance.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": { + "yfinance": { + "multiple_items_allowed": true + } + } + }, + { + "name": "start_date", + "type": "date | None | str", + "description": "Start date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "end_date", + "type": "date | None | str", + "description": "End date of the data, in YYYY-MM-DD format.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + }, + { + "name": "limit", + "type": "int | None", + "description": "The number of data entries to return.", + "default": null, + "optional": true, + "choices": [], + "multiple_items_allowed": false, + "json_schema_extra": {} + } + ], + "yfinance": [] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "YfCompanyNews", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Literal['yfinance'] | None", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": { + "standard": [ + { + "name": "date", + "type": "datetime | str", + "description": "The date of the data. The date of publication.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "title", + "type": "str", + "description": "Title of the article.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "author", + "type": "str | None", + "description": "Author of the article.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "excerpt", + "type": "str | None", + "description": "Excerpt of the article text.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "body", + "type": "str | None", + "description": "Body of the article text.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "images", + "type": "Any | None", + "description": "Images associated with the article.", + "default": null, + "optional": true, + "json_schema_extra": {} + }, + { + "name": "url", + "type": "str", + "description": "URL to the article.", + "default": null, + "optional": false, + "json_schema_extra": {} + }, + { + "name": "symbols", + "type": "str | None", + "description": "Symbols associated with the article.", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ], + "yfinance": [ + { + "name": "source", + "type": "str | None", + "description": "Source of the news article", + "default": null, + "optional": true, + "json_schema_extra": {} + } + ] + }, + "model": "YfCompanyNews", + "openapi_extra": { + "model": "YfCompanyNews" + } + }, + "/yfinance/tv_widget": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Open an interactive TradingView chart powered by the Yahoo Finance datafeed.\n\nOpens a native desktop window, or renders inline in a Jupyter notebook, and\nreturns immediately without blocking. On a headless host, returns the iframe\nendpoint to embed instead.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Open an interactive TradingView chart in a native window.\nobb.yfinance.tv_widget(symbol='AAPL')\n```\n\n", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "", + "default": "AAPL", + "optional": true + }, + { + "name": "interval", + "type": "str", + "description": "", + "default": "1d", + "optional": true + }, + { + "name": "theme", + "type": "str", + "description": "", + "default": "dark", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "Any", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "exclude": true + }, + "model": "" + } + }, + "/yfinance/screener_builder": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Open the interactive Yahoo Finance screener builder.\n\nOpens a native desktop window, or renders inline in a Jupyter notebook, and\nreturns immediately without blocking. On a headless host (e.g. the REST API),\nreturns the iframe endpoint to embed instead. To run a screen\nprogrammatically, use ``obb.yfinance.equity.screener``.", + "examples": "Examples\n--------\n\n```python\nfrom openbb import obb\n# Open the interactive screener builder in a native window.\nobb.yfinance.screener_builder()\n```\n\n", + "parameters": { + "standard": [ + { + "name": "theme", + "type": "str", + "description": "", + "default": "dark", + "optional": true + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "Any", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "str", + "description": "Provider name." + }, + { + "name": "warnings", + "type": "Optional[list[Warning_]]", + "description": "list of warnings." + }, + { + "name": "chart", + "type": "Optional[Chart]", + "description": "Chart object." + }, + { + "name": "extra", + "type": "dict[str, Any]", + "description": "Extra info." + } + ] + }, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "exclude": true + }, + "model": "" + } + }, + "/yfinance/tv_widget/view": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Serve PyWry's TradingView widget HTML for the Workspace iframe.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "", + "default": "AAPL", + "optional": true + }, + { + "name": "interval", + "type": "str", + "description": "", + "default": "1d", + "optional": true + }, + { + "name": "theme", + "type": "str", + "description": "", + "default": "dark", + "optional": true + } + ] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "name": "TradingView Chart (Yahoo Finance)", + "description": "Interactive TradingView chart powered by Yahoo Finance.", + "type": "iframe", + "category": "Equity", + "subCategory": "Charts", + "widgetId": "yfinance_tv_widget_obb", + "params": [ + { + "paramName": "symbol", + "label": "Symbol", + "value": "AAPL", + "description": "The ticker symbol to chart." + }, + { + "paramName": "interval", + "label": "Interval", + "value": "1d", + "description": "The chart interval.", + "options": [ + { + "label": "1m", + "value": "1m" + }, + { + "label": "5m", + "value": "5m" + }, + { + "label": "15m", + "value": "15m" + }, + { + "label": "30m", + "value": "30m" + }, + { + "label": "1h", + "value": "1h" + }, + { + "label": "1d", + "value": "1d" + }, + { + "label": "1w", + "value": "1w" + }, + { + "label": "1M", + "value": "1M" + } + ] + } + ], + "gridData": { + "w": 40, + "h": 20 + }, + "refetchInterval": false, + "storage": { + "mcpUrl": "/api/v1/yfinance/mcp" + } + } + } + }, + "/yfinance/asset_info/view": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Serve the styled, live Asset Info overview widget HTML for the Workspace.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "symbol", + "type": "str", + "description": "", + "default": "AAPL", + "optional": true + }, + { + "name": "theme", + "type": "str", + "description": "", + "default": "dark", + "optional": true + } + ] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "name": "Asset Info (Yahoo Finance)", + "description": "Styled overview with a live header price \u2014 summary, key statistics, valuation, analyst coverage, ownership, calendar, holdings and sector weightings \u2014 adapting to the asset type.", + "type": "iframe", + "category": "Equity", + "subCategory": "Profile", + "widgetId": "yfinance_asset_info_obb", + "params": [ + { + "paramName": "symbol", + "label": "Symbol", + "value": "AAPL", + "description": "The ticker symbol." + } + ], + "gridData": { + "w": 20, + "h": 20 + }, + "refetchInterval": false + } + } + }, + "/yfinance/screener_builder/view": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Serve the interactive screener-builder iframe page.", + "examples": "", + "parameters": { + "standard": [ + { + "name": "theme", + "type": "str", + "description": "", + "default": "dark", + "optional": true + } + ] + }, + "returns": {}, + "data": {}, + "model": "", + "openapi_extra": { + "widget_config": { + "name": "Screener Builder (Yahoo Finance)", + "description": "Interactively build a screener configuration across equities, ETFs and funds; click 'Apply' to sync the results table.", + "type": "iframe", + "category": "Equity", + "subCategory": "Screener", + "widgetId": "yfinance_screener_builder_obb", + "params": [ + { + "paramName": "config", + "label": "Screener Config", + "value": "", + "description": "The screener configuration emitted by the builder.", + "show": false + } + ], + "gridData": { + "w": 16, + "h": 18 + }, + "refetchInterval": false + } + } + } + }, + "routers": { + "/econometrics": { + "description": "Econometrics panel-data regression commands." + }, + "/economy": { + "description": "Economic data." + }, + "/equity": { + "description": "Equity market data." + }, + "/oecd": { + "description": "OECD provider router." + }, + "/quantitative": { + "description": "Return-attribution commands." + }, + "/quantitative/rolling": { + "description": "Quantitative rolling-statistics commands." + }, + "/quantitative/stats": { + "description": "Quantitative statistics commands." + }, + "/quantitative/performance": { + "description": "Quantitative performance-metric commands." + }, + "/technical": { + "description": "Indicator catalogue." + }, + "/technical/signals": { + "description": "Market-regime classification." + }, + "/yfinance": { + "description": "Yahoo Finance data and analytics." + }, + "/yfinance/funds": { + "description": "Yahoo Finance mutual fund & ETF analytics." + }, + "/yfinance/estimates": { + "description": "Yahoo Finance analyst estimates." + }, + "/yfinance/ownership": { + "description": "Yahoo Finance ownership data." + }, + "/yfinance/sectors": { + "description": "Yahoo Finance sector comparisons." + }, + "/yfinance/industry": { + "description": "Yahoo Finance industry comparisons." + }, + "/yfinance/etf": { + "description": "Yahoo Finance ETF data." + }, + "/yfinance/derivatives": { + "description": "Yahoo Finance derivatives data." + }, + "/yfinance/derivatives/options": { + "description": "Options data." + }, + "/yfinance/derivatives/futures": { + "description": "Futures data." + }, + "/yfinance/index": { + "description": "Yahoo Finance index data." + }, + "/yfinance/index/price": { + "description": "Index price data." + }, + "/yfinance/crypto": { + "description": "Yahoo Finance crypto data." + }, + "/yfinance/crypto/price": { + "description": "Crypto price data." + }, + "/yfinance/currency": { + "description": "Yahoo Finance currency data." + }, + "/yfinance/currency/price": { + "description": "Currency price data." + }, + "/yfinance/news": { + "description": "Yahoo Finance news data." + }, + "/yfinance/tv_widget": { + "description": "Yahoo Finance data and analytics." + }, + "/yfinance/asset_info": { + "description": "Yahoo Finance data and analytics." + }, + "/yfinance/screener_builder": { + "description": "Yahoo Finance data and analytics." + } + } } \ No newline at end of file diff --git a/openbb_platform/core/openbb/package/__init__.py b/openbb_platform/core/openbb/package/__init__.py index 6eec957f14b..6d065b4f42f 100644 --- a/openbb_platform/core/openbb/package/__init__.py +++ b/openbb_platform/core/openbb/package/__init__.py @@ -1,3 +1,2 @@ -"""Autogenerated OpenBB module.""" - +""" Autogenerated OpenBB module.""" ### THIS FILE IS AUTO-GENERATED. DO NOT EDIT. ### diff --git a/openbb_platform/extensions/platform_api/.coveragerc b/openbb_platform/extensions/platform_api/.coveragerc new file mode 100644 index 00000000000..488a7c5f40b --- /dev/null +++ b/openbb_platform/extensions/platform_api/.coveragerc @@ -0,0 +1,24 @@ +[run] +branch = False +parallel = True +source = openbb_platform_api +data_file = .coverage +omit = + openbb_platform_api/assets/** + tests/* + integration/* + +[paths] +source = + openbb_platform_api + */site-packages/openbb_platform_api + */platform_api/openbb_platform_api + +[report] +show_missing = True +skip_covered = False +exclude_lines = + pragma: no cover + if TYPE_CHECKING: + if __name__ == .__main__.: + raise NotImplementedError diff --git a/openbb_platform/extensions/platform_api/README.md b/openbb_platform/extensions/platform_api/README.md index 4d274a73849..a64de5764fa 100644 --- a/openbb_platform/extensions/platform_api/README.md +++ b/openbb_platform/extensions/platform_api/README.md @@ -1,21 +1,16 @@ -# OpenBB Platform API Launcher +# OpenBB Platform API -This package is responsible for launching and configuring an OpenBB Platform environment, or FastAPI instance, to use as an OpenBB Workspace [custom backend](https://docs.openbb.co/workspace/data-integration). +Launcher and widgets builder for the OpenBB Workspace [custom backend](https://docs.openbb.co/workspace/data-integration). Wraps any FastAPI application with the metadata, exception handling, and `widgets.json` generation that OpenBB Workspace expects — so a regular FastAPI app becomes a Workspace data source with no glue code. -## Installation +> **Full documentation:** [docs.openbb.co/odp/python/extensions/interface/openbb-api](https://docs.openbb.co/odp/python/extensions/interface/openbb-api) -This package is included when you run [`pip install openbb`](https://docs.openbb.co/platform/installation); however, it also works as a standalone package -for creating new backends that are not part of the OpenBB GitHub [repository](https://github.com/OpenBB-finance/OpenBB/). - -To install as a standalone, use a Python environment between versions 3.9 and 3.12, inclusively. +## Install ```sh pip install openbb-platform-api ``` -## Usage - -See the [keyword arguments](#keyword-arguments) section for parameters and descriptions. +Python ≥ 3.10. Already included when you install [`openbb`](https://docs.openbb.co/platform/installation). ### Launch OpenBB Platform @@ -23,31 +18,87 @@ To start the OpenBB Platform API, open a terminal, activate the environment wher ```sh openbb-api + +# Launch your own FastAPI app +openbb-api --app /path/to/your_app.py + +# Factory function? +openbb-api --app some_file.py:create_app --factory + +# Launch as a proxy from an openbb-cli .spec file +openbb-api --spec /path/to/cli.spec ``` -This will launch a Fast API instance, via `uvicorn`, at `http://127.0.0.1:6900` +`widgets.json` is auto-generated from your routes' types, response models, and docstrings. Plotly returns become chart widgets, `BaseModel` returns become tables, scalars become metrics — no manual wiring. -Uvicorn can be configured by adding keyword arguments, see the section [below](#keyword-arguments) +## Spec-driven proxy mode -### Launch Custom App - -To run your application as an OpenBB Workspace custom backend, add the path to the Python file with the FastAPI instance to the launch command. +Generate a spec file with `openbb-cli` against any OpenBB Platform deployment, then launch a Workspace-compatible backend that proxies every command to that upstream: ```sh -openbb-api --app /Users/some_user/path/to/main.py +# Generate the spec once +openbb --generate-spec --server https://api.example.com -o cli.spec + +# Launch the proxy +openbb-api --spec cli.spec ``` -#### Arbitrary Instance Name +Each command in the spec becomes a FastAPI route at its `url_path`; the launcher forwards every request to the spec's `base_url` (preserving query, body, and non-hop-by-hop headers). `widgets.json` is generated from the spec's parameter and response-schema metadata via the same builder used for in-process apps. -Define the FastAPI instance as an arbitrary name with the `--name` argument. +Useful for shipping a thin frontend container that talks to a managed backend in another cluster, without bundling `openbb-core` or any provider extensions. `--spec` is mutually exclusive with `--app`. -```sh -openbb-api --app some_file.py --name my_app +### `[spec]` config — credentials and base-URL override + +A `[spec]` table in `openbb.toml` carries the path plus the bits the file alone can't provide — `base_url` overrides for staging/prod, and `headers` injected on every upstream request. +Header values support the same `$VAR` substitution as `[env]`, so credentials live in environment variables (or `[env]` entries that read from them) and the TOML just maps them onto upstream header names: + +```toml +[env] +OPENBB_UPSTREAM_TOKEN = "$GITHUB_TOKEN" # or any orchestrator-injected secret + +[spec] +path = "/etc/openbb/cli.spec" +base_url = "https://prod.example.com" # optional; overrides spec's recorded value + +[spec.headers] +Authorization = "Bearer $OPENBB_UPSTREAM_TOKEN" +X-Tenant = "production" ``` -#### Factory Flag +Config-supplied headers OVERRIDE matching incoming-request headers — `[spec.headers]` is the credential-injection point, so a misbehaving client can't leak its own auth value upstream by sending the same header name. -If the FastAPI instance is served via a factory function, set the `--factory` flag. +## Custom HTTP middleware (`[middleware]`) + +Attach Starlette-style HTTP middleware functions from a config-supplied entrypoint — useful for auth, request logging, tracing, IP allow-listing, response transformation. Each entry is a `"module:async_callable"` reference resolved through the standard import system: + +```toml +[middleware] +hooks = [ + "my_pkg.middleware:auth_middleware", + "my_pkg.middleware:request_logger", +] +``` + +```python +# my_pkg/middleware.py +from fastapi.responses import JSONResponse + +async def auth_middleware(request, call_next): + if request.headers.get("X-API-Key") != "expected": + return JSONResponse({"error": "unauthorized"}, status_code=401) + return await call_next(request) + +async def request_logger(request, call_next): + response = await call_next(request) + print(f"{request.method} {request.url.path} → {response.status_code}") + return response +``` + +List order is **outermost-to-innermost**: the first entry sees the request first on the way in and the response last on the way out. Misconfigured references (missing module, wrong attribute, sync function, wrong arity) raise loudly at startup so deployments fail fast instead of silently passing requests through unauthenticated. + +## Single-file launch + +Everything in this README — app source (`--app` or `--spec`), env injection, host/port, SSL, agents, middleware, credentials — can be set in one `openbb.toml` and launched without any other CLI flags: ```sh openbb-api --app some_file.py:main --factory @@ -130,214 +181,55 @@ Examples below will assume this code block is at the start of the file. ```python from fastapi import FastAPI +from openbb_platform_api.response_models import ( + Data, + MetricResponseModel, + OmniWidgetResponseModel, + PdfResponseModel, +) app = FastAPI() -``` -### Markdown Widget - -This script will create a "markdown" widget with the returned text. - -```python +# Markdown — return a string @app.get("/hello") async def hello() -> str: - """Widget Description Generated By Docstring""" - return "Hello, from OpenBB!" + """Tooltip from the docstring.""" + return "Hello, OpenBB!" + +# Table — return a list of records or a typed Data subclass +@app.get("/rows") +async def rows() -> list[dict]: + return [{"symbol": "AAPL", "price": 150.0}] + +# Metric — single label/value/delta +@app.get("/score") +async def score() -> MetricResponseModel: + return MetricResponseModel(label="Score", value=100, delta="1%") + +# Chart — return a Plotly figure JSON +@app.get("/chart", openapi_extra={"widget_config": {"type": "chart"}}) +async def chart() -> dict: + from plotly.graph_objs import Bar, Figure + return Figure(data=[Bar(x=["A"], y=[1])]).to_plotly_json() ``` -### Table Widget - -Create a table widget by returning data shaped as a list of dictionaries (records) - -```python -@app.get("/hello") -async def hello() -> list: - """Widget Description Generated By Docstring""" - return [{"Column 1": "Hello", "Column 2": "from OpenBB!"}] -``` - -### Metric Widget - -This widget displays a label, value, and optional delta. - -To create a metric widget, import the custom response model below and define it as a return type. - -```python -from openbb_platform_api.response_models import MetricResponseModel - -@app.get("/hello_metric") -async def hello_metric() -> MetricResponseModel: - """Widget description created by docstring.""" - return MetricResponseModel(label="Good Vibes Score", value=100, delta="1%") -``` - -This type of widget can be created as an array of MetricResponseModels. Adjust the response to be a `list[MetricRespnoseModel]` - -### Query Parameters - -Function arguments will populate as widget parameters. - -```python -from typing import Literal, Optional - -@app.get("/hello") -async def hello(param1: Optional[str] = None, param2: Literal["Choice 1", "Choice 2"] = None, param3: bool = False) -> str: - """Widget Description Generated By Docstring""" - if not param1 and not param2 and not param3: - return "Enter a parameter or make a choice!" - if param3: - return f"Param3 enabled!" - if param2: - return f"You selected: {param2}" - if param1: - return f"You entered: {param1}" - - return "Nothing to return!" -``` - -### Easy Date Picker - -Name the parameter "date", or include "_date" in the name, and type it as a string. - -Additionally, a parameter type of `datetime.date` will work. - -```python -import datetime - -@app.get("/hello_date") -async def hello_date(date: str) -> list: - """Widget description created by docstring.""" - # Workspace returns the date as YYYY-MM-DD - return [{"Hello": "Row 1!"}, {"Hello": "Row 2!"}] - - -@app.get("/hello_date_range") -async def hello_date_range(start: datetime.date, end: datetime.date) -> list: - """Widget description created by docstring.""" - # Workspace returns the date as YYYY-MM-DD - return [{"Hello": "Row 1!"}, {"Hello": "Row 2!"}] -``` - -This demonstrates how to define any of the basic widget parameter types, in a no-frills way. If you just need something that works, it's an easy starting point. - -```python -@app.get("/hello_params") -async def hello_params( - required_param: datetime.date, - param_1: str = "Default", - param_2: int = 0, - param_3: float = None, - param_4: Literal["Choice 1", "Choice 2", "Choice 3"] = "Choice 1", - param_5: bool = True, -) -> list: - """Widget description created by docstring.""" - # Handle the "choices" parameter inside the function to convert the displayed label to the desired one. - choices_dict = {"Choice 1": "do_one", "Choice 2": "do_two", "Choice 3": "do_three"} - choice = choices_dict.get(param_4, None) - - # Do something with the parameters and return the result of work. - return [{"Hello": "Row 1!"}, {"Hello": "Row 2!"}] -``` - -### Annotated Query Params - -Adding helpful placeholder text and tooltips to parameters requires annotating them. This will also help code editors and improve the API documentation. - -Additional settings, compatible with `widgets.json`, are defined in the `json_schema_extra` dictionary, under a key, `x-widget_config` - -```python -from typing import Annotated -from fastapi import Query -``` - -The pattern for annotating a query parameter is: - -```python -my_param: Annotated[str, Query(title="My Title", description="My custom hovertext with detailed information")] = None -``` - -```python -@app.get("/hello_annotated_params") -async def hello_annotated_params( - required_param: Annotated[ - datetime.date, Query(description="The date is required.", title="Required Date") - ], - not_required_param: Annotated[ - Literal["Choice 1", "Choice 2", "Choice 3"], - Query( - description="Choose from a list of possible choices. The default is, 'Choice 1'", - title="Selector", - json_schema_extra={"x-widget_config": {"multiSelect": True}} # This lets you select multiple items from dropdown choices. - ), - ] = "Choice 1", -) -> list: - """Widget description created by docstring.""" - - # Do something with the parameters and return the result of work. - return [{"Hello": "Row 1!"}, {"Hello": "Row 2!"}] -``` - -### Annotated Table Fields - -The procedure for annotating the output is similar to the query parameters, and involves defining a response model. - -A response model is a Data model of Fields. Create one by defining a new class that inherits from "Data", and then define each column as a "Field". +Annotated `Data` models drive auto-generated table column definitions: ```python +from datetime import date from openbb_platform_api.response_models import Data from pydantic import Field -``` -Optional values should be defined, as `Optional[{type}]`, with a default value of `None`. - -```python -class MyData(Data): - """This is a custom Data model.""" - - # Add fields to the model. - column_1: datetime.date = Field( - description="The date column is a mandatory field.", - title="Some Date", - ) - column_2: Optional[str] = Field( - default=None, - description="This is an optional string column.", - title="Some String", - ) - column_3: int = Field( - default=-1, - description="This is an integer column.", - title="Some Integer", - ) - column_4: float = Field( - default=10.25, - description="This is a float column.", - title="Some Float", - ) - column_5: float = Field( - default=10.25, - description="This is a percent column.", - title="Some Percent", - json_schema_extra={"x-widget_config": {"formatterFn": "percent"}}, - ) - column_6: float = Field( - default=0.1025, - description="This is a normalized percent value adjusted for presentation.", - title="Some Normalized Percent", - json_schema_extra={ - "x-widget_config": { - "formatterFn": "normalizedPercent", - "renderFn": "greenRed", - } - }, +class MyRow(Data): + when: date = Field(title="Date", description="Trading date") + pct: float = Field( + title="Change", + json_schema_extra={"x-widget_config": {"formatterFn": "percent", "renderFn": "greenRed"}}, ) - -@app.get("/hello_data") -async def hello_data() -> list[MyData]: - """Widget description created by docstring.""" - # Do something with the parameters and return the result of work. - return [MyData(column_1=datetime.date.today(), column_2="Hello!")] +@app.get("/data") +async def data() -> list[MyRow]: + return [MyRow(when=date.today(), pct=0.0125)] ``` ### PDF Widget @@ -671,12 +563,8 @@ This can be changed by adding the path as an argument. openbb-api --apps-json /Users/some_user/path/to/workspace_apps.json ``` -The OpenBB Workspace allows you to export the current dashboard layout - when it is a custom backend - as a template. +The browser will warn about the untrusted cert — accept once, or add `localhost.crt` to the OS trust store. -To export the layout, right-click on the dashboard and select, "Export apps.json". +## License -A JSON dictionary will be exported. Insert the contents of the export into "~/OpenBBUserData/workspace_apps.json" by pasting between the JSON list markers, [ ]. - -If there are more than one, add a comma between each dictionary entry. - -See the page [here](https://docs.openbb.co/workspace/apps#creating-your-own-app) for details on custom backend apps. +AGPL-3.0-only. © OpenBB. diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/app/__init__.py b/openbb_platform/extensions/platform_api/openbb_platform_api/app/__init__.py new file mode 100644 index 00000000000..d01b911291c --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/app/__init__.py @@ -0,0 +1,25 @@ +"""FastAPI app boot subpackage. + +* ``args`` — command-line argument parsing for the ``openbb-api`` + launcher. +* ``bootstrap`` — pluggable FastAPI app loading + (``import_app``) and platform-extension detection. +* ``app`` — the launcher entry point. Holds the FastAPI app instance, + registers route handlers, exposes ``main`` / ``launch_api``. +""" + +from openbb_platform_api.app import args, bootstrap +from openbb_platform_api.app.args import LAUNCH_SCRIPT_DESCRIPTION, parse_args +from openbb_platform_api.app.bootstrap import ( + check_for_platform_extensions, + import_app, +) + +__all__ = [ + "LAUNCH_SCRIPT_DESCRIPTION", + "args", + "bootstrap", + "check_for_platform_extensions", + "import_app", + "parse_args", +] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/app/app.py b/openbb_platform/extensions/platform_api/openbb_platform_api/app/app.py new file mode 100644 index 00000000000..b97faca791c --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/app/app.py @@ -0,0 +1,435 @@ +"""OpenBB Platform API launcher entry point. + +Runs at import time: + +1. ``parse_args`` reads the CLI flags and may swap in a custom FastAPI + ``app`` via ``--app``. +2. The active ``app`` gets walked for platform-extension routes that + should be excluded from auto widget generation. +3. The Workspace endpoints (``/``, ``/widgets.json``, ``/apps.json``, + ``/agents.json``) are registered on whatever app instance is now + active — only when the user hasn't already mounted their own at + the same path. + +``main`` is the script entry point declared in pyproject.toml; it +delegates to ``launch_api`` which calls ``uvicorn.run`` with the +parsed kwargs. +""" + +import json +import logging +import os +import sys +from pathlib import Path + +import uvicorn +from fastapi.responses import HTMLResponse, JSONResponse +from openbb_core.app.service.system_service import SystemService +from openbb_core.env import Env + +from openbb_platform_api.app.args import parse_args +from openbb_platform_api.app.bootstrap import ( + apply_cors_from_system_service, + check_for_platform_extensions, +) +from openbb_platform_api.service import widgets_service +from openbb_platform_api.service.agents_service import ( + get_additional_agents, + has_additional_agents, +) +from openbb_platform_api.service.apps_service import ( + get_additional_apps, + has_additional_apps, +) +from openbb_platform_api.service.widgets_service import get_widgets_json +from openbb_platform_api.utils.network import check_port, get_user_settings + +logger = logging.getLogger("openbb_platform_api") +logger.setLevel(logging.INFO) +handler = logging.StreamHandler() +handler.setLevel(logging.INFO) +formatter = logging.Formatter("\n%(message)s\n") +handler.setFormatter(formatter) +logger.addHandler(handler) + + +# Adds the OpenBB Environment variables to the script process. +Env() +HOME = os.environ.get("HOME") or os.environ.get("USERPROFILE") + +if not HOME: # pragma: no cover — defensive against pristine env + # Both ``HOME`` (POSIX) and ``USERPROFILE`` (Windows) are unset + # only on a wholly stripped environment — no shell, no + # ``passwd``, no Windows user profile. Real launches always have + # one or the other; testing the absence requires module reload + # in a process with a wiped environment, which is intrusive + # for the rest of the test session. + raise ValueError("HOME or USERPROFILE environment variable not set.") + +CURRENT_USER_SETTINGS = os.path.join(HOME, ".openbb_platform", "user_settings.json") +# Widget filtering is optional and can be used to exclude widgets from the widgets.json file +# Alternatively, you can supply a JSON-encoded list of API paths to ignore. +WIDGET_SETTINGS = os.path.join(HOME, ".openbb_platform", "widget_settings.json") + + +# --------------------------------------------------------------------------- +# Module-level boot. Importing this module parses argv, may swap out +# ``app``, and registers the launcher routes. Tests that need the +# parsing path in isolation should import ``app/args`` directly rather +# than triggering the full import chain. +# --------------------------------------------------------------------------- +kwargs = parse_args() +_app = kwargs.pop("app", None) +if _app: + app = _app +else: + # Defer this import: ``openbb_core.api.rest_api`` triggers + # ``AppLoader.from_extensions`` and walks every installed extension + # to mount routers — heavy in a large environment. When the user + # supplied their own app via ``--app``, none of that work is needed + # and importing it just to drop ``default_app`` would waste seconds + # of startup time and a lot of memory. + from openbb_core.api.rest_api import app as default_app + + app = default_app + + +# Ensure CORS is wired from SystemService for every code path — +# ``--app`` (already handled inside ``import_app`` but the helper +# is idempotent so the second call is a no-op), ``--spec`` (the +# proxy app has no CORS otherwise — OPTIONS preflight returns 405), +# and the default ``rest_api`` app (which already has CORS, also +# skipped via dedup). Placing the call here guarantees a single +# source of truth at module import time. +apply_cors_from_system_service(app) + + +# Apply user-defined HTTP middleware hooks from the layered TOML +# config — runs once at module import, before any request hits the +# app. Reading the config a second time here is cheap (TOML parse is +# microseconds; ``main.py`` already validated it) and avoids +# threading a config object through ``parse_args``. +def _apply_configured_middleware() -> None: + """Pull ``[middleware] hooks`` from the bootstrapped config and + register each entrypoint as an HTTP middleware on ``app``. The + list runs outermost-to-innermost in TOML order; failures raise + loudly at startup so misconfigurations don't silently passthrough. + + Reads from ``get_bootstrapped_config()`` rather than re-running + the cascade — ``main.py``'s bootstrap is the only step that sniffs + ``--config-file`` from argv, so reusing its result is the only way + to see the same merged config here. + """ + from openbb_platform_api.app.config import get_bootstrapped_config + from openbb_platform_api.app.middleware import apply_http_middleware_hooks + + layered = get_bootstrapped_config() + hooks = (layered.get("middleware") or {}).get("hooks") + apply_http_middleware_hooks(app, hooks) + + +_apply_configured_middleware() + +WIDGETS_PATH = kwargs.pop("widgets-json", None) +APPS_PATH = kwargs.pop("apps-json", None) +EDITABLE = kwargs.pop("editable", None) is True or WIDGETS_PATH is not None +DEFAULT_APPS_PATH = ( + Path(__file__) + .absolute() + .parents[1] + .joinpath("assets") + .joinpath("default_apps.json") +) +AGENTS_PATH = kwargs.pop("agents-json", None) +build = kwargs.pop("build", True) +build = False if kwargs.pop("no-build", None) else build +dont_filter = kwargs.pop("no-filter", False) +widget_exclude_filter: list = kwargs.pop("exclude", []) +uvicorn_settings = ( + SystemService().system_settings.python_settings.model_dump().get("uvicorn", {}) +) +obb_headers = {"X-Backend-Type": "OpenBB Platform"} + +for key, value in uvicorn_settings.items(): + if key not in kwargs and key != "app" and value is not None: + kwargs[key] = value + +if not dont_filter and os.path.exists(WIDGET_SETTINGS): + with open(WIDGET_SETTINGS, encoding="utf-8") as widget_settings_file: + try: + widget_exclude_filter_json = json.load(widget_settings_file).get( + "exclude", [] + ) + if isinstance(widget_exclude_filter_json, list): + widget_exclude_filter.extend(widget_exclude_filter_json) + except json.JSONDecodeError as e: + logger.info("Error loading widget filter settings -> %s", e) + + +widget_exclude_filter = check_for_platform_extensions(app, widget_exclude_filter) +openapi = app.openapi() +current_settings = get_user_settings(CURRENT_USER_SETTINGS) +widgets_json = get_widgets_json( + build, openapi, widget_exclude_filter, EDITABLE, WIDGETS_PATH, app +) +APPS_PATH = ( + APPS_PATH + if APPS_PATH + else ( + current_settings.get("preferences", {}).get( + "data_directory", HOME + "/OpenBBUserData" + ) + + "/workspace_apps.json" + ) +) + + +@app.get("/") +async def root(): + """Serve the landing page HTML content.""" + html_path = Path(__file__).parents[1] / "assets" / "landing_page.html" + with open(html_path, encoding="utf-8") as f: + html_content = f.read() + return HTMLResponse(content=html_content) + + +# Check if the app has already defined widgets.json at the root. +has_root_widgets = any(getattr(d, "path", "") == "/widgets.json" for d in app.routes) + +if not has_root_widgets: + # We assume that if an app already has /widgets.json at the app root, + # we can leave it alone. Otherwise, use our endpoint to serve and/or generate. + @app.get("/widgets.json") + async def get_widgets(): + """Widgets configuration file for the OpenBB Workspace. + + Two modes: + + * **Editable** — re-load the on-disk ``widgets.json`` on every + request so manual edits to the file are picked up live with no + server restart. The file is created at startup if it doesn't + already exist; subsequent requests just stream the file's + current contents. + * **Ephemeral** — serve the in-memory build that ran at startup. + ``FIRST_RUN`` flips on the first request so any router-attached + widget discovery happens once, then the cached dict is reused. + """ + if EDITABLE: + # Editable always loads from disk — including the first + # request — so the user's hand edits are reflected without + # a restart. Bypassing the FIRST_RUN cache here is the whole + # point of the flag. + return JSONResponse( + content=get_widgets_json( + False, openapi, widget_exclude_filter, EDITABLE, WIDGETS_PATH, app + ), + headers=obb_headers, + ) + if widgets_service.FIRST_RUN is True: + widgets_service.FIRST_RUN = False + return JSONResponse(content=widgets_json, headers=obb_headers) + +else: + # Populate the local name `get_widgets` with the endpoint function + # of the existing root /widgets.json route so callers (e.g. + # get_apps_json) can await it. ``app.routes`` returns + # ``BaseRoute`` instances that don't expose ``endpoint`` — + # ``getattr`` keeps the attribute access uniform whether the + # matched object is an ``APIRoute`` (has ``endpoint``) or + # something else (Mount, static-files route — falls back to the + # local async wrapper below). + root_route = next( + (r for r in app.routes if getattr(r, "path", "") == "/widgets.json"), None + ) + _root_endpoint = getattr(root_route, "endpoint", None) if root_route else None + if _root_endpoint is not None: + get_widgets = _root_endpoint + else: + # Fallback mechanism + async def get_widgets(): + """Return the generated widgets.json""" + return JSONResponse(content=widgets_json, headers=obb_headers) + + +# Check if the app has already defined apps.json at the root. +has_root_apps = any(getattr(d, "path", "") == "/apps.json" for d in app.routes) + +if not has_root_apps: + + @app.get("/apps.json") + async def get_apps_json(): + """Get the apps.json file.""" + new_templates: list = [] + default_templates: list = [] + widgets = await get_widgets() + + if not os.path.exists(APPS_PATH): + apps_dir = os.path.dirname(APPS_PATH) + if not os.path.exists(apps_dir): + os.makedirs(apps_dir, exist_ok=True) + # Write an empty file for the user to add exported apps from Workspace to. + with open(APPS_PATH, "w", encoding="utf-8") as templates_file: + templates_file.write(json.dumps([])) + + if os.path.exists(DEFAULT_APPS_PATH): + with open(DEFAULT_APPS_PATH, encoding="utf-8") as f: + default_templates = json.load(f) + + if has_additional_apps(app): + additional_apps = await get_additional_apps(app) + + if additional_apps: + for apps in additional_apps.values(): + if not apps: + continue + if apps and isinstance(apps, list): + default_templates.extend(apps) + elif apps and not isinstance(apps, list): + logger.error( + "TypeError: Invalid apps.json format. Expected a list[dict] got %s instead -> %s", + type(apps), + str(apps), + ) + + if os.path.exists(APPS_PATH): + with open(APPS_PATH, encoding="utf-8") as templates_file: + templates = json.load(templates_file) + + if isinstance(templates, dict): + templates = [templates] + + templates.extend(default_templates) + + for template in templates: + if _id := template.get("id"): + if _id in widgets and template not in new_templates: + new_templates.append(template) + continue + elif template.get("layout") or template.get("tabs"): + if _tabs := template.get("tabs"): + for v in _tabs.values(): + if v.get("layout", []) and all( + item.get("i", "").startswith("rich_note") + or item.get("i") in widgets_json + for item in v.get("layout") + ): + new_templates.append(template) + break + elif ( + template.get("layout") + and all( + item.get("i", "").startswith("rich_note") + or item.get("i") in widgets_json + for item in template["layout"] + ) + and template not in new_templates + ): + new_templates.append(template) + + if new_templates: + return JSONResponse(content=new_templates, headers=obb_headers) + + return JSONResponse(content=[], headers=obb_headers) + + +if AGENTS_PATH: + + @app.get("/agents.json") + async def get_agents(): + """Get the agents.json file.""" + if os.path.exists(AGENTS_PATH): + with open(AGENTS_PATH, encoding="utf-8") as f: + agents = json.load(f) + return JSONResponse(content=agents, headers=obb_headers) + return JSONResponse(content={}, headers=obb_headers) + + +# Check if the app has already defined agents.json at the root. +has_root_agents = any(getattr(d, "path", "") == "/agents.json" for d in app.routes) + +if not has_root_agents and has_additional_agents(app): + + @app.get("/agents.json") + async def get_agents_json(): + """Get the agents.json file.""" + new_agents: dict = {} + additional_agents = await get_additional_agents(app) + if additional_agents: + for path_agents in additional_agents.values(): + for k, v in path_agents.items(): + new_agents[k] = v + return JSONResponse(content=new_agents, headers=obb_headers) + +else: + + @app.get("/agents.json") + async def get_agents_json(): + """Get an empty agents.json file.""" + return {} + + +def launch_api(**_kwargs): # noqa: PLR0912 + """Start the API server. + + Validates / clamps the host/port from kwargs (or env vars), probes + for a free port via ``check_port`` if the requested one is taken, + and hands off to ``uvicorn.run`` with the launcher's module path so + the FastAPI app instance imported here is the one served. + """ + host = _kwargs.pop("host", os.getenv("OPENBB_API_HOST", "127.0.0.1")) + if not host: + logger.info( + "OPENBB_API_HOST is set incorrectly. It should be an IP address or hostname." + ) + host = input("Enter the host IP address or hostname: ") + if not host: + host = "127.0.0.1" + + port = _kwargs.pop("port", os.getenv("OPENBB_API_PORT", "6900")) + + try: + port = int(port) + except ValueError: + logger.info("OPENBB_API_PORT is set incorrectly. It should be an port number.") + port = input("Enter the port number: ") + try: + port = int(port) + except ValueError: + logger.info("Invalid port number. Defaulting to 6900.") + port = 6900 + if port < 1025: + port = 6900 + logger.info("Invalid port number, must be above 1024. Defaulting to 6900.") + + free_port = check_port(host, port) + + if free_port != port: + logger.info("Port %d is already in use. Using port %d.", port, free_port) + port = free_port + + if "use_colors" not in _kwargs: + _kwargs["use_colors"] = "win" not in sys.platform or os.name != "nt" + + _msg = ( + "\nTo access this data from OpenBB Workspace, use the link displayed after the application startup completes." + "\nChrome is the recommended browser. Other browsers may conflict or require additional configuration." + f"\n{f'Documentation is available at {app.docs_url}.' if app.docs_url else ''}" + ) + logger.info(_msg) + uvicorn.run("openbb_platform_api.main:app", host=host, port=port, **_kwargs) + + +def main(): + """Launch the API.""" + launch_api(**kwargs) + + +if __name__ == "__main__": # pragma: no cover — script entry + # Direct ``python -m openbb_platform_api.app.app`` invocation. + # The console script declared in ``pyproject.toml`` calls + # ``main()`` via ``main:main`` instead, so this branch only fires + # for manual debugging. + try: + main() + except KeyboardInterrupt: + sys.exit(0) diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/app/args.py b/openbb_platform/extensions/platform_api/openbb_platform_api/app/args.py new file mode 100644 index 00000000000..2e4bd076710 --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/app/args.py @@ -0,0 +1,456 @@ +"""Command-line argument parsing for ``openbb-api``. + +Pulled out of ``utils/api.py`` so the CLI parsing path can be exercised +in isolation. ``parse_args`` reads ``sys.argv[1:]`` and returns a dict +of normalized kwargs; the entry-point in ``app/app.py`` consumes that +dict to wire up the FastAPI app and pass remaining flags through to +``uvicorn.run``. +""" + +import json +import logging +import os +import sys +from pathlib import Path + +_spec_headers_logger = logging.getLogger("openbb_platform_api.spec.headers") + + +def _collect_multi_spec_entries(spec_section: dict) -> dict[str, dict]: + """Pick out the ``[spec.NAME]`` subtables that look like spec entries. + + Multi-spec form uses ``[spec.equity]`` / ``[spec.crypto]`` / ... + subtables, each carrying its own ``path`` (and optional ``mount``, + ``base_url``, ``content_sha256``, ``[spec.NAME.headers]``, etc.). + + A subtable counts as a multi-spec entry only when: + + * The value is a dict — protects against scalar reserved keys + (``path``, ``base_url``, ``content_sha256``, ``mount``, + ``headers`` are single-spec siblings, not entries). + * The dict has a ``path`` key — keeps non-spec sibling tables + (``[spec.headers]`` from the single-spec form, etc.) out of the + multi-spec set. + """ + out: dict[str, dict] = {} + for name, value in spec_section.items(): + if isinstance(value, dict) and isinstance(value.get("path"), str): + out[name] = value + return out + + +def _expand_spec_headers( + raw_headers: dict | None, +) -> dict[str, str]: + """Expand ``$VAR`` / ``${VAR}`` refs in a ``[spec.headers]`` table. + + Each value goes through the same substitution logic ``[env]`` uses + (against the current ``os.environ``), so credentials live in one + place — the orchestrator-supplied env vars — and the launcher TOML + just maps them to upstream header names. Entries with unresolved + references are SKIPPED with a warning rather than shipped to the + upstream as literal ``"$MISSING"`` values. Non-string keys (which + real TOML can't produce, but defensive code keeps us honest) are + silently dropped. + """ + from openbb_platform_api.app.config import expand_env_refs + + if not raw_headers: + return {} + expanded: dict[str, str] = {} + for header_name, header_value in raw_headers.items(): + if not isinstance(header_name, str): + continue + substituted, missing = expand_env_refs(str(header_value)) + if missing: + _spec_headers_logger.warning( + "Skipping [spec.headers] entry %s: references " + "unresolved variable(s) %s", + header_name, + ", ".join(missing), + ) + continue + expanded[header_name] = substituted + return expanded + + +LAUNCH_SCRIPT_DESCRIPTION = """ +Serve the OpenBB Platform API. + + +Launcher specific arguments: + + --app Absolute path to the Python file with the target FastAPI instance. Default is the installed OpenBB Platform API. + --name Name of the FastAPI instance in the app file. Default is 'app'. + --factory Flag to indicate if the app name is a factory function. Default is 'false'. + --editable Flag to make widgets.json an editable file that can be modified during runtime. Default is 'false'. + --build If the file already exists, changes prompt action to overwrite/append/ignore. Only valid when --editable true. + --no-build Do not build the widgets.json file. Use this flag to load an existing widgets.json file without checking for updates. + --exclude JSON encoded list of API paths to exclude from widgets.json. Disable entire routes with '*' - e.g. '["/api/v1/*"]'. + --no-filter Do not filter out widgets in widget_settings.json file. + --widgets-json Absolute/relative path to use as the widgets.json file. Default is ~/envs/{env}/assets/widgets.json, when --editable is 'true'. + --apps-json Absolute/relative path to use as the apps.json file. Default is ~/OpenBBUserData/workspace_apps.json. + --agents-json Absolute/relative path to use as the agents.json file. Including this will add the /agents endpoint to the API. + --config-file Absolute/relative path to a TOML config file. Highest-priority TOML layer in the cascade. + Also honored via $OPENBB_API_CONFIG or $OPENBB_CONFIG. CLI flags still override TOML values. + --spec Absolute/relative path to an `openbb-cli` generated `.spec` file. Synthesizes a Workspace-compatible + backend that proxies every command in the spec to its `base_url`. widgets.json / apps.json are + generated from the spec's metadata. Mutually exclusive with --app. + + +Config file (`openbb.toml`) — runtime defaults + env injection: + +The launcher reads the same layered TOML cascade openbb-core ships with +(pyproject → user-global → project → explicit → .env → real env vars). +Two extra tables are launcher-specific: + + [launcher] + # Default values for any of the CLI flags above. Use the same + # names (TOML accepts hyphens). CLI flags ALWAYS win over TOML. + host = "0.0.0.0" + port = 6900 + agents-json = "/etc/openbb/agents.json" + exclude = ["/api/v1/admin/*"] + + [env] + # Pushed into os.environ before any heavy import runs. Existing + # shell env vars are NEVER clobbered. Useful in containers to + # inject OPENBB_* keys without shell exports. + OPENBB_API_HOST = "0.0.0.0" + OPENBB_API_PORT = "6900" + # Values support shell-style $VAR / ${VAR} substitution against + # the current environment, so you can map orchestrator-injected + # secrets (Kubernetes, docker run -e, CI tokens) to the names the + # application expects. Entries with unresolved references are + # SKIPPED with a warning rather than set to a literal "$MISSING". + OPENBB_GITHUB_TOKEN = "$GITHUB_TOKEN" + OPENBB_API_URL = "https://${HOST}:${PORT}/v1" + + [spec] + # Single-spec mode: spec-driven proxy with one upstream target. + # The path can also be supplied via --spec on the CLI; the rest + # of the per-spec config (``base_url``, ``content_sha256``, + # ``headers``) ONLY lives here. + path = "/etc/openbb/cli.spec" + base_url = "https://upstream.example.com" # optional override of the spec's recorded base_url + # Optional pinned SHA-256. When set, the launcher recomputes the + # spec's canonical-JSON hash and refuses to start unless it + # matches. Lets ops version-control the expected hash separately + # from the spec file itself — useful for remote-distributed + # specs whose payload is fetched at boot but whose version is + # pinned by the deployment manifest. + content_sha256 = "abc123..." + + [spec.headers] + # Static headers injected on every proxied upstream request. + # Override matching incoming-request headers — credential + # injection point. Same $VAR / ${VAR} substitution as [env]. + Authorization = "Bearer $OPENBB_UPSTREAM_TOKEN" + X-API-Key = "$OPENBB_UPSTREAM_KEY" + + # Multi-spec mode (alternative to single-spec): each [spec.NAME] + # subtable mounts an independent spec under its own prefix. The + # launcher serves a parent FastAPI whose routes union all mounts. + # Every per-spec key (base_url, content_sha256, headers, auth, + # middleware) is scoped to that subtable. + [spec.equity] + path = "/etc/openbb/equity.spec" + mount = "/equity" # optional; defaults to "/" + base_url = "https://equity.example.com" + content_sha256 = "abc..." + [spec.equity.headers] + Authorization = "Bearer $EQUITY_TOKEN" + [spec.equity.auth] + hooks = ["my_pkg.auth:equity_token_check"] + [spec.equity.middleware] + hooks = ["my_pkg.middleware:equity_rate_limit"] + + [spec.crypto] + path = "/etc/openbb/crypto.spec" + [spec.crypto.headers] + X-API-Key = "$CRYPTO_KEY" + + [middleware] + # HTTP middleware applied to every inbound server request. + # Each entry is a "module:async_callable" reference resolved via + # the standard import system. The callable signature must be + # ``async def fn(request, call_next): ...``. List order is + # outermost-to-innermost: the first entry sees the request first + # on the way in and the response last on the way out. + hooks = [ + "my_pkg.middleware:auth_middleware", + "my_pkg.middleware:request_logger", + ] + +The cascade is container-friendly: every layer is optional, the +user-global layer is skipped when HOME is unset, and project-local +discovery walks up from CWD so a single openbb.toml at the container +root configures the whole stack. + + +The FastAPI app instance can be imported to another script, modified, and launched by using the --app argument. + +If the path to the app file is not absolute, it will be resolved relative to the current working directory. + +Imported with: + +>>> from openbb_platform_api.main import app +>>> +>>> @app.get() +>>> async def hello(input: str = "Hello") -> str: +>>> '''Widget description created by doctring.''' +>>> return f"You entered: {input}" + +Launched with: + +>>> openbb-api --app /path/to/some_file.py + +The app instance name can be defined by either the --name argument, or by referencing the module name, for example: + +>>> openbb-api --app some_file.py:main --factory + +A name must be set when using the factory flag. + +All other arguments will be passed to uvicorn. Here are the most common ones: + + --host TEXT Host IP address or hostname. + [default: 127.0.0.1] + --port INTEGER Port number. + [default: 6900] + --ssl-keyfile TEXT SSL key file. + --ssl-certfile TEXT SSL certificate file. + --ssl-keyfile-password TEXT SSL keyfile password. + --ssl-version INTEGER SSL version to use. + (see stdlib ssl module's) + [default: 17] + --ssl-cert-reqs INTEGER Whether client certificate is required. + (see stdlib ssl module's) + [default: 0] + --ssl-ca-certs TEXT CA certificates file. + --ssl-ciphers TEXT Ciphers to use. + (see stdlib ssl module's) + [default: TLSv1] + +Run `uvicorn --help` to get the full list of arguments. +""" # noqa: E501 + + +def parse_args() -> dict: # noqa: PLR0912 + """Parse ``sys.argv[1:]`` into the launcher's keyword-arg dict. + + Recognized launcher flags get normalized (booleans, JSON-encoded + lists, ``app`` → imported FastAPI instance, ``agents-json`` / + ``widgets-json`` / ``apps-json`` paths resolved relative to ``cwd``). + Anything else passes through verbatim and ultimately reaches + ``uvicorn.run`` as keyword arguments. + """ + + from openbb_platform_api.app.bootstrap import import_app # noqa + from openbb_platform_api.app.config import ( + load_launcher_config, + merge_launcher_kwargs, + resolve_explicit_config_path, + ) + + args = sys.argv[1:].copy() + cwd = Path.cwd() + _kwargs: dict = {} + for i, arg in enumerate(args): + if arg == "--help": + print(LAUNCH_SCRIPT_DESCRIPTION) # noqa: T201 + sys.exit(0) + if arg.startswith("--"): + key = arg[2:] + if key in ["no-use-colors", "use-colors"]: + _kwargs["use_colors"] = key == "use-colors" + elif i + 1 < len(args) and not args[i + 1].startswith("--"): + value = args[i + 1] + if isinstance(value, str) and value.lower() in ["false", "true"]: + _kwargs[key] = value.lower() == "true" + elif key == "exclude": + _kwargs[key] = json.loads(value) + else: + _kwargs[key] = value + else: + _kwargs[key] = True + + # Layered TOML config — overlay ``[launcher]`` defaults under the + # CLI kwargs (CLI always wins). The explicit-path slot is fed by + # ``--config-file`` here, or falls back to ``$OPENBB_API_CONFIG`` + # / ``$OPENBB_CONFIG`` when the flag isn't supplied. + cli_config_path = _kwargs.pop("config-file", None) + explicit_path = resolve_explicit_config_path(cli_config_path) + # ``apply_to_*=False`` here — ``main.py`` already ran the bootstrap + # and applied env / services. We just want the merged dict to + # extract the ``[launcher]`` defaults from. + layered = load_launcher_config( + explicit_path=explicit_path, + apply_to_services=False, + apply_to_env=False, + ) + _kwargs = merge_launcher_kwargs(_kwargs, layered.get("launcher")) + + # ``--spec`` (or ``[spec] path``) and ``--app`` are mutually + # exclusive — both supply the FastAPI ``app`` instance the + # launcher serves. ``--spec`` synthesizes one from a digested + # OpenAPI snapshot; ``--app`` imports a user-written one. Reject + # combinations early so the failure mode is a clear startup error. + spec_section: dict = layered.get("spec") or {} + multi_specs = _collect_multi_spec_entries(spec_section) + single_spec_path = spec_section.get("path") or _kwargs.pop("spec", None) + if (single_spec_path or multi_specs) and _kwargs.get("app"): + raise ValueError( + "Error: --spec and --app are mutually exclusive. " + "--spec synthesizes a proxy app from the spec's commands; " + "--app imports a user-supplied app. Pick one." + ) + + if multi_specs: + # Multi-spec mode — every ``[spec.NAME]`` subtable becomes a + # mounted sub-app. The launcher serves a single parent FastAPI + # whose routes are union-ed from all mounts. + from typing import Any + + from openbb_platform_api.app.spec import build_apps_from_specs, load_spec + + specs_config: dict[str, dict[str, Any]] = {} + for name, sub in multi_specs.items(): + sub_path = sub["path"] + if not Path(sub_path).is_absolute(): + sub_path = str(cwd.joinpath(sub_path).resolve()) + specs_config[name] = { + "spec": load_spec( + sub_path, + expected_content_sha256=sub.get("content_sha256"), + ), + "mount": sub.get("mount"), + "base_url_override": sub.get("base_url"), + "extra_headers": _expand_spec_headers(sub.get("headers")), + "spec_name": Path(sub_path).name, + "auth_hooks": (sub.get("auth") or {}).get("hooks"), + "middleware_hooks": (sub.get("middleware") or {}).get("hooks"), + } + _kwargs["app"] = build_apps_from_specs(specs_config) + + elif single_spec_path: + from openbb_platform_api.app.spec import build_app_from_spec, load_spec + + if not Path(single_spec_path).is_absolute(): + single_spec_path = str(cwd.joinpath(single_spec_path).resolve()) + + # Optional ``[spec].content_sha256`` lets ops version-control + # the expected hash separately from the spec file itself — + # important for remote distribution where the operator's + # deployment manifest is the source of truth and the file in + # ``[spec].path`` is just a fetched artifact. + _kwargs["app"] = build_app_from_spec( + load_spec( + single_spec_path, + expected_content_sha256=spec_section.get("content_sha256"), + ), + base_url_override=spec_section.get("base_url"), + extra_headers=_expand_spec_headers(spec_section.get("headers")), + # Full filename ("fertilizer.spec" for + # "/etc/openbb/fertilizer.spec") — used as the source + # citation on every auto-generated widget so dashboards + # don't display a generic "Custom" label for every column. + spec_name=Path(single_spec_path).name, + ) + + elif _kwargs.get("app"): + _app_path = _kwargs.pop("app", None) + _name = _kwargs.pop("name", "app") + _factory = _kwargs.pop("factory", False) + + def _is_module_colon_notation(path: str) -> bool: + if ":" not in path: + return False + if len(path) >= 2 and path[1] == ":" and path[0].isalpha(): + return path.count(":") > 1 + return True + + if _is_module_colon_notation(_app_path): + _app_instance_name = _app_path.split(":")[-1] + _name = _app_instance_name if _app_instance_name else _name + + if _factory and not _name: + raise ValueError( + "Error: The factory function name must be provided to the --name parameter when the factory flag is set." + ) + _kwargs["app"] = import_app(_app_path, _name, _factory) + + if isinstance(_kwargs.get("exclude"), str): + _kwargs["exclude"] = [_kwargs["exclude"]] + + if _kwargs.get("agents-json") or _kwargs.get("copilots-path"): + _agents_path = _kwargs.pop("agents-json", None) or _kwargs.pop( + "copilots-path", None + ) + + if not str(_agents_path).endswith(".json"): + # ``os.path.join`` produces platform-native separators so the + # path round-trips cleanly through ``Path``/``str`` on + # Windows — the prior f-string with a literal ``/`` produced + # mixed ``C:\dir/agents.json`` on Windows. + _agents_path = os.path.join(str(_agents_path), "agents.json") + + if str(_agents_path).startswith("./"): + _agents_path = str(cwd.joinpath(_agents_path).resolve()) + + _kwargs["agents-json"] = _agents_path + + if _kwargs.get("widgets-json") or _kwargs.get("widgets-path"): + _widgets_path = _kwargs.pop("widgets-json", None) or _kwargs.pop( + "widgets-path", None + ) + + # If it's a file (endswith .json), use as is; else treat as directory and append widgets.json + if str(_widgets_path).endswith(".json"): + widgets_file_path = _widgets_path + else: + # Platform-native join — see ``apps-json`` branch above for + # rationale (avoids mixed ``C:\dir/widgets.json`` on Windows). + widgets_file_path = os.path.join(str(_widgets_path), "widgets.json") + + # Resolve relative paths to absolute + if str(widgets_file_path).startswith("./"): + widgets_file_path = str(cwd.joinpath(widgets_file_path).resolve()) + + _kwargs["widgets-json"] = widgets_file_path + + if _kwargs.get("widgets-json"): + _kwargs["editable"] = True + # If the file already exists, we assume that it is already built. + if os.path.exists(_kwargs["widgets-json"]): + _kwargs["no-build"] = True + + # Handle apps-json and templates-path in the same way as widgets-path + if _kwargs.get("apps-json") or _kwargs.get("templates-path"): + _apps_path = _kwargs.pop("apps-json", None) or _kwargs.pop( + "templates-path", None + ) + + # If it's a file (endswith .json), use as is; else treat as directory and append apps.json + if str(_apps_path).endswith(".json"): + apps_file_path = _apps_path + else: + # ``os.path.join`` gives platform-native separators (``\`` on + # Windows, ``/`` elsewhere), so the produced string round-trips + # through ``str(Path(...))`` cleanly on every OS — the previous + # f-string concatenation hard-coded ``/`` and produced mixed + # separators on Windows like ``C:\tmp\dir/apps.json``. + possible_workspace_file = os.path.join( + str(_apps_path), "workspace_apps.json" + ) + if os.path.isfile(possible_workspace_file): + apps_file_path = possible_workspace_file + else: + apps_file_path = os.path.join(str(_apps_path), "apps.json") + + # Resolve relative paths to absolute + if str(apps_file_path).startswith("./"): + apps_file_path = str(cwd.joinpath(apps_file_path).resolve()) + + _kwargs["apps-json"] = apps_file_path + + return _kwargs diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/app/bootstrap.py b/openbb_platform/extensions/platform_api/openbb_platform_api/app/bootstrap.py new file mode 100644 index 00000000000..2d6486c659e --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/app/bootstrap.py @@ -0,0 +1,229 @@ +"""FastAPI app bootstrapping helpers. + +* ``import_app`` — load a user-supplied FastAPI app from a file path, + module path, or factory function. Used by the ``--app`` launcher flag + to swap in a custom app while preserving the launcher's middleware / + exception-handler wiring. +* ``check_for_platform_extensions`` — detect installed data-processing + extensions (econometrics, quantitative, technical) and add their + routes to the widget exclude filter so they don't pollute the + Workspace widget catalogue. +* ``apply_cors_from_system_service`` — install ``CORSMiddleware`` on + the FastAPI app using the origins/methods/headers configured on + ``SystemService.system_settings.api_settings.cors``. Idempotent — + skips installation when ``CORSMiddleware`` is already in the + middleware stack so the launcher can call it unconditionally + regardless of which path produced the app instance. +""" + +import os +import sys +from pathlib import Path + +from fastapi import FastAPI + + +def apply_cors_from_system_service(app: FastAPI) -> bool: + """Add ``CORSMiddleware`` to ``app`` from SystemService settings. + + Without this, OPTIONS preflight requests from a browser hit the + actual route (which only allows GET/POST) and return ``405 Method + Not Allowed`` — Workspace shows the widget as broken. + + Idempotent: returns ``True`` when the middleware was added, + ``False`` when it was already present (e.g. ``rest_api`` already + installed it on the default app, or a user-supplied app brought + its own CORS configuration). + """ + from fastapi.middleware.cors import CORSMiddleware + from openbb_core.app.service.system_service import SystemService + + if any(getattr(m, "cls", None) is CORSMiddleware for m in app.user_middleware): + return False + + cors = SystemService().system_settings.api_settings.cors + app.add_middleware( + CORSMiddleware, + allow_origins=cors.allow_origins, + allow_methods=cors.allow_methods, + allow_headers=cors.allow_headers, + ) + return True + + +def import_app(app_path: str, name: str = "app", factory: bool = False) -> FastAPI: + """Import a user-defined FastAPI app from a path / module spec. + + ``app_path`` accepts three forms: + + * Module-colon notation — ``my.module:app`` or + ``./my_file.py:app``. The text after ``:`` overrides ``name``. + * Bare file path — ``./my_app/main.py`` (relative or absolute). + Falls back to a ``spec_from_file_location`` import. + * Bare module path — ``my.module``. Goes through ``import_module``. + + ``factory=True`` calls the resulting attribute and uses its return + value as the app instance; the launcher prints a soft-warning and + invokes the factory anyway when it detects callability without the + flag set, matching uvicorn's behavior. + + Wires CORS + OpenBB exception handlers into the imported app so + the launcher's standard middleware applies regardless of which + app the user pointed at. + + Raises ``FileNotFoundError`` / ``AttributeError`` / ``TypeError`` + on import / lookup / shape mismatches. + """ + + from importlib import import_module, util + + from fastapi.exceptions import ResponseValidationError + + # Decoupled from ``openbb_core.api.rest_api`` and + # ``openbb_core.api.app_loader`` on purpose: those modules pull in + # ``RouterLoader`` / ``ExtensionLoader``, which discover and mount + # every installed extension. The user supplied their own app via + # ``--app`` precisely to avoid that machinery, so we go directly + # to the lightweight pieces (CORS + exception handler functions) + # without booting the default app. + from openbb_core.api.exception_handlers import ExceptionHandlers + from openbb_core.app.model.abstract.error import OpenBBError + from openbb_core.provider.utils.errors import ( + EmptyDataError, + UnauthorizedError, + ) + from pydantic import ValidationError + + def _is_module_colon_notation(app_path: str) -> bool: + """Tell colon-as-name from colon-as-Windows-drive.""" + if ":" not in app_path: + return False + # Windows absolute path check (e.g., C:\path or D:/path) + if len(app_path) >= 2 and app_path[1] == ":" and app_path[0].isalpha(): + # Could still have colon notation: C:\path\file.py:app + parts = app_path.split(":") + return len(parts) > 2 # More than just drive letter colon + return True + + def _load_module_from_file_path(file_path: str): + spec_name = os.path.basename(file_path).split(".")[0] + spec = util.spec_from_file_location(spec_name, file_path) + + if spec is None or spec.loader is None: + # ``spec_from_file_location`` returns None for unloadable + # paths; the loader can be None when the spec is a + # namespace package or otherwise un-execable. Either way + # we can't run the module, so surface a clear error. + raise RuntimeError(f"Failed to load the file specs for '{file_path}'") + + module = util.module_from_spec(spec) + sys.modules[spec_name] = module + spec.loader.exec_module(module) + return module + + if _is_module_colon_notation(app_path): + module_path, name = app_path.rsplit(":", 1) + try: # First try to import as a module + module = import_module(module_path) + except ImportError: # If module import fails, try to load as a local file + if not module_path.endswith(".py"): + module_path += ".py" + + if not Path(module_path).is_absolute(): + cwd = Path.cwd() + file_path = str(cwd.joinpath(module_path).resolve()) + else: + file_path = module_path + + if not Path(file_path).exists(): + raise FileNotFoundError( + f"Error: Neither module '{module_path}' could be imported nor file '{file_path}' exists" + ) + + module = _load_module_from_file_path(file_path) + + # Bare file path (e.g., "main.py" or "my_app/main.py") + else: + if not Path(app_path).is_absolute(): + cwd = Path.cwd() + app_path = str(cwd.joinpath(app_path).resolve()) + + if not Path(app_path).exists(): + raise FileNotFoundError(f"Error: The app file '{app_path}' does not exist") + + module = _load_module_from_file_path(app_path) + + if not hasattr(module, name): + raise AttributeError( + f"Error: The app file '{app_path}' does not contain an '{name}' instance" + ) + + app_or_factory = getattr(module, name) + + # Mirror uvicorn's factory detection: if the attribute is callable, + # invoke it; if the user didn't pass --factory but it works, soft- + # warn rather than failing. + try: + app = app_or_factory() + if not factory: + print( # noqa: T201 + "\n\n[WARNING] " + "App factory detected. Using it, but please consider setting the --factory flag explicitly.\n" + ) + except TypeError: + if factory: + raise TypeError( + f"Error: The {name} instance in '{app_path}' appears not to be a callable factory function" + ) + app = app_or_factory + + if not isinstance(app, FastAPI): + raise TypeError( + f"Error: The {name} instance in '{app_path}' is not an instance of FastAPI" + ) + + apply_cors_from_system_service(app) + + # Inlined from ``AppLoader.add_exception_handlers`` so this code path + # avoids importing ``openbb_core.api.app_loader`` (which transitively + # pulls in ``RouterLoader`` / ``ExtensionLoader``). + app.exception_handlers[Exception] = ExceptionHandlers.exception + app.exception_handlers[ValidationError] = ExceptionHandlers.validation + app.exception_handlers[ResponseValidationError] = ExceptionHandlers.validation + app.exception_handlers[OpenBBError] = ExceptionHandlers.openbb + app.exception_handlers[EmptyDataError] = ExceptionHandlers.empty_data + app.exception_handlers[UnauthorizedError] = ExceptionHandlers.unauthorized + + return app + + +def check_for_platform_extensions( + fastapi_app: FastAPI, widgets_to_exclude: list +) -> list: + """Add data-processing-extension routes to the widget exclude filter. + + ``econometrics`` / ``quantitative`` / ``technical`` are utility + extensions that operate on already-fetched data; they don't make + sense as Workspace widgets. When the launcher detects any of them + loaded into the FastAPI app's tags, it adds their route prefix + (``/api/v1//*``) to the exclude filter so those endpoints + don't sprout widgets in the catalogue. + """ + + from openbb_core.app.service.system_service import SystemService + + to_check_for = ["econometrics", "quantitative", "technical"] + openapi_tags = fastapi_app.openapi_tags or [] + tags: list = [] + for tag in openapi_tags: + if any(mod in tag.get("name", "") for mod in to_check_for): + tags.append(tag.get("name", "")) + + if tags and (any(f"openbb_{mod}" in sys.modules for mod in to_check_for)): + api_prefix = SystemService().system_settings.api_settings.prefix + for tag in tags: + if f"openbb_{tag}" in sys.modules: + # If the module is loaded, we can safely add it to the exclude filter. + widgets_to_exclude.append(f"{api_prefix}/{tag}/*") + + return widgets_to_exclude diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/app/config.py b/openbb_platform/extensions/platform_api/openbb_platform_api/app/config.py new file mode 100644 index 00000000000..9dea97bf48c --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/app/config.py @@ -0,0 +1,350 @@ +"""Launcher TOML config bootstrap. + +Hooks ``openbb-api`` into the same layered TOML cascade +``openbb-core`` ships with (pyproject → user-global → project → +explicit → .env → real-shell env vars), and surfaces two extra tables +the core loader doesn't itself consume: + +* ``[launcher]`` — defaults for the ``openbb-api`` CLI flags (``host``, + ``port``, ``app``, ``agents-json``, ``editable``, etc.). CLI flags + always win; the TOML provides fallbacks. +* ``[env]`` — extra environment variables pushed into ``os.environ`` + before any heavy import runs. Lets containers inject API keys and + other ``OPENBB_*`` knobs without shell exports. Real shell env vars + always win (we never clobber a value the operator already set). + +The ``--config-file `` CLI flag and the ``OPENBB_API_CONFIG`` / +``OPENBB_CONFIG`` env vars all map to the same explicit-path slot in +the cascade — the highest-priority TOML layer below real env vars. + +Container compatibility: + +* Every cascade layer is optional. The user-global layer + (``$HOME/.openbb_platform/openbb.toml``) is gracefully skipped when + ``HOME`` is unset or unreadable. +* Project-local discovery walks up from CWD, so a single + ``openbb.toml`` at the container root configures everything. +* The bootstrap runs BEFORE ``openbb_platform_api.app.app`` imports, + so ``[env]`` keys are visible to the very first ``openbb_core.*`` + module load — no race between env injection and module-time + ``Env()`` reads. +""" + +from __future__ import annotations + +import logging +import os +import re +import sys +from collections.abc import Mapping, MutableMapping +from typing import Any + +logger = logging.getLogger("openbb_platform_api.config") + +#: Match ``$VAR`` and ``${VAR}`` references in ``[env]`` values. The +#: identifier rule mirrors POSIX shell variable names: a leading +#: letter / underscore followed by letters / digits / underscores. A +#: ``$`` not followed by a valid identifier (e.g. ``$5``, ``$ ``, +#: ``$@``) is left untouched, so literal-dollar use cases still work. +_ENV_REF_PATTERN = re.compile( + r"\$\{(?P[A-Za-z_][A-Za-z0-9_]*)\}" + r"|\$(?P[A-Za-z_][A-Za-z0-9_]*)" +) + +#: Env vars that, when set, point ``load_launcher_config`` at an +#: explicit TOML to use as the highest-priority layer in the cascade +#: (real shell env vars still win). ``OPENBB_API_CONFIG`` is the +#: launcher-specific name; ``OPENBB_CONFIG`` is core's existing slot — +#: we honor it too so a single env var can configure the whole stack. +EXPLICIT_CONFIG_ENVS: tuple[str, ...] = ("OPENBB_API_CONFIG", "OPENBB_CONFIG") + +#: CLI flag that supplies an explicit config path. Detected by +#: ``extract_config_file_from_argv`` before the launcher's full +#: ``parse_args`` runs, so the bootstrap can find the TOML before +#: any heavy import. +CONFIG_FILE_FLAG = "--config-file" + + +def extract_config_file_from_argv(argv: list[str] | None = None) -> str | None: + """Sniff ``--config-file `` out of argv WITHOUT importing args. + + Used in two places: + + 1. ``main.py`` — to find the config before the launcher's heavy + module body runs. + 2. ``args.parse_args`` — same flag, full parser; this helper just + gives ``main.py`` a peek. + + Accepts both ``--config-file PATH`` and ``--config-file=PATH``. + Returns ``None`` if the flag isn't present or has no value. + """ + args = (argv if argv is not None else sys.argv[1:]).copy() + for i, arg in enumerate(args): + if arg == CONFIG_FILE_FLAG and i + 1 < len(args): + value = args[i + 1] + if value and not value.startswith("--"): + return value + elif arg.startswith(f"{CONFIG_FILE_FLAG}="): + return arg.split("=", 1)[1] or None + return None + + +def resolve_explicit_config_path( + cli_path: str | None = None, + env: Mapping[str, str] | None = None, +) -> str | None: + """Pick the explicit-path slot for the cascade. + + Priority (highest first): ``--config-file`` CLI value → env vars in + ``EXPLICIT_CONFIG_ENVS`` order. Returns the first non-empty value + or ``None`` when nothing is set. + + ``env`` is typed as ``Mapping`` (read-only) so callers can pass + either ``os.environ`` (an ``_Environ`` instance) or a plain dict + without ty complaining about variance. + """ + if cli_path: + return cli_path + env_map: Mapping[str, str] = env if env is not None else os.environ + for key in EXPLICIT_CONFIG_ENVS: + value = env_map.get(key) + if value: + return value + return None + + +def _validate_explicit_toml(explicit_path: str) -> None: + """Pre-parse the user-supplied config so malformed TOML fails loudly. + + The core cascade swallows TOML decode errors (every layer is + best-effort by design — a typo in someone else's pyproject.toml + shouldn't crash an unrelated launcher). But when the operator + passes ``--config-file`` or sets ``$OPENBB_API_CONFIG``, a parse + failure means the deployment is silently misconfigured and the + launcher would boot with defaults the operator never asked for. + Raise instead so the error surfaces at startup. + + Missing files are tolerated (cascade behavior) — only + parse failures escalate. + """ + import sys as _sys + from pathlib import Path + + if _sys.version_info >= (3, 11): # pragma: no cover — version-conditional + import tomllib + else: # pragma: no cover — exercised only on the 3.10 backport path + import tomli as tomllib # ty: ignore[unresolved-import] + + p = Path(explicit_path) + if not p.is_file(): + return + try: + with p.open("rb") as fh: + tomllib.load(fh) + except tomllib.TOMLDecodeError as exc: + raise ValueError( + f"Malformed TOML at explicit config path '{explicit_path}': {exc}" + ) from exc + + +def load_launcher_config( + explicit_path: str | None = None, + *, + apply_to_services: bool = True, + apply_to_env: bool = True, +) -> dict[str, Any]: + """Run the layered TOML cascade and return the merged config. + + Thin wrapper around ``openbb_core.app.config.loader.load_layered_config``. + Defaults preserve the core loader's behavior — push system/user + sections onto the singleton services and emit ``OPENBB_*`` env vars + for the promoted top-level flags. Pass ``apply_to_services=False`` + if you only want the dict for inspection (e.g., in tests). + + The returned dict includes the ``[launcher]`` and ``[env]`` tables + raw — callers must run ``apply_launcher_env`` / merge launcher + kwargs themselves; the core loader doesn't know about those. + + If ``explicit_path`` is provided, the file is pre-parsed so any + TOML syntax errors raise immediately (instead of being silently + swallowed by the cascade's best-effort layer handling). + """ + from openbb_core.app.config.loader import load_layered_config + + if explicit_path: + _validate_explicit_toml(explicit_path) + + return load_layered_config( + explicit_path=explicit_path, + apply_to_services=apply_to_services, + apply_to_env=apply_to_env, + ) + + +def expand_env_refs( + value: str, env: Mapping[str, str] | None = None +) -> tuple[str, list[str]]: + """Public entry point for ``$VAR`` / ``${VAR}`` substitution. + + Used by ``apply_launcher_env`` for the ``[env]`` table and by + ``[spec.headers]`` parsing for credential injection on the spec + proxy. Defaults ``env`` to ``os.environ`` so callers don't have to + plumb it through. ``env`` is typed as ``Mapping`` so ``os.environ`` + (an ``_Environ`` instance, not a plain dict) is accepted. + Returns ``(expanded_value, missing_refs)`` — missing refs are + reported left-to-right with deduplication. + """ + return _expand_env_refs(value, env if env is not None else os.environ) + + +def _expand_env_refs(value: str, env: Mapping[str, str]) -> tuple[str, list[str]]: + """Substitute ``$VAR`` / ``${VAR}`` references against ``env``. + + Returns ``(expanded_value, missing_refs)``. Missing references stay + as their literal ``$VAR`` form in the expanded value so a caller + that wants to skip the entry has the original spelling for the + error message. Each missing name is reported only once even if it + appears multiple times in the same value. + + Order of substitution matches the regex match order (left to + right), and ``apply_launcher_env`` iterates in TOML insertion + order — so an earlier ``[env]`` entry that lands in ``os.environ`` + becomes resolvable for a later entry that references it. + """ + missing: list[str] = [] + + def replace(match: re.Match[str]) -> str: + name = match.group("braced") or match.group("bare") + if name and name in env: + return env[name] + if name and name not in missing: + missing.append(name) + return match.group(0) + + return _ENV_REF_PATTERN.sub(replace, value), missing + + +def apply_launcher_env( + env_section: dict[str, Any] | None, + *, + env: MutableMapping[str, str] | None = None, +) -> list[str]: + """Push ``[env]`` table entries into ``os.environ`` (no clobber). + + Real shell env vars always win — if a key is already set, we leave + it alone. Values support shell-style ``$VAR`` / ``${VAR}`` + substitution against the current environment, so a TOML can map + secrets injected by the orchestrator (Kubernetes secrets, + ``docker run -e``, ``GITHUB_TOKEN`` from CI, etc.) into the + ``OPENBB_*`` names the application expects:: + + [env] + OPENBB_GITHUB_TOKEN = "$GITHUB_TOKEN" + OPENBB_API_HOST = "${HOST}" + + Entries whose value references an unresolved variable are SKIPPED + (a warning is logged) — silently setting a value of literal + ``"$MISSING"`` would let the application boot with bogus credentials. + + Substitution order is TOML-insertion order, so an earlier ``[env]`` + entry can be referenced by a later one within the same table. + + Returns the list of keys actually applied (resolved + set) so + callers can log/observe what came from TOML versus the shell. + ``env`` arg is for tests; callers normally let it default to the + real ``os.environ``. + """ + if not env_section: + return [] + target: MutableMapping[str, str] = env if env is not None else os.environ + applied: list[str] = [] + for key, value in env_section.items(): + if not isinstance(key, str): + continue + if key in target: + continue + expanded, missing = _expand_env_refs(str(value), target) + if missing: + logger.warning( + "Skipping [env] entry %s: references unresolved variable(s) %s", + key, + ", ".join(missing), + ) + continue + target[key] = expanded + applied.append(key) + return applied + + +def merge_launcher_kwargs( + cli_kwargs: dict[str, Any], + launcher_section: dict[str, Any] | None, +) -> dict[str, Any]: + """Overlay ``[launcher]`` section under CLI kwargs (CLI wins). + + The launcher section is treated as a defaults dict — every key it + provides is filled into ``cli_kwargs`` only when the CLI didn't + already set it. Lists / dicts / scalars are all replaced wholesale + (no deep merge); the CLI is the single source of truth for any + explicit setting. + """ + if not launcher_section: + return cli_kwargs + merged = dict(launcher_section) + merged.update(cli_kwargs) + return merged + + +#: Stashed result of the most recent ``bootstrap_launcher_config`` so +#: downstream consumers (``parse_args``, the middleware hook +#: registrar in ``app.py``) can read the same merged config without +#: re-walking the cascade — and, more importantly, without losing the +#: ``--config-file`` argv sniff that only ``bootstrap_launcher_config`` +#: performs. Cleared by ``reset_bootstrapped_config`` for tests. +_BOOTSTRAPPED_CONFIG: dict[str, Any] | None = None + + +def bootstrap_launcher_config( + argv: list[str] | None = None, +) -> dict[str, Any]: + """One-call bootstrap for ``main.py``. + + Resolves the explicit config path from ``--config-file``/env vars, + runs the layered cascade, and applies the ``[env]`` table. The + merged result is also stashed in ``_BOOTSTRAPPED_CONFIG`` so + later boot phases (``parse_args``, middleware registration) can + reuse it via ``get_bootstrapped_config()`` without re-doing + cascade discovery — that matters because the launcher's argv + sniff for ``--config-file`` only happens here, and ``app.py`` + has no other way to find the same explicit path. + + Safe to call before any ``openbb-core`` import — the only modules + touched are stdlib + the core's lightweight loader module. + """ + global _BOOTSTRAPPED_CONFIG # noqa: PLW0603 + + cli_path = extract_config_file_from_argv(argv) + explicit_path = resolve_explicit_config_path(cli_path) + config = load_launcher_config(explicit_path=explicit_path) + apply_launcher_env(config.get("env")) + _BOOTSTRAPPED_CONFIG = config + return config + + +def get_bootstrapped_config() -> dict[str, Any]: + """Return the config loaded by the most recent ``bootstrap_launcher_config``. + + Returns an empty dict when bootstrap hasn't run — keeps the + consumer code path branch-free (it can ``.get("middleware", {})`` + without first checking for None). + """ + return _BOOTSTRAPPED_CONFIG or {} + + +def reset_bootstrapped_config() -> None: + """Clear the stashed bootstrap config. For tests only — production + code shouldn't need to reset launcher state. + """ + global _BOOTSTRAPPED_CONFIG # noqa: PLW0603 + + _BOOTSTRAPPED_CONFIG = None diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/app/middleware.py b/openbb_platform/extensions/platform_api/openbb_platform_api/app/middleware.py new file mode 100644 index 00000000000..398aff71ec9 --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/app/middleware.py @@ -0,0 +1,185 @@ +"""HTTP middleware hook entrypoints for ``openbb-api``. + +Lets a deployment attach Starlette-style HTTP middleware functions to +the launcher's FastAPI app from a config-supplied entrypoint, with no +launcher source changes. Useful for: + +* request authentication / authorization (API keys, JWT validation, + mTLS client cert checks) +* request logging, tracing, metric collection +* IP allow-listing / rate limiting +* response transformation (CORS overrides, custom headers) + +Configured via the ``[middleware]`` table in the launcher TOML:: + + [middleware] + hooks = [ + "my_pkg.middleware:auth_middleware", + "my_pkg.middleware:request_logger", + ] + +Each entry is a ``"module:callable"`` reference resolved through the +standard import system. The callable must be an async function with +the Starlette HTTP middleware signature:: + + async def auth_middleware(request, call_next): + # pre-process — short-circuit by returning a Response + if not request.headers.get("X-API-Key"): + return JSONResponse({"error": "unauthorized"}, status_code=401) + + response = await call_next(request) + + # post-process — inspect or mutate the response + response.headers["X-Custom"] = "value" + return response + +Order matters. Hooks are registered so the FIRST entry in the TOML +list is the OUTERMOST layer — it sees the request first on the way in +and the response last on the way out. This matches the natural +"outside in" reading of the config file. + +Failures during hook resolution / registration raise loudly at +startup. A misconfigured middleware reference is a deployment bug +that needs to surface immediately, not a silent passthrough. +""" + +from __future__ import annotations + +import importlib +import inspect +import logging +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, cast + +if TYPE_CHECKING: + from fastapi import FastAPI + +logger = logging.getLogger("openbb_platform_api.middleware") + + +def _resolve_entrypoint(path: str): + """Import ``module:attr`` and return the resolved attribute. + + Splits on the first ``:`` so attribute names with dots + (``MyClass.method``) work. Errors are re-raised with a message + that names the offending entrypoint string for fast debugging. + """ + if ":" not in path: + raise ValueError( + f"Middleware entrypoint must be 'module:attr', got {path!r}. " + "Example: 'my_pkg.middleware:auth_middleware'." + ) + module_path, _, attr_path = path.partition(":") + try: + module = importlib.import_module(module_path) + except ImportError as exc: + raise ImportError( + f"Failed to import middleware module {module_path!r} " + f"(entrypoint {path!r}): {exc}" + ) from exc + + target = module + for part in attr_path.split("."): + try: + target = getattr(target, part) + except AttributeError as exc: + raise AttributeError( + f"Middleware module {module_path!r} has no attribute " + f"{attr_path!r} (entrypoint {path!r})" + ) from exc + return target + + +def _validate_middleware_callable(fn, path: str) -> None: + """Sanity-check the resolved callable so misconfigurations fail at + startup rather than on the first incoming request. + + Verifies it's callable, takes at least two positional parameters, + and is an async function (Starlette HTTP middleware must be + coroutines — sync functions silently break the middleware chain). + """ + if not callable(fn): + raise TypeError( + f"Middleware entrypoint {path!r} resolved to a non-callable " + f"{type(fn).__name__}." + ) + if not inspect.iscoroutinefunction(fn): + raise TypeError( + f"Middleware entrypoint {path!r} must be an async function " + "(``async def fn(request, call_next): ...``). Sync middleware " + "silently breaks the Starlette middleware chain." + ) + try: + sig = inspect.signature(fn) + except (TypeError, ValueError): # pragma: no cover — rare builtins + # Some builtins / C-implemented callables don't expose a + # signature — let those through; FastAPI will surface any + # arity mismatch when the middleware is invoked. Pragma'd + # because constructing such a callable in a test is brittle + # and the branch is purely defensive. + return + positional = [ + p + for p in sig.parameters.values() + if p.kind + in { + inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD, + } + ] + if len(positional) < 2: + raise TypeError( + f"Middleware entrypoint {path!r} must accept (request, " + f"call_next); signature has only {len(positional)} positional " + "parameter(s)." + ) + + +def apply_http_middleware_hooks( + app: FastAPI, + hooks: list[str] | None, +) -> list[str]: + """Register each entrypoint as HTTP middleware on ``app``. + + Hooks run in the order ``[middleware] hooks`` lists them — the + first entry is the outermost layer. Returns the list of resolved + entrypoint paths, in registration order, for logging / tests. + + Raises immediately on resolution / signature-validation failures + so a deployment with a bad reference fails at startup, not + on the first inbound request. + """ + if not hooks: + return [] + if not isinstance(hooks, list): + raise TypeError( + "[middleware] hooks must be a list of 'module:attr' strings; " + f"got {type(hooks).__name__}." + ) + + resolved: list[tuple[str, object]] = [] + for hook_path in hooks: + if not isinstance(hook_path, str): + raise TypeError( + f"[middleware] hooks entries must be strings; got " + f"{type(hook_path).__name__} ({hook_path!r})." + ) + fn = _resolve_entrypoint(hook_path) + _validate_middleware_callable(fn, hook_path) + resolved.append((hook_path, fn)) + + # Starlette's middleware stack is built bottom-up: ``add_middleware`` + # inserts at index 0, so the LAST registered entry runs FIRST on + # the way in. To make the TOML list read top-to-bottom as + # outermost-to-innermost (the natural reading order), we register + # in REVERSE — the first hook ends up at the top of the stack. + for hook_path, fn in reversed(resolved): + # ``fn`` was just validated to be an async callable with the + # right arity by ``_validate_middleware_callable``, but it's + # statically typed as ``object`` because ``_resolve_entrypoint`` + # returns the raw imported attribute. Cast for the decorator's + # benefit so ty doesn't trip on the ``DecoratedCallable`` bound. + app.middleware("http")(cast("Callable[..., Any]", fn)) + logger.info("Registered HTTP middleware: %s", hook_path) + + return [path for path, _ in resolved] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/app/spec.py b/openbb_platform/extensions/platform_api/openbb_platform_api/app/spec.py new file mode 100644 index 00000000000..c9c678d8b3d --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/app/spec.py @@ -0,0 +1,906 @@ +"""Launch the API from an ``openbb-cli`` generated ``.spec`` file. + +A ``.spec`` is a digested OpenAPI snapshot — commands with their +``url_path``, ``parameters``, ``response_schema``, and a ``base_url`` +pointing at the upstream that produced it. Feeding one to +``openbb-api`` synthesizes a Workspace-compatible backend that: + +* exposes the same surface as the upstream (every command becomes a + FastAPI route), and +* proxies every request to ``base_url + url_path``, replaying query + string / body / headers verbatim. + +The launcher's existing ``widgets.json`` builder runs against the +synthesized OpenAPI document, so widget generation, ``apps.json`` +discovery, and the editable / SSRM flows all work without changes. +``apps.json`` is file-based and unaffected. + +Use cases: + +* Connect Workspace to a remote OpenBB Platform deployment without + running the full backend locally — ship just ``openbb-platform-api`` + and the spec. +* Containerize a thin frontend that dispatches to a managed backend + in another cluster / region. + +Trade-offs: + +* Latency doubles (proxy hop). Fine for dashboard-driven workloads, + worth measuring for high-frequency endpoints. +* The spec doesn't carry credentials. Headers from the incoming + request are forwarded as-is, so deploy the launcher behind whatever + auth your upstream expects (mTLS, JWT, API gateway, …). +""" + +from __future__ import annotations + +import hashlib +import json +import re +from pathlib import Path +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field, ValidationError + +#: Trailing ``T00:00:00`` patterns we treat as "no real time-of-day" +#: when EVERY row in a response carries the same shape for a given +#: field. Matches ``T00:00:00``, ``T00:00:00.000``, ``T00:00:00Z``, +#: ``T00:00:00.000Z``, and ``T00:00:00+00:00``-style offsets. Does NOT +#: match ``T00:00:00.001`` (real ms), ``T00:00:01`` (real second), etc. +_ZERO_TIME_RE = re.compile(r"T00:00:00(?:\.0+)?(?:Z|[+-]\d{2}:?\d{2})?$") + +#: Common envelope keys whose value is the row-list. Inspected when the +#: top-level payload is a dict — the trim recurses into the list under +#: any of these keys before bailing. +_ENVELOPE_KEYS: tuple[str, ...] = ("results", "data", "rows", "records") + +from fastapi import FastAPI, Request +from fastapi.responses import Response + +#: Spec versions this loader knows how to consume. Newer minor +#: revisions can extend the schema with optional fields without +#: bumping; ``SPEC_VERSION`` matches the openbb-cli generator. +SUPPORTED_SPEC_VERSIONS: frozenset[int] = frozenset({5}) + + +# --------------------------------------------------------------------------- +# Spec provenance + structural compatibility models +# --------------------------------------------------------------------------- +# +# Mirrors ``openbb_cli.dispatchers.spec.SpecDocument``. Inlining the model +# (rather than importing from openbb-cli) keeps the launcher's runtime +# dep tree light — the CLI's spec module pulls argparse translators and +# openapi walkers that are pure deadweight here. + + +class _CommandParameterModel(BaseModel): + """One parameter inside a command's ``parameters`` list.""" + + model_config = ConfigDict(extra="allow", populate_by_name=True) + + name: str + type: str = "string" + is_list: bool = False + required: bool = False + default: Any = None + choices: list[Any] = Field(default_factory=list) + + +class _CommandSpecModel(BaseModel): + """One entry in ``spec["commands"]`` keyed by dotted command path.""" + + model_config = ConfigDict(extra="allow") + + url_path: str + method: str + parameters: list[_CommandParameterModel] = Field(default_factory=list) + request_body_schema: dict[str, Any] | None = None + response_schema: dict[str, Any] | None = None + + +class _SpecDocumentModel(BaseModel): + """The on-disk shape of a ``.spec`` file. + + Validation is structural: every required field must be present + with the declared type. ``content_sha256`` is required — every + spec produced by ``openbb-cli`` stamps the field at generation + time, so an absent value indicates either a corrupted file or + a hand-rolled forgery; either way the launcher refuses to load + it. Unknown fields are accepted so older launchers stay + forward-compat with future spec generators that introduce + optional metadata. + """ + + model_config = ConfigDict(extra="allow") + + version: int + base_url: str + api_prefix: str = "" + commands: dict[str, _CommandSpecModel] + content_sha256: str + routers: dict[str, Any] = Field(default_factory=dict) + reference: dict[str, Any] = Field(default_factory=dict) + generated_at: str | None = None + generator: str | None = None + source_url: str | None = None + api_version: str | None = None + + +def _content_hash(spec_doc: dict[str, Any]) -> str: + """Hash the spec doc deterministically, ignoring ``content_sha256``. + + The hash covers the canonical JSON serialization of the doc with + the ``content_sha256`` field excluded, so the value is reproducible + from the written file alone — same algorithm openbb-cli uses when + stamping the spec at generation time. + """ + payload = {k: v for k, v in spec_doc.items() if k != "content_sha256"} + canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() + return hashlib.sha256(canonical).hexdigest() + + +# --------------------------------------------------------------------------- +# Spec loading + provenance / integrity verification +# --------------------------------------------------------------------------- + + +def load_spec( + path: str | Path, + *, + expected_content_sha256: str | None = None, +) -> dict[str, Any]: + """Read, validate, and verify the integrity of a ``.spec`` file. + + Performs four checks before handing the spec to downstream + consumers (the proxy builder, widgets generator, etc.): + + 1. **File parseability** — must exist and contain valid JSON + under a top-level object. + 2. **Version compatibility** — ``version`` must be one of + ``SUPPORTED_SPEC_VERSIONS``. Mismatches raise with the + regenerate hint. + 3. **Structural schema** — pydantic-validated against + ``_SpecDocumentModel`` (required fields, declared types, + command shape). Catches deeper malformations than the + single-key sanity checks could. + 4. **Content integrity** — recompute SHA-256 over the canonical + JSON serialization and verify against the spec's + ``content_sha256``. The field is REQUIRED — every spec from + ``openbb-cli`` carries one, and an absent or mismatched + value indicates corruption or tampering. When + ``expected_content_sha256`` is also supplied (typically from + ``[spec].content_sha256`` in the deploy TOML), the recomputed + hash must ALSO match the deploy pin so a remotely-distributed + spec can be tied to a specific revision in the deployment + manifest. Both checks raise distinct errors so the operator + sees whether the failure is in-file tampering or version + drift between the manifest and the deployed artifact. + + Returns the spec as a plain dict so callers don't pay the + pydantic-model overhead on every read. + + Raises ``FileNotFoundError`` for missing paths, ``ValueError`` for + every other failure. Each ValueError names the offending file path + so multi-spec deployments can pinpoint the bad input. + """ + p = Path(path) + if not p.is_file(): + raise FileNotFoundError(f"Spec file not found: {path}") + try: + spec = json.loads(p.read_text(encoding="utf-8")) + except json.JSONDecodeError as exc: + raise ValueError(f"Spec file is not valid JSON: {path} — {exc}") from exc + if not isinstance(spec, dict): + raise ValueError(f"Spec file does not contain a top-level object: {path}") + version = spec.get("version") + if version not in SUPPORTED_SPEC_VERSIONS: + raise ValueError( + f"Unsupported spec version {version!r} at {path}. " + f"This launcher knows versions {sorted(SUPPORTED_SPEC_VERSIONS)}. " + "Regenerate with a matching ``openbb --generate-spec``." + ) + base_url = spec.get("base_url") + if not isinstance(base_url, str) or not base_url: + raise ValueError( + f"Spec missing required ``base_url`` field at {path}. " + "Spec must point at the upstream the proxy will forward to." + ) + if not isinstance(spec.get("commands"), dict): + raise ValueError(f"Spec missing required ``commands`` table at {path}.") + try: + _SpecDocumentModel.model_validate(spec) + except ValidationError as exc: + raise ValueError( + f"Spec file at {path} does not conform to the expected schema:\n{exc}" + ) from exc + + # ``content_sha256`` is required by the pydantic schema, so we + # know it's a non-empty string at this point. + recorded_hash = spec["content_sha256"] + actual_hash = _content_hash(spec) + if recorded_hash != actual_hash: + raise ValueError( + f"Spec file at {path} failed integrity check: recorded SHA-256 " + f"{recorded_hash!r} does not match recomputed {actual_hash!r}. " + "The file has been modified since it was generated." + ) + if expected_content_sha256 is not None and expected_content_sha256 != actual_hash: + raise ValueError( + f"Spec file at {path} failed deploy-config pin check: " + f"expected SHA-256 {expected_content_sha256!r} (from deploy " + f"config) does not match recomputed {actual_hash!r}. The " + "deployed spec is not the version pinned by the operator." + ) + return spec + + +# --------------------------------------------------------------------------- +# Spec → OpenAPI dict (so build_json keeps working unchanged) +# --------------------------------------------------------------------------- + +#: Map ``spec.parameters[].type`` strings back to the JSON Schema types +#: the existing OpenAPI walker (``utils/openapi.py``) expects. +_SPEC_TYPE_TO_JSON_SCHEMA: dict[str, str] = { + "string": "string", + "integer": "integer", + "number": "number", + "boolean": "boolean", +} + + +def _spec_param_to_openapi(param: dict[str, Any]) -> dict[str, Any]: + """Reverse ``cli.spec``'s ``_normalize_parameter`` so the param looks + like the OpenAPI parameter object the launcher's widget builder + consumes. + + The shapes are similar but not identical — spec params hoist + ``type`` / ``default`` / ``choices`` to the top level; OpenAPI + nests them under ``schema``. This re-nests them and recovers + ``description`` from the spec's ``help`` slot. + """ + schema: dict[str, Any] = {} + type_name = param.get("type", "string") + json_schema_type = _SPEC_TYPE_TO_JSON_SCHEMA.get(type_name, "string") + if param.get("is_list"): + schema["type"] = "array" + schema["items"] = {"type": json_schema_type} + else: + schema["type"] = json_schema_type + if (default := param.get("default")) is not None: + schema["default"] = default + if param.get("choices"): + schema["enum"] = list(param["choices"]) + if (description := param.get("help")) is not None: + schema["description"] = description + + out: dict[str, Any] = { + "name": param["name"], + "in": param.get("in", "query"), + "required": bool(param.get("required")), + "schema": schema, + } + if description is not None: + out["description"] = description + if (example := param.get("example")) is not None: + out["example"] = example + return out + + +def synthesize_openapi_from_spec(spec: dict[str, Any]) -> dict[str, Any]: + """Reconstruct an OpenAPI document from a spec's command list. + + The launcher's widget builder consumes raw OpenAPI dicts. Rather + than fork that code path for spec-driven launches, we reverse the + spec normalization and feed ``build_json`` an OpenAPI it already + knows how to walk. Every command yields one path entry; the + response_schema (when present) flows through unchanged so column + auto-detection works the same as in a live-platform launch. + """ + paths: dict[str, dict[str, Any]] = {} + for cmd_name, cmd in spec.get("commands", {}).items(): + url = cmd.get("url_path") + method = (cmd.get("method") or "get").lower() + if not url or method not in {"get", "post"}: + continue + + op: dict[str, Any] = { + "operationId": cmd_name, + "summary": cmd.get("description") or "", + "description": cmd.get("description") or "", + "parameters": [ + _spec_param_to_openapi(p) for p in (cmd.get("parameters") or []) + ], + } + + if response_schema := cmd.get("response_schema"): + op["responses"] = { + "200": {"content": {"application/json": {"schema": response_schema}}} + } + else: + op["responses"] = {"200": {"content": {"application/json": {}}}} + + if method == "post" and (request_body_schema := cmd.get("request_body_schema")): + op["requestBody"] = { + "content": {"application/json": {"schema": request_body_schema}} + } + + paths.setdefault(url, {})[method] = op + + return { + "openapi": "3.1.0", + "info": { + "title": "OpenBB Platform API (spec-driven proxy)", + "version": str(spec.get("api_version") or "1.0"), + }, + "paths": paths, + "components": {"schemas": {}}, + } + + +# --------------------------------------------------------------------------- +# Spec → FastAPI proxy app +# --------------------------------------------------------------------------- + +#: Hop-by-hop headers that must NOT be forwarded across a proxy. +#: From RFC 7230 §6.1 plus a few practical extras (Workspace requests +#: tend to come through ``starlette``, which already handles these, +#: but we filter explicitly to be safe). +_HOP_BY_HOP_HEADERS: frozenset[str] = frozenset( + { + "connection", + "content-encoding", + "content-length", + "host", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "te", + "trailer", + "transfer-encoding", + "upgrade", + } +) + + +def _filter_request_headers(headers: dict[str, str]) -> dict[str, str]: + """Drop hop-by-hop headers before forwarding upstream.""" + return {k: v for k, v in headers.items() if k.lower() not in _HOP_BY_HOP_HEADERS} + + +def _filter_response_headers(headers: dict[str, str]) -> dict[str, str]: + """Drop hop-by-hop headers on the response side — let the + launcher's own response infrastructure (Content-Length, + Content-Encoding) take over rather than echoing upstream's, which + can mismatch after transcoding. + """ + return {k: v for k, v in headers.items() if k.lower() not in _HOP_BY_HOP_HEADERS} + + +def build_app_from_spec( + spec: dict[str, Any], + *, + base_url_override: str | None = None, + extra_headers: dict[str, str] | None = None, + spec_name: str | None = None, +) -> FastAPI: + """Synthesize a FastAPI app whose routes proxy to ``spec.base_url``. + + Each command in ``spec["commands"]`` becomes one FastAPI route at + its ``url_path``. The handler reads the incoming request, replays + it against ``base_url + url_path`` with the same method / query / + body / (filtered) headers, and streams the upstream response back. + + The route's OpenAPI metadata is populated from the spec via + ``openapi_extra`` so the launcher's ``widgets.json`` builder sees + the rich parameter / response_schema info the spec already + digested. Falls back to ``synthesize_openapi_from_spec`` for the + document-level fields that ``build_json`` reads. + + ``base_url_override`` swaps the spec's recorded ``base_url`` — + useful when a single spec is shared across staging/prod or when + the recorded URL points at an internal host the launcher needs + to reroute. + + ``extra_headers`` injects static headers on every upstream + request. Configured via ``[spec.headers]`` in the launcher TOML + so deployments can ship credentials (``Authorization``, + ``X-API-Key``, …) without baking them into the spec or relying on + the incoming request to carry them. Config-supplied headers + OVERRIDE matching incoming-request headers — the launcher's + static auth is the source of truth. + + ``spec_name`` is the human-readable label that replaces the + default ``["Custom"]`` source citation on every auto-generated + widget. ``args.parse_args`` passes the spec file's full name + (``Path(spec_path).name``) so dashboards built from + ``/etc/openbb/fertilizer.spec`` cite ``fertilizer.spec`` instead + of a generic ``Custom``. Stashed on + ``app.state.openbb_spec_source`` where + ``widgets_service.get_widgets_json`` reads it. + """ + base_url = (base_url_override or spec["base_url"]).rstrip("/") + api_title = "OpenBB Platform API (spec-driven proxy)" + + app = FastAPI( + title=api_title, + version=str(spec.get("api_version") or "1.0"), + ) + + # Stash on app.state so callers (the launcher's widgets builder, + # tests, custom middleware) can introspect what the proxy was + # built from. ``openbb_spec_source`` is the citation label + # ``widgets_service`` reads to override the auto-generated + # ``["Custom"]`` source on every spec-driven widget. The other + # fields are provenance metadata captured at spec-generation time: + # ``content_sha256`` (already integrity-verified by ``load_spec``) + # is exposed so observability layers can fingerprint the active + # spec, ``generator`` / ``generated_at`` / ``source_url`` give a + # forensic trail back to the openbb-cli invocation that produced it. + app.state.openbb_spec = spec + app.state.openbb_spec_base_url = base_url + app.state.openbb_spec_source = spec_name + app.state.openbb_spec_extra_headers = dict(extra_headers or {}) + app.state.openbb_spec_version = spec.get("version") + app.state.openbb_spec_generator = spec.get("generator") + app.state.openbb_spec_generated_at = spec.get("generated_at") + app.state.openbb_spec_source_url = spec.get("source_url") + app.state.openbb_spec_content_sha256 = spec.get("content_sha256") + app.state.openbb_spec_api_version = spec.get("api_version") + + def _make_handler( + method: str, + upstream_path: str, + wire_name_map: dict[str, str], + ): + """Closure-bind ``method`` + ``upstream_path`` + ``wire_name_map`` + for the route. + + ``wire_name_map`` is per-route because each command can declare + its own friendly→wire translations (Socrata datasets emit + ``limit``/``offset`` but the upstream API actually expects + ``$limit``/``$offset`` — the spec records the wire form on the + parameter so the proxy can rewrite the query string before + forwarding). + + The handler is intentionally untyped on its return — FastAPI's + OpenAPI generator inspects return annotations to build a + response model, and we don't want it to override the + spec-supplied ``response_schema`` we attach via + ``openapi_extra``. An un-annotated return + a typed ``Request`` + parameter gives FastAPI just enough to wire the route without + overwriting the spec metadata. + """ + + async def handler(request: Request): + # Substitute FastAPI path-parameter placeholders + # (``{axis}`` etc.) with the resolved values from + # ``request.path_params`` before forwarding upstream. + # The spec records ``url_path`` in its template form + # (``/breakdown/{axis}``), so without substitution the + # upstream sees the literal ``{axis}`` and rejects the + # request — caught against the BlackRock spec during + # the live integration test. + resolved_path = _substitute_path_params(upstream_path, request.path_params) + return await _proxy_request( + request, + method=method, + upstream_url=base_url + resolved_path, + extra_headers=app.state.openbb_spec_extra_headers, + wire_name_map=wire_name_map, + ) + + return handler + + for cmd_name, cmd in spec.get("commands", {}).items(): + url_path = cmd.get("url_path") + method = (cmd.get("method") or "get").lower() + if not url_path or method not in {"get", "post"}: + continue + + # Filter out malformed (non-dict) parameter entries once so + # both consumers below operate on a clean list — keeps the + # launcher tolerant of partial/corrupt specs without + # duplicating the type guard in two places. + cmd_params: list[dict[str, Any]] = [ + p for p in (cmd.get("parameters") or []) if isinstance(p, dict) + ] + + # Capture friendly→wire-name translations from this command's + # parameters. Socrata-style specs emit e.g. ``name="limit"``, + # ``wire_name="$limit"`` so the proxy can keep the friendly + # name in the widget UI (and OpenAPI) but send the actual + # upstream-expected name in the forwarded query string. + # Without this rewrite, Socrata responds + # ``Unrecognized arguments [limit]`` because it expects + # ``$limit``. + wire_name_map: dict[str, str] = {} + for p in cmd_params: + wire = p.get("wire_name") + friendly = p.get("name") + if wire and friendly and wire != friendly: + wire_name_map[friendly] = wire + + # Build the OpenAPI ``operation`` for this route. We pass it + # through ``openapi_extra`` so FastAPI doesn't strip the + # spec-supplied ``parameters`` / ``responses`` blocks. + operation_extra: dict[str, Any] = { + "parameters": [_spec_param_to_openapi(p) for p in cmd_params], + } + if response_schema := cmd.get("response_schema"): + operation_extra["responses"] = { + "200": {"content": {"application/json": {"schema": response_schema}}} + } + if method == "post" and (request_body_schema := cmd.get("request_body_schema")): + operation_extra["requestBody"] = { + "content": {"application/json": {"schema": request_body_schema}} + } + + app.add_api_route( + path=url_path, + endpoint=_make_handler(method, url_path, wire_name_map), + methods=[method.upper()], + name=cmd_name, + description=cmd.get("description") or "", + openapi_extra=operation_extra, + ) + + return app + + +def build_apps_from_specs( + specs_config: dict[str, dict[str, Any]], +) -> FastAPI: + """Build a parent FastAPI app that mounts each spec at its prefix. + + ``specs_config`` is a dict of ``{name: per_spec_kwargs}`` where each + per-spec entry carries: + + * ``spec`` — the loaded spec dict (already validated by + ``load_spec``). + * ``mount`` — the path prefix to mount the spec's app at, e.g. + ``/equity``. Defaults to ``"/" + name`` when not supplied. + * ``base_url_override`` / ``extra_headers`` / ``spec_name`` — same + semantics as the single-spec ``build_app_from_spec`` kwargs; + passed through verbatim so each spec keeps its own upstream + target, credential headers, and citation label. + * ``auth_hooks`` / ``middleware_hooks`` — optional lists of + ``module:async_callable`` references applied as + ``BaseHTTPMiddleware`` to that spec's sub-app. Auth runs as + the outermost layer, then the middleware list. The hooks are + naturally scoped to the sub-app's mount prefix because they're + registered on that sub-app instance, not the parent. + + Returns a parent ``FastAPI`` whose ``app.state.openbb_specs`` is + a dict of ``{mount: sub_app_state_snapshot}`` — gives the widgets + builder, telemetry layers, and tests a single attachment point + to introspect every mounted spec. + + Mount prefix collisions raise ``ValueError`` immediately so a + misconfigured deployment fails at startup, not on the first + routing miss. + """ + if not specs_config: + raise ValueError("build_apps_from_specs requires at least one spec entry.") + + parent = FastAPI(title="OpenBB Platform API (multi-spec proxy)") + + used_mounts: dict[str, str] = {} + state_by_mount: dict[str, dict[str, Any]] = {} + + for name, entry in specs_config.items(): + if not isinstance(entry, dict): + raise TypeError( + f"Spec entry {name!r} must be a dict; got {type(entry).__name__}." + ) + if "spec" not in entry: + raise ValueError( + f"Spec entry {name!r} is missing the required 'spec' field " + "(the loaded spec dict)." + ) + + # Mount prefix: explicit ``mount`` wins, otherwise derive from + # the dict key. Always forced to start with ``/``. + mount = entry.get("mount") or f"/{name}" + if not mount.startswith("/"): + mount = "/" + mount + # Normalize trailing slash off (FastAPI mount eats the trailing + # slash on its own when forming sub-app paths). + normalized_mount = mount.rstrip("/") or "/" + + if normalized_mount in used_mounts: + other = used_mounts[normalized_mount] + raise ValueError( + f"Spec mount collision: {name!r} and {other!r} both target " + f"{normalized_mount!r}. Each spec must mount at a distinct " + "prefix (set ``mount = ...`` to disambiguate)." + ) + used_mounts[normalized_mount] = name + + sub_app = build_app_from_spec( + entry["spec"], + base_url_override=entry.get("base_url_override"), + extra_headers=entry.get("extra_headers"), + spec_name=entry.get("spec_name") or name, + ) + + # Apply per-spec auth + middleware hooks. Auth runs as the + # outermost layer. ``BaseHTTPMiddleware(dispatch=fn)`` adapts + # the ``async def fn(request, call_next)`` shape into a + # Starlette middleware class without subclassing. + from starlette.middleware.base import BaseHTTPMiddleware + + from openbb_platform_api.app.middleware import ( + _resolve_entrypoint, + _validate_middleware_callable, + ) + + for label, hooks in ( + ("middleware", entry.get("middleware_hooks")), + ("auth", entry.get("auth_hooks")), + ): + # Reverse so the FIRST entry of each list ends up + # OUTERMOST after Starlette's stack-build (add_middleware + # inserts at index 0). Auth registered LAST = outermost + # overall, so it sees the request before any middleware + # hook does. + if not hooks: + continue + if not isinstance(hooks, list): + raise TypeError( + f"[spec.{name}.{label}] hooks must be a list of " + f"'module:attr' strings; got {type(hooks).__name__}." + ) + for hook_path in reversed(hooks): + if not isinstance(hook_path, str): + raise TypeError( + f"[spec.{name}.{label}] hook entries must be " + f"strings; got {type(hook_path).__name__} " + f"({hook_path!r})." + ) + fn = _resolve_entrypoint(hook_path) + _validate_middleware_callable(fn, hook_path) + sub_app.add_middleware(BaseHTTPMiddleware, dispatch=fn) + + parent.mount(normalized_mount, sub_app, name=name) + + # Snapshot the sub-app's ``app.state`` provenance for the + # parent's introspection dict — saves consumers from walking + # ``parent.routes`` to find each Mount and reach into its + # nested app.state. + state_by_mount[normalized_mount] = { + "name": name, + "spec_name": sub_app.state.openbb_spec_source, + "base_url": sub_app.state.openbb_spec_base_url, + "version": sub_app.state.openbb_spec_version, + "generator": sub_app.state.openbb_spec_generator, + "generated_at": sub_app.state.openbb_spec_generated_at, + "source_url": sub_app.state.openbb_spec_source_url, + "content_sha256": sub_app.state.openbb_spec_content_sha256, + "api_version": sub_app.state.openbb_spec_api_version, + } + + parent.state.openbb_specs = state_by_mount + return parent + + +def _substitute_path_params(template: str, params: dict[str, Any]) -> str: + """Replace FastAPI ``{name}`` path-param placeholders with resolved values. + + The spec records URL paths in their template form + (``/breakdown/{axis}``). FastAPI parses path-params from incoming + requests into ``request.path_params``; we splice them back into + the template so the upstream URL is fully resolved before the + proxy hop. Each value is URL-quoted with ``safe=""`` so segments + containing slashes, spaces, or other special characters don't + corrupt the path. Templates without ``{...}`` placeholders flow + through unchanged. + """ + from urllib.parse import quote + + resolved = template + for name, value in params.items(): + placeholder = "{" + str(name) + "}" + if placeholder in resolved: + resolved = resolved.replace(placeholder, quote(str(value), safe="")) + return resolved + + +def _rewrite_query_string( + query_string: str, wire_name_map: dict[str, str] | None +) -> str: + """Rewrite query-string param names per ``wire_name_map``. + + Preserves ordering and value encoding; only the keys are swapped. + Unmapped keys flow through verbatim. Returns the original string + when no map is given (or it's empty) so the common no-rewrite + path stays a single dict-emptiness check. + + Implemented over ``parse_qsl(keep_blank_values=True)`` so an empty + ``?foo=`` round-trips correctly. + """ + if not wire_name_map or not query_string: + return query_string + from urllib.parse import parse_qsl, urlencode + + pairs = parse_qsl(query_string, keep_blank_values=True) + rewritten = [(wire_name_map.get(k, k), v) for k, v in pairs] + return urlencode(rewritten) + + +def _trim_uniform_zero_time_columns(payload: Any) -> Any: + """Strip ``T00:00:00*`` from columns that are uniformly all-zero-time. + + Data-driven, not schema-driven: walks the actual response and + decides per-column based on what every row carries. + + * For each field across every row, if EVERY string value matches + ``T00:00:00*``, the time portion carries no information for that + column — strip it. + * If even one row has a real time-of-day for that field + (``T00:00:01``, ``T13:45:00``, anything with non-zero + hours/minutes/seconds/sub-seconds), the whole column is left + alone — midnight IS a legitimate time when the upstream actually + records it. + + Handles two payload shapes: + + * Top-level list of dicts (Socrata's typical format). + * Top-level dict with a list under one of ``_ENVELOPE_KEYS`` + (``results``, ``data``, ``rows``, ``records``) — the OBBject + envelope and similar wrapper shapes. + + Single records / unwrapped scalars pass through unchanged: with + only one row there's no cross-row signal, and per the contract + above, midnight on a lone record could be meaningful. + """ + if isinstance(payload, list): + return _trim_uniform_zero_time_in_records(payload) + if isinstance(payload, dict): + for key in _ENVELOPE_KEYS: + inner = payload.get(key) + if isinstance(inner, list): + trimmed = _trim_uniform_zero_time_in_records(inner) + if trimmed is inner: + return payload + return {**payload, key: trimmed} + return payload + + +def _trim_uniform_zero_time_in_records(records: list[Any]) -> list[Any]: + """Per-column unanimity check + strip on a list of dict records. + + Returns the input list unchanged (same identity) when no column + needs trimming so callers can detect no-op cheaply. + """ + dict_records = [r for r in records if isinstance(r, dict)] + if not dict_records: + return records + + # Pass 1: find columns where every present string value matches the + # zero-time pattern. ``None`` values don't disqualify a column — + # they just don't contribute. A column with ZERO matching values + # (e.g. all-None, or all numbers) is also excluded since there's + # nothing to trim. + all_keys: set[str] = set() + for r in dict_records: + all_keys.update(r.keys()) + + zero_time_columns: set[str] = set() + for key in all_keys: + present_values = [] + for r in dict_records: + v = r.get(key) + if v is None: + continue + present_values.append(v) + if not present_values: + continue + if all(isinstance(v, str) and _ZERO_TIME_RE.search(v) for v in present_values): + zero_time_columns.add(key) + + if not zero_time_columns: + return records + + # Pass 2: rebuild records, stripping the time suffix only on the + # identified columns. Non-dict entries (rare, but possible in + # heterogeneous responses) flow through untouched. + out: list[Any] = [] + for r in records: + if not isinstance(r, dict): + out.append(r) + continue + rewritten = { + k: ( + _ZERO_TIME_RE.sub("", v) + if k in zero_time_columns and isinstance(v, str) + else v + ) + for k, v in r.items() + } + out.append(rewritten) + return out + + +async def _proxy_request( + request: Request, + *, + method: str, + upstream_url: str, + extra_headers: dict[str, str] | None = None, + wire_name_map: dict[str, str] | None = None, +) -> Response: + """Forward ``request`` to ``upstream_url`` and stream the response. + + Uses ``aiohttp`` because it's already a runtime dep of + ``openbb-core`` (no new transitive deps for the launcher). One + ``ClientSession`` is created per request — fine for dashboard + traffic; revisit with a shared session pool if proxying becomes + high-frequency. + + ``extra_headers`` are merged in AFTER hop-by-hop filtering and + OVERRIDE any matching incoming-request header. This is the slot + the ``[spec.headers]`` TOML table fills, so deployments can inject + auth credentials regardless of what the dashboard widget sent. + + ``wire_name_map`` rewrites query-string param names from the + user-facing form to whatever the upstream expects. The Socrata + case — friendly ``limit``/``offset``, wire ``$limit``/``$offset`` — + is the motivating example; without the rewrite the upstream + responds ``Unrecognized arguments [limit]``. + + JSON responses are run through ``_trim_uniform_zero_time_columns`` + before being relayed — that's data-driven, not schema-driven: a + column whose every row carries ``T00:00:00*`` (across the whole + response) loses the meaningless time portion. Columns where ANY + row has a real time-of-day are left intact. + """ + import aiohttp + + body = await request.body() if method in {"post", "put", "patch"} else None + forwarded_headers = _filter_request_headers(dict(request.headers)) + if extra_headers: + # Static headers from config are the source of truth — they + # override matching incoming-request headers. Reasoning: + # ``[spec.headers]`` exists for credential injection, and + # we don't want a misbehaving client to leak its own auth + # value upstream by accident. + forwarded_headers.update(extra_headers) + query_string = _rewrite_query_string(request.url.query, wire_name_map) + + target = upstream_url + ("?" + query_string if query_string else "") + + timeout = aiohttp.ClientTimeout(total=60) + async with ( + aiohttp.ClientSession(timeout=timeout) as session, + session.request( + method.upper(), + target, + data=body, + headers=forwarded_headers, + ) as upstream, + ): + content = await upstream.read() + response_headers = _filter_response_headers(dict(upstream.headers)) + + # JSON responses get the per-column unanimity trim. The + # function returns the original payload (same identity) when + # nothing needs trimming, so we only re-encode + drop the + # upstream Content-Length when we actually mutated something. + if "application/json" in (upstream.headers.get("Content-Type") or ""): + try: + payload = json.loads(content) + except (json.JSONDecodeError, UnicodeDecodeError): + payload = None + if payload is not None: + trimmed = _trim_uniform_zero_time_columns(payload) + if trimmed is not payload: + content = json.dumps(trimmed).encode("utf-8") + response_headers.pop("Content-Length", None) + response_headers.pop("content-length", None) + + return Response( + content=content, + status_code=upstream.status, + headers=response_headers, + media_type=upstream.headers.get("Content-Type"), + ) diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/main.py b/openbb_platform/extensions/platform_api/openbb_platform_api/main.py index b570d12873a..cc9daf75073 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/main.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/main.py @@ -1,343 +1,95 @@ """OpenBB Platform API. Launch script and widgets builder for the OpenBB Workspace Custom Backend. + +The launcher logic moved to ``openbb_platform_api.app.app`` in the V5 +layout reorganization. This module is preserved as the canonical +entry point used by the ``openbb-api`` console script (declared in +``pyproject.toml``) and as the importable module path +``openbb_platform_api.main:app`` consumed by ``uvicorn.run``. + +Every public name from ``app.app`` is re-exported here so legacy +imports (and tests that ``patch("openbb_platform_api.main.", …)``) +keep working while new code imports from the V5 module directly. + +Boot order — three short-circuits / hooks run BEFORE importing +``app.app`` so the launcher's heavy top-level imports stay +deferred until they're actually needed: + +1. ``--help`` / ``-h`` — print the launcher's help text and exit + without importing anything beyond ``args`` (stdlib-only at module + scope). +2. Layered TOML config bootstrap — discover ``openbb.toml`` in the + cascade (or ``--config-file `` / ``$OPENBB_API_CONFIG``), + apply the ``[env]`` table to ``os.environ``, and push the + ``[system]`` / ``[user]`` sections onto the singleton services. + Containers can ship one TOML and avoid shell exports entirely. +3. Re-export ``app.app``'s public names (``main``, ``app``, + ``launch_api``, route handlers) so legacy + ``openbb_platform_api.main:`` patches still resolve. """ -import json -import logging -import os import sys -from pathlib import Path - -import uvicorn -from fastapi.responses import HTMLResponse, JSONResponse -from openbb_core.api.rest_api import app -from openbb_core.app.service.system_service import SystemService -from openbb_core.env import Env - -from .utils.api import ( - FIRST_RUN, - check_port, - get_user_settings, - get_widgets_json, - parse_args, -) -from .utils.merge_agents import get_additional_agents, has_additional_agents -from .utils.merge_apps import get_additional_apps, has_additional_apps - -logger = logging.getLogger("openbb_platform_api") -logger.setLevel(logging.INFO) -handler = logging.StreamHandler() -handler.setLevel(logging.INFO) -formatter = logging.Formatter("\n%(message)s\n") -handler.setFormatter(formatter) -logger.addHandler(handler) -logger.setLevel(logging.INFO) -# Adds the OpenBB Environment variables to the script process. -Env() -HOME = os.environ.get("HOME") or os.environ.get("USERPROFILE") +def _short_circuit_on_help() -> None: + """Print help and exit when ``--help`` / ``-h`` is on argv. -if not HOME: - raise ValueError("HOME or USERPROFILE environment variable not set.") + Must run before importing ``app.app`` — that module's top-level + imports pull in the entire openbb-core stack, which is wasted work + when the user just wants to read the flags. + """ + argv = sys.argv[1:] + if "--help" in argv or "-h" in argv: + from openbb_platform_api.app.args import LAUNCH_SCRIPT_DESCRIPTION -CURRENT_USER_SETTINGS = os.path.join(HOME, ".openbb_platform", "user_settings.json") -# Widget filtering is optional and can be used to exclude widgets from the widgets.json file -# Alternatively, you can supply a JSON-encoded list of API paths to ignore. -WIDGET_SETTINGS = os.path.join(HOME, ".openbb_platform", "widget_settings.json") -kwargs = parse_args() -_app = kwargs.pop("app", None) - -if _app: - app = _app - -WIDGETS_PATH = kwargs.pop("widgets-json", None) -APPS_PATH = kwargs.pop("apps-json", None) -EDITABLE = kwargs.pop("editable", None) is True or WIDGETS_PATH is not None -DEFAULT_APPS_PATH = ( - Path(__file__).absolute().parent.joinpath("assets").joinpath("default_apps.json") -) -AGENTS_PATH = kwargs.pop("agents-json", None) -build = kwargs.pop("build", True) -build = False if kwargs.pop("no-build", None) else build -dont_filter = kwargs.pop("no-filter", False) -widget_exclude_filter: list = kwargs.pop("exclude", []) -uvicorn_settings = ( - SystemService().system_settings.python_settings.model_dump().get("uvicorn", {}) -) -obb_headers = {"X-Backend-Type": "OpenBB Platform"} - -for key, value in uvicorn_settings.items(): - if key not in kwargs and key != "app" and value is not None: - kwargs[key] = value - -if not dont_filter and os.path.exists(WIDGET_SETTINGS): - with open(WIDGET_SETTINGS, encoding="utf-8") as widget_settings_file: - try: - widget_exclude_filter_json = json.load(widget_settings_file).get( - "exclude", [] - ) - if isinstance(widget_exclude_filter_json, list): - widget_exclude_filter.extend(widget_exclude_filter_json) - except json.JSONDecodeError as e: - logger.info("Error loading widget filter settings -> %s", e) + print(LAUNCH_SCRIPT_DESCRIPTION) # noqa: T201 + sys.exit(0) -def check_for_platform_extensions(fastapi_app, widgets_to_exclude) -> list: - """Check for data-processing Platform extensions and add them to the widget exclude filter.""" - to_check_for = ["econometrics", "quantitative", "technical"] - openapi_tags = fastapi_app.openapi_tags or [] - tags: list = [] - for tag in openapi_tags: - if any(mod in tag.get("name", "") for mod in to_check_for): - tags.append(tag.get("name", "")) +def _bootstrap_config() -> None: + """Run the launcher TOML cascade BEFORE the launcher imports. - if tags and (any(f"openbb_{mod}" in sys.modules for mod in to_check_for)): - api_prefix = SystemService().system_settings.api_settings.prefix - for tag in tags: - if f"openbb_{tag}" in sys.modules: - # If the module is loaded, we can safely add it to the exclude filter. - widgets_to_exclude.append(f"{api_prefix}/{tag}/*") + Picks up ``--config-file `` / ``$OPENBB_API_CONFIG`` / + ``$OPENBB_CONFIG``, merges every layer (pyproject → user-global → + project → explicit → ``.env`` → real env vars), and applies the + ``[env]`` table to ``os.environ`` so the very first + ``openbb_core.*`` module load sees the injected values. Also + pushes the merged ``[system]`` / ``[user]`` sections onto the + singleton services so any TOML-supplied OpenBB settings take + effect for downstream code. - return widgets_to_exclude + Best-effort by design: if the TOML chain produces no overrides + (no file present anywhere) the call is a no-op. Raises if a + discovered file is malformed — silent corruption is worse than + a clear startup error. + """ + from openbb_platform_api.app.config import bootstrap_launcher_config + + bootstrap_launcher_config() -widget_exclude_filter = check_for_platform_extensions(app, widget_exclude_filter) -openapi = app.openapi() -current_settings = get_user_settings(CURRENT_USER_SETTINGS) -widgets_json = get_widgets_json( - build, openapi, widget_exclude_filter, EDITABLE, WIDGETS_PATH, app -) -APPS_PATH = ( - APPS_PATH - if APPS_PATH - else ( - current_settings.get("preferences", {}).get( - "data_directory", HOME + "/OpenBBUserData" - ) - + "/workspace_apps.json" - ) +_short_circuit_on_help() +_bootstrap_config() + + +from openbb_platform_api.app import app as _app_module # noqa: E402 + +# Re-export every non-dunder name from the launcher's module-level +# scope so legacy attribute-access patterns continue to resolve. The +# names point at the same objects ``app.app`` defines, so patches via +# the legacy or the V5 path target the same binding. +globals().update( + { + name: value + for name, value in _app_module.__dict__.items() + if not name.startswith("__") + } ) -@app.get("/") -async def root(): - """Serve the landing page HTML content.""" - html_path = Path(__file__).parent / "assets" / "landing_page.html" - with open(html_path, encoding="utf-8") as f: - html_content = f.read() - return HTMLResponse(content=html_content) - - -# Check if the app has already defined widgets.json at the root. -has_root_widgets = any(getattr(d, "path", "") == "/widgets.json" for d in app.routes) - -if not has_root_widgets: - # We assume that if an app already has /widgets.json at the app root, - # we can leave it alone. Otherwise, use our endpoint to serve and/or generate. - @app.get("/widgets.json") - async def get_widgets(): - """Widgets configuration file for the OpenBB Workspace.""" - # This allows us to serve an edited widgets.json file without reloading the server. - global FIRST_RUN # noqa PLW0603 # pylint: disable=global-statement - if FIRST_RUN is True: - FIRST_RUN = False - return JSONResponse(content=widgets_json, headers=obb_headers) - if EDITABLE: - return JSONResponse( - content=get_widgets_json( - False, openapi, widget_exclude_filter, EDITABLE, WIDGETS_PATH, app - ), - headers=obb_headers, - ) - return JSONResponse(content=widgets_json, headers=obb_headers) - -else: - # Populate the local name `get_widgets` with the endpoint function of the existing - # root /widgets.json route so callers (e.g. get_apps_json) can await it. - root_route = next( - (r for r in app.routes if getattr(r, "path", "") == "/widgets.json"), None - ) - if root_route and getattr(root_route, "endpoint", None): - get_widgets = root_route.endpoint # type: ignore - else: - # Fallback mechanism - async def get_widgets(): - """Return the generated widgets.json""" - return JSONResponse(content=widgets_json, headers=obb_headers) - - -# Check if the app has already defined apps.json at the root. -has_root_apps = any(getattr(d, "path", "") == "/apps.json" for d in app.routes) - -if not has_root_apps: - - @app.get("/apps.json") - async def get_apps_json(): - """Get the apps.json file.""" - new_templates: list = [] - default_templates: list = [] - widgets = await get_widgets() - - if not os.path.exists(APPS_PATH): - apps_dir = os.path.dirname(APPS_PATH) - if not os.path.exists(apps_dir): - os.makedirs(apps_dir, exist_ok=True) - # Write an empty file for the user to add exported apps from Workspace to. - with open(APPS_PATH, "w", encoding="utf-8") as templates_file: - templates_file.write(json.dumps([])) - - if os.path.exists(DEFAULT_APPS_PATH): - with open(DEFAULT_APPS_PATH, encoding="utf-8") as f: - default_templates = json.load(f) - - if has_additional_apps(app): - additional_apps = await get_additional_apps(app) - - if additional_apps: - for apps in additional_apps.values(): - if not apps: - continue - if apps and isinstance(apps, list): - default_templates.extend(apps) - elif apps and not isinstance(apps, list): - logger.error( - "TypeError: Invalid apps.json format. Expected a list[dict] got %s instead -> %s", - type(apps), - str(apps), - ) - - if os.path.exists(APPS_PATH): - with open(APPS_PATH, encoding="utf-8") as templates_file: - templates = json.load(templates_file) - - if isinstance(templates, dict): - templates = [templates] - - templates.extend(default_templates) - - for template in templates: - if _id := template.get("id"): - if _id in widgets and template not in new_templates: - new_templates.append(template) - continue - elif template.get("layout") or template.get("tabs"): - if _tabs := template.get("tabs"): - for v in _tabs.values(): - if v.get("layout", []) and all( - item.get("i", "").startswith("rich_note") - or item.get("i") in widgets_json - for item in v.get("layout") - ): - new_templates.append(template) - break - elif ( - template.get("layout") - and all( - item.get("i", "").startswith("rich_note") - or item.get("i") in widgets_json - for item in template["layout"] - ) - and template not in new_templates - ): - new_templates.append(template) - - if new_templates: - return JSONResponse(content=new_templates, headers=obb_headers) - - return JSONResponse(content=[], headers=obb_headers) - - -if AGENTS_PATH: - - @app.get("/agents.json") - async def get_agents(): - """Get the agents.json file.""" - if os.path.exists(AGENTS_PATH): - with open(AGENTS_PATH, encoding="utf-8") as f: - agents = json.load(f) - return JSONResponse(content=agents, headers=obb_headers) - return JSONResponse(content={}, headers=obb_headers) - - -# Check if the app has already defined agents.json at the root. -has_root_agents = any(getattr(d, "path", "") == "/agents.json" for d in app.routes) - -if not has_root_agents and has_additional_agents(app): - - @app.get("/agents.json") - async def get_agents_json(): # type: ignore - """Get the agents.json file.""" - new_agents: dict = {} - additional_agents = await get_additional_agents(app) - if additional_agents: - for path_agents in additional_agents.values(): - for k, v in path_agents.items(): - new_agents[k] = v - return JSONResponse(content=new_agents, headers=obb_headers) - -else: - - @app.get("/agents.json") - async def get_agents_json(): - """Get an empty agents.json file.""" - return {} - - -def launch_api(**_kwargs): # noqa PRL0912 - """Start the API server.""" - host = _kwargs.pop("host", os.getenv("OPENBB_API_HOST", "127.0.0.1")) - if not host: - logger.info( - "OPENBB_API_HOST is set incorrectly. It should be an IP address or hostname." - ) - host = input("Enter the host IP address or hostname: ") - if not host: - host = "127.0.0.1" - - port = _kwargs.pop("port", os.getenv("OPENBB_API_PORT", "6900")) - +if __name__ == "__main__": # pragma: no cover — manual launcher entry try: - port = int(port) - except ValueError: - logger.info("OPENBB_API_PORT is set incorrectly. It should be an port number.") - port = input("Enter the port number: ") - try: - port = int(port) - except ValueError: - logger.info("Invalid port number. Defaulting to 6900.") - port = 6900 - if port < 1025: - port = 6900 - logger.info("Invalid port number, must be above 1024. Defaulting to 6900.") - - free_port = check_port(host, port) - - if free_port != port: - logger.info("Port %d is already in use. Using port %d.", port, free_port) - port = free_port - - if "use_colors" not in _kwargs: - _kwargs["use_colors"] = "win" not in sys.platform or os.name != "nt" - - package_name = __package__ - _msg = ( - "\nTo access this data from OpenBB Workspace, use the link displayed after the application startup completes." - "\nChrome is the recommended browser. Other browsers may conflict or require additional configuration." - f"\n{f'Documentation is available at {app.docs_url}.' if app.docs_url else ''}" - ) - logger.info(_msg) - uvicorn.run(f"{package_name}.main:app", host=host, port=port, **_kwargs) - - -def main(): - """Launch the API.""" - launch_api(**kwargs) - - -if __name__ == "__main__": - try: - main() + main() # ty: ignore[unresolved-reference] # noqa: F821 — provided via globals().update except KeyboardInterrupt: sys.exit(0) diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/models/__init__.py b/openbb_platform/extensions/platform_api/openbb_platform_api/models/__init__.py new file mode 100644 index 00000000000..ebae95e1f69 --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/models/__init__.py @@ -0,0 +1,20 @@ +"""Pydantic models for the OpenBB Platform API extension. + +Re-exports the public surface so callers can write +``from openbb_platform_api.models import OmniWidgetInput`` rather than +having to know the exact module each model lives in. +""" + +from openbb_platform_api.models.query import OmniWidgetInput +from openbb_platform_api.models.response import ( + MetricResponseModel, + OmniWidgetResponseModel, + PdfResponseModel, +) + +__all__ = [ + "MetricResponseModel", + "OmniWidgetInput", + "OmniWidgetResponseModel", + "PdfResponseModel", +] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/models/query.py b/openbb_platform/extensions/platform_api/openbb_platform_api/models/query.py new file mode 100644 index 00000000000..a683a2affda --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/models/query.py @@ -0,0 +1,67 @@ +"""OpenBB Workspace Query Models.""" + +from typing import Any + +from openbb_core.provider.abstract.data import Data +from pydantic import AliasGenerator, ConfigDict, Field, field_validator +from pydantic.alias_generators import to_snake + + +class OmniWidgetInput(Data): + """Input for OmniWidget.""" + + model_config = ConfigDict( + extra="allow", + alias_generator=AliasGenerator(to_snake), + title="OmniWidget Input Data for POST Request.", + json_schema_extra={ + "x-widget_config": { + "$.type": "omni", + } + }, + ) + + prompt: Any | None = Field( + default=None, + description="The prompt text or JSON object sent from Workspace.", + json_schema_extra={ + "x-widget_config": { + "type": "text", + "value": "", + "description": "Input prompt value for the OmniWidget.", + "show": False, + } + }, + ) + + @field_validator("prompt", mode="before") + @classmethod + def _validate_prompt(cls, v): + """Validate and parse the prompt field.""" + + import json + import re + + if not v or v == "": + return None + + # Non-string scalars (int / dict / list / etc.) bypass the + # JSON-loads dance entirely — Workspace can pass structured + # payloads that are already typed; they don't need parsing. + if not isinstance(v, (str, bytes, bytearray)): + return v + + prompt: Any = "" + + try: + prompt = json.loads(v) + except json.JSONDecodeError: + # Try to fix common JSON errors like trailing commas + try: + # Remove trailing commas in objects and arrays + cleaned_prompt = re.sub(r",(\s*[}\]])", r"\1", v) + prompt = json.loads(cleaned_prompt) + except json.JSONDecodeError: + prompt = v + + return prompt if prompt != "" else None diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/models/response.py b/openbb_platform/extensions/platform_api/openbb_platform_api/models/response.py new file mode 100644 index 00000000000..11b403623d2 --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/models/response.py @@ -0,0 +1,328 @@ +"""OpenBB Workspace Response Models.""" + +from typing import Any + +from openbb_core.provider.abstract.data import Data +from pydantic import ConfigDict, Field, model_validator + + +def _is_pandas_dataframe(obj: Any) -> bool: + """Detect a pandas ``DataFrame`` without importing pandas. + + The launcher's runtime should not pull pandas in for an + ``isinstance(content, pd.DataFrame)`` check — pandas is an + optional extra of ``openbb-core[pandas]`` and most spec-driven + launches never need it. We walk ``type(obj).__mro__`` looking + for a base class named ``DataFrame`` whose module is ``pandas`` + (or a sub-package thereof), which catches both actual pandas + instances and any user-defined ``DataFrame`` subclass — same + semantics as ``isinstance(obj, pd.DataFrame)`` but without the + import. The module pin keeps this from matching unrelated + ``DataFrame`` classes from other libraries. + """ + for cls in type(obj).__mro__: + if cls.__name__ != "DataFrame": + continue + module = getattr(cls, "__module__", "") or "" + if module == "pandas" or module.startswith("pandas."): + return True + return False + + +class MetricResponseModel(Data): + """ + Metric Widget Response Model. + + Supply a label, value, and optional delta. + + Fields + ------ + label : str + The label to display in the metric widget. + value : int, float, or str + The value to display in the metric widget. + delta : int, float, or str + The, optional, delta value to display in the metric widget. + + Returns + ------- + object + Object with the label, value, and optional delta value. + """ + + model_config = ConfigDict( + extra="ignore", + json_schema_extra={ + "title": "Metric Widget Response Model", + "x-widget_config": { + "$.type": "metric", + "$.category": "Metric", + "$.searchCategory": "Metric", + }, + }, + ) + + label: str = Field( + description="The label to display in the metric widget.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + value: int | float | str = Field( + description="The value to display in the metric widget.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + delta: int | float | str | None = Field( + default=None, + description="The delta value to display in the metric widget.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + + +class PdfResponseModel(Data): + """ + PDF Widget Response Model. + + Supply the url or content, and an optional filename. + + Fields + ------ + filename : str + The filename of the PDF content. + content : bytes + The PDF content to display in the PDF widget. + url : str + The URL reference to the PDF + + Returns + ------- + object + Object with the PDF content serialized as a Base64 encoded string. + + Raises + ------ + ValueError + If neither 'content' or 'url_reference' is provided, or an invalid URL reference is provided. + """ + + model_config = ConfigDict( + extra="ignore", + json_schema_extra={ + "x-widget_config": { + "$.type": "pdf", + "$.refetchInterval": False, + "$.category": "File", + "$.subCategory": "PDF", + "$.searchCategory": "File", + } + }, + ) + + filename: str | None = Field( + default="", + description="The filename of the PDF content.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + content: str | bytes | None = Field( + default=None, + description="The PDF content to display in the PDF widget.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + url: str | None = Field( + default=None, + description="The URL reference to the PDF content.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + data_format: dict | None = Field( + default=None, + description="Leave this field empty. This is populated by the model_validator.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + + @model_validator(mode="after") + @classmethod + def validate_model(cls, values) -> "PdfResponseModel": + """Validate the PDF content.""" + + import base64 # noqa + from io import BytesIO + + content = getattr(values, "content", None) + file_reference = getattr(values, "url", None) + filename = getattr(values, "filename", "") + + if not content and not file_reference: + raise ValueError("Either 'content' or 'url' must be provided.") + + if file_reference and "://" not in file_reference: + raise ValueError("Invalid URL reference provided") + + if content: + pdf = ( + base64.b64encode(BytesIO(content).getvalue()).decode("utf-8") + if isinstance(content, bytes) + else content + ) + # Only stamp the encoded payload back when content was + # actually provided — the url-only path leaves + # ``values.content`` at its existing value (typically + # ``None``) instead of touching an unset local. + values.content = pdf + if file_reference: + values.url = file_reference + elif hasattr(values, "url"): + del values.url + values.data_format = {"data_type": "pdf", "filename": filename} + + return values + + +class OmniWidgetResponseModel(Data): + """Omni Widget Response Model. + + Supply the content, and optionally the `parse_as` field. + + Fields + ------ + content : Any + The content to display in the Omni widget. + parse_as : Optional[str] + The type of content to parse as. One of "table", "chart", or "text". + Attempts to set this automatically based on the content type, but can be overridden. + + Returns + ------- + object + Object that conforms to the validated output requirements of the API. + + Example + ------- + >>> from openbb_platform_api.main import app + >>> @app.get("/omni_widget", response_model=OmniWidgetResponseModel) + >>> async def get_omni_widget(): + >>> return {"content": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]} + """ + + model_config = ConfigDict( + extra="ignore", + json_schema_extra={ + "x-widget_config": { + "$.type": "omni", + } + }, + ) + + content: Any = Field( + description="The content to display in the Omni widget.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + parse_as: str | None = Field( + default=None, + description="The type of content to parse as. One of 'table', 'chart', or 'text'.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + data_format: dict | None = Field( + default=None, + description="Leave this field empty. This is populated by the model_validator.", + json_schema_extra={"x-widget_config": {"exclude": True}}, + ) + + @model_validator(mode="after") + @classmethod + def validate_model(cls, values) -> "OmniWidgetResponseModel": + """Validate the Omni widget content.""" + + import json # noqa + import re + + content = getattr(values, "content", None) + + if content is None: + raise ValueError("Content cannot be empty.") + + parse_as = getattr(values, "parse_as", None) + + if parse_as and parse_as not in ("table", "chart", "text"): + raise ValueError( + "Invalid parse_as value. Must be one of 'table', 'chart', or 'text'." + ) + + # If parameter was supplied, assume the data is formatted correctly. + # ``content is not None`` (instead of bare ``if content``) avoids + # tripping pandas' ambiguous-truthiness ``__bool__`` on DataFrame + # / Series payloads. + if content is not None and parse_as: + data_format = { + "data_type": "object", + "parse_as": parse_as, + } + values.data_format = data_format + del values.parse_as + + return values + + if content.__class__.__name__ == "Figure": + values.parse_as = "chart" + try: + content = content.to_json() + except Exception as e: + raise ValueError("Failed to convert chart to JSON") from e + values.content = content + elif isinstance(content, dict) and "layout" in content and "data" in content: + values.parse_as = "chart" + elif isinstance(content, list) and all( + isinstance(item, dict) for item in content + ): + values.parse_as = "table" + elif _is_pandas_dataframe(content): + # Detect via class metadata so the launcher doesn't need + # pandas in its runtime dep tree — pandas is an optional + # extra of ``openbb-core`` and most spec-driven launches + # never touch it. + values.parse_as = "table" + try: + content = json.loads(content.to_json(orient="records")) + except Exception as e: + raise ValueError("Failed to convert DataFrame to JSON") from e + values.content = content + elif isinstance(content, dict) and all( + isinstance(v, list) for v in content.values() + ): + values.parse_as = "table" + try: + # Stdlib transpose: ``{"a": [1, 2], "b": [3, 4]}`` → + # ``[{"a": 1, "b": 3}, {"a": 2, "b": 4}]``. Avoids + # pulling pandas into the launcher's hot path; the + # zip-based form is faster anyway for the typical + # widget-payload shapes. + keys = list(content.keys()) + rows = list(zip(*content.values(), strict=True)) + content = [dict(zip(keys, row, strict=True)) for row in rows] + except (TypeError, ValueError) as e: + raise ValueError( + "Failed to convert dictionary of lists to list of records" + ) from e + values.content = content + elif isinstance(content, str) and content.strip(): # noqa: PLR0916 + try: + content = json.loads(content) + except json.JSONDecodeError: + # Remove trailing commas in objects and arrays + try: + cleaned_content = re.sub(r",(\s*[}\]])", r"\1", content) + content = json.loads(cleaned_content) + except json.JSONDecodeError: + pass + + values.parse_as = "table" if isinstance(content, (list, dict)) else "text" + values.content = content + else: + values.parse_as = "text" + + data_format = { + "data_type": "object", + "parse_as": parse_as if parse_as else values.parse_as, + } + values.data_format = data_format + + del values.parse_as + + return values diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/query_models.py b/openbb_platform/extensions/platform_api/openbb_platform_api/query_models.py index 9e74d0f8e54..562446c62d6 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/query_models.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/query_models.py @@ -1,61 +1,11 @@ -"""OpenBB Workspace Query Models.""" +"""Backwards-compatibility shim. -from typing import Any +The Workspace query models moved to ``openbb_platform_api.models.query`` +in the V5 layout reorganization. This module preserves the legacy import +path for external callers — new code should import from the +``models`` subpackage directly. +""" -from openbb_core.provider.abstract.data import Data -from pydantic import AliasGenerator, ConfigDict, Field, field_validator -from pydantic.alias_generators import to_snake +from openbb_platform_api.models.query import OmniWidgetInput - -class OmniWidgetInput(Data): - """Input for OmniWidget.""" - - model_config = ConfigDict( - extra="allow", - alias_generator=AliasGenerator(to_snake), - title="OmniWidget Input Data for POST Request.", - json_schema_extra={ - "x-widget_config": { - "$.type": "omni", - } - }, - ) - - prompt: Any | None = Field( - default=None, - description="The prompt text or JSON object sent from Workspace.", - json_schema_extra={ - "x-widget_config": { - "type": "text", - "value": "", - "description": "Input prompt value for the OmniWidget.", - "show": False, - } - }, - ) - - @field_validator("prompt", mode="before") - @classmethod - def _validate_prompt(cls, v): - """Validate and parse the prompt field.""" - # pylint: disable=import-outside-toplevel - import json - import re - - if not v or v == "": - return None - - prompt = "" - - try: - prompt = json.loads(v) - except json.JSONDecodeError: - # Try to fix common JSON errors like trailing commas - try: - # Remove trailing commas in objects and arrays - cleaned_prompt = re.sub(r",(\s*[}\]])", r"\1", prompt) - prompt = json.loads(cleaned_prompt) - except json.JSONDecodeError: - prompt = v - - return prompt if prompt != "" else None +__all__ = ["OmniWidgetInput"] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/response_models.py b/openbb_platform/extensions/platform_api/openbb_platform_api/response_models.py index 9520485dcf6..f9f71a82432 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/response_models.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/response_models.py @@ -1,290 +1,20 @@ -"""OpenBB Workspace Response Models.""" +"""Backwards-compatibility shim. -from typing import Any +The Workspace response models moved to +``openbb_platform_api.models.response`` in the V5 layout +reorganization. This module preserves the legacy import path for +external callers — new code should import from the ``models`` +subpackage directly. +""" -from openbb_core.provider.abstract.data import Data -from pydantic import ConfigDict, Field, model_validator +from openbb_platform_api.models.response import ( + MetricResponseModel, + OmniWidgetResponseModel, + PdfResponseModel, +) - -class MetricResponseModel(Data): - """ - Metric Widget Response Model. - - Supply a label, value, and optional delta. - - Fields - ------ - label : str - The label to display in the metric widget. - value : int, float, or str - The value to display in the metric widget. - delta : int, float, or str - The, optional, delta value to display in the metric widget. - - Returns - ------- - object - Object with the label, value, and optional delta value. - """ - - model_config = ConfigDict( - extra="ignore", - json_schema_extra={ - "title": "Metric Widget Response Model", - "x-widget_config": { - "$.type": "metric", - "$.category": "Metric", - "$.searchCategory": "Metric", - }, - }, - ) - - label: str = Field( - description="The label to display in the metric widget.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - value: int | float | str = Field( - description="The value to display in the metric widget.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - delta: int | float | str | None = Field( - default=None, - description="The delta value to display in the metric widget.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - - -class PdfResponseModel(Data): - """ - PDF Widget Response Model. - - Supply the url or content, and an optional filename. - - Fields - ------ - filename : str - The filename of the PDF content. - content : bytes - The PDF content to display in the PDF widget. - url : str - The URL reference to the PDF - - Returns - ------- - object - Object with the PDF content serialized as a Base64 encoded string. - - Raises - ------ - ValueError - If neither 'content' or 'url_reference' is provided, or an invalid URL reference is provided. - """ - - model_config = ConfigDict( - extra="ignore", - json_schema_extra={ - "x-widget_config": { - "$.type": "pdf", - "$.refetchInterval": False, - "$.category": "File", - "$.subCategory": "PDF", - "$.searchCategory": "File", - } - }, - ) - - filename: str | None = Field( - default="", - description="The filename of the PDF content.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - content: str | bytes | None = Field( - default=None, - description="The PDF content to display in the PDF widget.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - url: str | None = Field( - default=None, - description="The URL reference to the PDF content.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - data_format: dict | None = Field( - default=None, - description="Leave this field empty. This is populated by the model_validator.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - - @model_validator(mode="after") - @classmethod - def validate_model(cls, values) -> "PdfResponseModel": - """Validate the PDF content.""" - # pylint: disable=import-outside-toplevel - import base64 # noqa - from io import BytesIO - - content = getattr(values, "content", None) - file_reference = getattr(values, "url", None) - filename = getattr(values, "filename", "") - - if not content and not file_reference: - raise ValueError("Either 'content' or 'url' must be provided.") - - if file_reference and "://" not in file_reference: - raise ValueError("Invalid URL reference provided") - - if content: - pdf = ( - base64.b64encode(BytesIO(content).getvalue()).decode("utf-8") - if isinstance(content, bytes) - else content - ) - - values.content = pdf - if file_reference: - values.url = file_reference - elif hasattr(values, "url"): - del values.url - values.data_format = {"data_type": "pdf", "filename": filename} - - return values - - -class OmniWidgetResponseModel(Data): - """Omni Widget Response Model. - - Supply the content, and optionally the `parse_as` field. - - Fields - ------ - content : Any - The content to display in the Omni widget. - parse_as : Optional[str] - The type of content to parse as. One of "table", "chart", or "text". - Attempts to set this automatically based on the content type, but can be overridden. - - Returns - ------- - object - Object that conforms to the validated output requirements of the API. - - Example - ------- - >>> from openbb_platform_api.main import app - >>> @app.get("/omni_widget", response_model=OmniWidgetResponseModel) - >>> async def get_omni_widget(): - >>> return {"content": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]} - """ - - model_config = ConfigDict( - extra="ignore", - json_schema_extra={ - "x-widget_config": { - "$.type": "omni", - } - }, - ) - - content: Any = Field( - description="The content to display in the Omni widget.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - parse_as: str | None = Field( - default=None, - description="The type of content to parse as. One of 'table', 'chart', or 'text'.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - data_format: dict | None = Field( - default=None, - description="Leave this field empty. This is populated by the model_validator.", - json_schema_extra={"x-widget_config": {"exclude": True}}, - ) - - @model_validator(mode="after") - @classmethod - def validate_model(cls, values) -> "OmniWidgetResponseModel": - """Validate the Omni widget content.""" - # pylint: disable=import-outside-toplevel - import json # noqa - import re - import pandas as pd - - content = getattr(values, "content", None) - - if content is None: - raise ValueError("Content cannot be empty.") - - parse_as = getattr(values, "parse_as", None) - - if parse_as and parse_as not in ("table", "chart", "text"): - raise ValueError( - "Invalid parse_as value. Must be one of 'table', 'chart', or 'text'." - ) - - # If parameter was supplied, assume the data is formatted correctly. - if content and parse_as: - data_format = { - "data_type": "object", - "parse_as": parse_as, - } - values.data_format = data_format - del values.parse_as - - return values - - if content.__class__.__name__ == "Figure": - values.parse_as = "chart" - try: - content = content.to_json() - except Exception as e: - raise ValueError("Failed to convert chart to JSON") from e - values.content = content - elif isinstance(content, dict) and "layout" in content and "data" in content: - values.parse_as = "chart" - elif isinstance(content, list) and all( - isinstance(item, dict) for item in content - ): - values.parse_as = "table" - elif isinstance(content, pd.DataFrame): - values.parse_as = "table" - try: - content = json.loads(content.to_json(orient="records")) - except Exception as e: - raise ValueError("Failed to convert DataFrame to JSON") from e - values.content = content - elif isinstance(content, dict) and all( - isinstance(v, list) for v in content.values() - ): - values.parse_as = "table" - try: - df = pd.DataFrame(content) - content = json.loads(df.to_json(orient="records")) - except Exception as e: - raise ValueError( - "Failed to convert dictionary of lists to list of records" - ) from e - values.content = content - elif isinstance(content, str) and content.strip(): # pylint: disable=R0916 - try: - content = json.loads(content) - except json.JSONDecodeError: - # Remove trailing commas in objects and arrays - try: - cleaned_content = re.sub(r",(\s*[}\]])", r"\1", content) - content = json.loads(cleaned_content) - except json.JSONDecodeError: - pass - - values.parse_as = "table" if isinstance(content, (list, dict)) else "text" - values.content = content - else: - values.parse_as = "text" - - data_format = { - "data_type": "object", - "parse_as": parse_as if parse_as else values.parse_as, - } - values.data_format = data_format - - del values.parse_as - - return values +__all__ = [ + "MetricResponseModel", + "OmniWidgetResponseModel", + "PdfResponseModel", +] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/service/__init__.py b/openbb_platform/extensions/platform_api/openbb_platform_api/service/__init__.py new file mode 100644 index 00000000000..235e0a7ecae --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/service/__init__.py @@ -0,0 +1,31 @@ +"""Service-layer modules for the OpenBB Platform API extension. + +Each service owns one of the launcher's data-orchestration flows +(widgets / apps / agents). The FastAPI route handlers in +``app/app.py`` thin-wrap these services so the request shape and the +catalogue-building logic stay separable. +""" + +from openbb_platform_api.service.agents_service import ( + get_additional_agents, + has_additional_agents, +) +from openbb_platform_api.service.apps_service import ( + get_additional_apps, + has_additional_apps, +) +from openbb_platform_api.service.widgets_service import ( + FIRST_RUN, + PATH_WIDGETS, + get_widgets_json, +) + +__all__ = [ + "FIRST_RUN", + "PATH_WIDGETS", + "get_additional_agents", + "get_additional_apps", + "get_widgets_json", + "has_additional_agents", + "has_additional_apps", +] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/service/agents_service.py b/openbb_platform/extensions/platform_api/openbb_platform_api/service/agents_service.py new file mode 100644 index 00000000000..a49c6e9d2de --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/service/agents_service.py @@ -0,0 +1,69 @@ +"""Discover and merge router-attached ``agents.json`` endpoints. + +Mirror of the ``apps_service`` flow: the launcher walks the FastAPI app's +routes for any whose path ends with ``agents.json`` (other than the +canonical root ``/agents.json``), calls each one, and folds the returned +agent definitions into the response served at ``/agents.json``. +""" + +from fastapi import FastAPI +from openbb_core.app.route_iter import iter_api_routes + + +def has_additional_agents(app: FastAPI) -> bool: + """Return ``True`` when the app has any non-root ``*agents.json`` route.""" + for route in iter_api_routes(app): + path = getattr(route, "path", "") + if path == "/agents.json": + continue + if path.endswith("agents.json"): + return True + return False + + +async def get_additional_agents(app: FastAPI) -> dict: + """Collect ``agents`` dicts from every non-root ``*agents.json`` route. + + Returns ``{prefix: agents_dict}`` where ``prefix`` is the route's + path stripped of the trailing ``agents.json``. Each agent's + relative ``endpoints`` are rewritten to absolute paths under the + route's prefix so Workspace can call them directly. + """ + if not has_additional_agents(app): + return {} + + path_agents: dict = {} + + # ``iter_api_routes`` resolves routes attached via ``include_router``, + # which FastAPI 0.137+ wraps in ``_IncludedRouter`` rather than + # flattening into ``app.routes``. ``original_route`` is the leaf + # ``APIRoute`` whose ``endpoint`` we invoke. + for route in iter_api_routes(app): + path = getattr(route, "path", "") + if path in {"/agents.json", ""} or not path.endswith("agents.json"): + continue + + leaf = getattr(route, "original_route", route) + endpoint = getattr(leaf, "endpoint", None) + if endpoint is None: + continue + + agents = await endpoint() + + if not isinstance(agents, dict): + continue + + path = path.replace("agents.json", "") + for k, v in agents.copy().items(): + endpoints = v.get("endpoints", {}) if isinstance(v, dict) else {} + for name, endpoint_path in endpoints.items(): + if ( + isinstance(endpoint_path, str) + and endpoint_path.startswith("/") + and not endpoint_path.startswith(path) + ): + agents[k]["endpoints"][name] = path + endpoint_path[1:] + + path_agents[path] = agents + + return path_agents diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/service/apps_service.py b/openbb_platform/extensions/platform_api/openbb_platform_api/service/apps_service.py new file mode 100644 index 00000000000..05b462b2b60 --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/service/apps_service.py @@ -0,0 +1,59 @@ +"""Discover and merge router-attached ``apps.json`` endpoints. + +User code can ship its own ``apps.json``-shaped endpoint by registering +a route whose path *ends* with ``apps.json`` (anything other than the +canonical root ``/apps.json``). The launcher walks those routes, calls +each one, and folds the returned templates into the catalogue served at +``/apps.json``. +""" + +from fastapi import FastAPI +from openbb_core.app.route_iter import iter_api_routes + + +def has_additional_apps(app: FastAPI) -> bool: + """Return ``True`` when the app has any non-root ``*apps.json`` route.""" + for route in iter_api_routes(app): + path = getattr(route, "path", "") + if path == "/apps.json": + continue + if path.endswith("apps.json"): + return True + return False + + +async def get_additional_apps(app: FastAPI) -> dict: + """Collect ``apps`` lists from every non-root ``*apps.json`` route. + + Returns ``{prefix: [app, ...]}`` where ``prefix`` is the route's + path stripped of the trailing ``apps.json``. Routes whose endpoint + returns a non-list payload are skipped — the merger expects each + fragment to be a list of app templates. + """ + if not has_additional_apps(app): + return {} + + path_apps: dict = {} + + # ``iter_api_routes`` resolves routes attached via ``include_router``, + # which FastAPI 0.137+ wraps in ``_IncludedRouter`` rather than + # flattening into ``app.routes``. ``original_route`` is the leaf + # ``APIRoute`` whose ``endpoint`` we invoke. + for route in iter_api_routes(app): + path = getattr(route, "path", "") + if path in {"/apps.json", ""} or not path.endswith("apps.json"): + continue + + leaf = getattr(route, "original_route", route) + endpoint = getattr(leaf, "endpoint", None) + if endpoint is None: + continue + + apps = await endpoint() + + if not isinstance(apps, list): + continue + + path_apps[path.replace("apps.json", "")] = apps + + return path_apps diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/service/widgets_service.py b/openbb_platform/extensions/platform_api/openbb_platform_api/service/widgets_service.py new file mode 100644 index 00000000000..2d471ab6f11 --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/service/widgets_service.py @@ -0,0 +1,214 @@ +"""Build / read / serve the Workspace ``widgets.json`` catalogue. + +Higher-level orchestration around ``utils/widgets.build_json``: handles +the editable-on-disk flow (build → diff → prompt-to-merge → write) and +the ephemeral in-memory build path. Holds the launcher's mutable state +(``FIRST_RUN``, ``PATH_WIDGETS``) that the FastAPI route handlers in +``app/app.py`` consult. +""" + +import json +import logging +import os +import sys +from pathlib import Path + +from deepdiff import DeepDiff +from fastapi import FastAPI + +logger = logging.getLogger("openbb_platform_api") + +# Mutable launcher state. ``FIRST_RUN`` flips to ``False`` on the very +# first ``/widgets.json`` request so the cached build can be served +# verbatim instead of recomputed; ``PATH_WIDGETS`` accumulates router- +# defined widget definitions so they merge into the auto-generated set. +PATH_WIDGETS: dict = {} +FIRST_RUN: bool = True + + +def get_widgets_json( # noqa: PLR0912 + _build: bool, + _openapi: dict, + widget_exclude_filter: list, + editable: bool = False, + widgets_path: str | None = None, + app: FastAPI | None = None, +) -> dict: + """Return the widgets.json contents the Workspace expects. + + Two modes: + + * **Editable** (``editable=True``): widgets.json lives on disk at + ``widgets_path`` (or ``/assets/widgets.json``). On + ``_build=True`` the function rebuilds from the current openapi, + diffs against the file, and (when there are changes) prompts the + user interactively to overwrite / append-only / ignore. The + chosen result is written back so subsequent loads are cheap. + * **Ephemeral** (``editable=False``): rebuild every call from the + live openapi; merge in any router-attached widgets that + ``has_additional_widgets(app)`` discovered. Nothing touches disk. + """ + + from openbb_core.provider.utils.helpers import run_async + + from openbb_platform_api.utils.merge_widgets import ( + get_and_fix_widget_paths, + has_additional_widgets, + ) + from openbb_platform_api.utils.widgets import build_json + + global PATH_WIDGETS # noqa: PLW0603 + + if ( + FIRST_RUN is True + and app + and isinstance(app, FastAPI) + and has_additional_widgets(app) + ): + PATH_WIDGETS = run_async(get_and_fix_widget_paths, app) + + if PATH_WIDGETS and ( + to_exclude := [p + "*" for p in PATH_WIDGETS if p.endswith("/")] + ): + # Router-attached widgets stake their own subtree; the auto + # builder must skip those paths so we don't end up with two + # widgets per route. + widget_exclude_filter.extend(to_exclude) + + if editable is True: + if widgets_path is None: + python_path = Path(sys.executable) + parent_path = ( + python_path.parent if os.name == "nt" else python_path.parents[1] + ) + widgets_json_path = parent_path.joinpath("assets", "widgets.json").resolve() + else: + widgets_json_path = Path(widgets_path).absolute().resolve() + + json_exists = widgets_json_path.exists() + + if not json_exists: + widgets_json_path.parent.mkdir(parents=True, exist_ok=True) + _build = True + json_exists = widgets_json_path.exists() + + existing_widgets_json: dict = {} + + if json_exists: + with open(widgets_json_path, encoding="utf-8") as f: + existing_widgets_json = json.load(f) + + _widgets_json = ( + existing_widgets_json + if _build is False + else build_json(_openapi, widget_exclude_filter) + ) + + if _build: + diff = DeepDiff(existing_widgets_json, _widgets_json, ignore_order=True) + merge_prompt = None + if diff and json_exists: + print("Differences found:", diff) # noqa: T201 + merge_prompt = input( + "\nDo you want to overwrite the existing widgets.json configuration?" + "\nEnter 'n' to append existing with only new entries, or 'i' to ignore all changes. (y/n/i): " + ) + if merge_prompt.lower().startswith("n"): + _widgets_json.update(existing_widgets_json) + elif merge_prompt.lower().startswith("i"): + _widgets_json = existing_widgets_json + + if merge_prompt is None or not merge_prompt.lower().startswith("i"): + try: + with open(widgets_json_path, "w", encoding="utf-8") as f: + json.dump(_widgets_json, f, ensure_ascii=False, indent=4) + except Exception as e: + print( # noqa: T201 + f"Error writing widgets.json: {e}. Loading from memory instead." + ) + _widgets_json = ( + existing_widgets_json + if existing_widgets_json + else build_json(_openapi, widget_exclude_filter) + ) + else: + _widgets_json = build_json(_openapi, widget_exclude_filter) + + if PATH_WIDGETS: + for k in PATH_WIDGETS: + if k in widget_exclude_filter or k + "*" in widget_exclude_filter: + continue + + for widget_id, widget in PATH_WIDGETS[k].items(): + if widget_id not in widget_exclude_filter: + _widgets_json[widget_id] = widget + + # Spec-driven launches stash a citation label on + # ``app.state.openbb_spec_source`` (the spec file's name). Use it + # to override every v4-platform default that ``build_json`` + # hardcoded — the ``["Custom"]`` source citation AND the + # ``mcp_tool.mcp_server: "Open Data Platform"`` namespace. + # Author-supplied overrides via ``widget_config`` are preserved. + # Runs on both the initial startup build and the editable rebuild + # path so the override is consistent across all surfaces. + _apply_spec_defaults_override(_widgets_json, app) + + return _widgets_json + + +def _apply_spec_defaults_override(widgets_json: dict, app: FastAPI | None) -> None: + """Replace v4-platform default values with spec-driven equivalents. + + When ``app.state.openbb_spec_source`` is set (the spec file's + name, populated by ``args.parse_args`` for spec-driven launches), + this helper rewrites three v4 launcher defaults on every widget + that still carries them: + + * ``source: ["Custom"]`` → ``source: [spec_source]`` + * ``mcp_tool.mcp_server: "Open Data Platform"`` → + ``mcp_tool.mcp_server: spec_source`` + * ``mcp_tool.tool_id: ""`` → + ``mcp_tool.tool_id: "__"`` + + The first two are citation labels — straightforward replace. + The third is namespacing: MCP tool IDs need to be globally + unique across MCP servers a client connects to, so prefixing with + the spec stem (filename minus extension) prevents collisions when + multiple spec-driven launchers are wired up to the same client. + The ``__`` separator is used because widget IDs can contain dots + and underscores; ``__`` is unambiguous as a namespace boundary. + + All three defaults come from ``utils/widgets.build_json`` which + was written for the v4 in-process Platform launch and assumes + the canonical OpenBB MCP namespace. Spec-driven backends are + their own MCP namespace — they should advertise themselves + under the file they were generated from, not under the + platform's. + + Author-supplied overrides (anything other than the literals + above) are preserved untouched. The tool_id rewrite is gated on + the v4 mcp_server default also being in place — if the author + overrode ``mcp_server`` they own the whole ``mcp_tool`` block. + No-op for non-spec-driven launches. + """ + if app is None: + return + spec_source = getattr(app.state, "openbb_spec_source", None) + if not spec_source: + return + # ``Path().stem`` strips the last extension only, so a filename + # like ``my.app.spec`` becomes ``my.app`` (not ``my``). Matches + # what most operators would call the namespace. + spec_stem = Path(spec_source).stem or spec_source + for widget in widgets_json.values(): + if widget.get("source") == ["Custom"]: + widget["source"] = [spec_source] + mcp_tool = widget.get("mcp_tool") + if ( + isinstance(mcp_tool, dict) + and mcp_tool.get("mcp_server") == "Open Data Platform" + ): + mcp_tool["mcp_server"] = spec_source + tool_id = mcp_tool.get("tool_id") + if isinstance(tool_id, str) and tool_id: + mcp_tool["tool_id"] = f"{spec_stem}__{tool_id}" diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/__init__.py b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/__init__.py index aa3b0ef651f..ff1a56be678 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/__init__.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/__init__.py @@ -1 +1,14 @@ -"""OpenBB Platform API Utils.""" +"""Pure utility helpers for the OpenBB Platform API extension. + +* ``network`` — port probing and user-settings loading. +* ``openapi`` — OpenAPI schema → widget-config translation. +* ``widgets`` — widgets.json builder + provider-aware schema modifier. +* ``merge_widgets`` — discover & merge router-attached widget endpoints. +""" + +from openbb_platform_api.utils.network import check_port, get_user_settings + +__all__ = [ + "check_port", + "get_user_settings", +] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/api.py b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/api.py index 0f30dec4224..8c995c00701 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/api.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/api.py @@ -1,420 +1,83 @@ -"""API Utils.""" - -import json -import logging -import os -import socket -import sys -from pathlib import Path - -from deepdiff import DeepDiff -from fastapi import FastAPI - -logger = logging.getLogger("openbb_platform_api") -PATH_WIDGETS: dict = {} -FIRST_RUN: bool = True -LAUNCH_SCRIPT_DESCRIPTION = """ -Serve the OpenBB Platform API. - - -Launcher specific arguments: - - --app Absolute path to the Python file with the target FastAPI instance. Default is the installed OpenBB Platform API. - --name Name of the FastAPI instance in the app file. Default is 'app'. - --factory Flag to indicate if the app name is a factory function. Default is 'false'. - --editable Flag to make widgets.json an editable file that can be modified during runtime. Default is 'false'. - --build If the file already exists, changes prompt action to overwrite/append/ignore. Only valid when --editable true. - --no-build Do not build the widgets.json file. Use this flag to load an existing widgets.json file without checking for updates. - --exclude JSON encoded list of API paths to exclude from widgets.json. Disable entire routes with '*' - e.g. '["/api/v1/*"]'. - --no-filter Do not filter out widgets in widget_settings.json file. - --widgets-json Absolute/relative path to use as the widgets.json file. Default is ~/envs/{env}/assets/widgets.json, when --editable is 'true'. - --apps-json Absolute/relative path to use as the apps.json file. Default is ~/OpenBBUserData/workspace_apps.json. - --agents-json Absolute/relative path to use as the agents.json file. Including this will add the /agents endpoint to the API. - - -The FastAPI app instance can be imported to another script, modified, and launched by using the --app argument. - -If the path to the app file is not absolute, it will be resolved relative to the current working directory. - -Imported with: - ->>> from openbb_platform_api.main import app ->>> ->>> @app.get() ->>> async def hello(input: str = "Hello") -> str: ->>> '''Widget description created by doctring.''' ->>> return f"You entered: {input}" - -Launched with: - ->>> openbb-api --app /path/to/some_file.py - -The app instance name can be defined by either the --name argument, or by referencing the module name, for example: - ->>> openbb-api --app some_file.py:main --factory - -A name must be set when using the factory flag. - -All other arguments will be passed to uvicorn. Here are the most common ones: - - --host TEXT Host IP address or hostname. - [default: 127.0.0.1] - --port INTEGER Port number. - [default: 6900] - --ssl-keyfile TEXT SSL key file. - --ssl-certfile TEXT SSL certificate file. - --ssl-keyfile-password TEXT SSL keyfile password. - --ssl-version INTEGER SSL version to use. - (see stdlib ssl module's) - [default: 17] - --ssl-cert-reqs INTEGER Whether client certificate is required. - (see stdlib ssl module's) - [default: 0] - --ssl-ca-certs TEXT CA certificates file. - --ssl-ciphers TEXT Ciphers to use. - (see stdlib ssl module's) - [default: TLSv1] - -Run `uvicorn --help` to get the full list of arguments. -""" # noqa: E501 - - -def check_port(host, port): - """Check if the port number is free.""" - port = int(port) - not_free = True - while not_free: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - res = sock.connect_ex((host, port)) - if res != 0: - not_free = False - else: - port += 1 - return port - - -def get_user_settings(current_user_settings: str) -> dict: - """Login to the OpenBB Platform.""" - if Path(current_user_settings).exists(): - with open(current_user_settings, encoding="utf-8") as f: - user_settings = json.load(f) - else: - user_settings = { - "credentials": {}, - "preferences": {}, - "defaults": {"commands": {}}, - } - return user_settings - - -def get_widgets_json( - _build: bool, - _openapi, - widget_exclude_filter: list, - editable: bool = False, - widgets_path: str | None = None, - app: FastAPI | None = None, -): - """Generate and serve the widgets.json for the OpenBB Platform API.""" - # pylint: disable=import-outside-toplevel - from openbb_core.provider.utils.helpers import run_async # noqa - from .merge_widgets import get_and_fix_widget_paths, has_additional_widgets - from .widgets import build_json - - global PATH_WIDGETS # noqa pylint: disable=W0603 - - if ( - FIRST_RUN is True - and app - and isinstance(app, FastAPI) - and has_additional_widgets(app) - ): - PATH_WIDGETS = run_async(get_and_fix_widget_paths, app) - - if PATH_WIDGETS and ( - to_exclude := [p + "*" for p in PATH_WIDGETS if p.endswith("/")] - ): - # Exclude explicit router paths from the automated generation. - # These widgets have been added by a router, so we assume they don't want - # the factory for those paths. - widget_exclude_filter.extend(to_exclude) - - if editable is True: - if widgets_path is None: - python_path = Path(sys.executable) - parent_path = ( - python_path.parent if os.name == "nt" else python_path.parents[1] - ) - widgets_json_path = parent_path.joinpath("assets", "widgets.json").resolve() - else: - widgets_json_path = Path(widgets_path).absolute().resolve() - - json_exists = widgets_json_path.exists() - - if not json_exists: - widgets_json_path.parent.mkdir(parents=True, exist_ok=True) - _build = True - json_exists = widgets_json_path.exists() - - existing_widgets_json: dict = {} - - if json_exists: - with open(widgets_json_path, encoding="utf-8") as f: - existing_widgets_json = json.load(f) - - _widgets_json = ( - existing_widgets_json - if _build is False - else build_json(_openapi, widget_exclude_filter) - ) - - if _build: - diff = DeepDiff(existing_widgets_json, _widgets_json, ignore_order=True) - merge_prompt = None - if diff and json_exists: - print("Differences found:", diff) # noqa: T201 - merge_prompt = input( - "\nDo you want to overwrite the existing widgets.json configuration?" - "\nEnter 'n' to append existing with only new entries, or 'i' to ignore all changes. (y/n/i): " - ) - if merge_prompt.lower().startswith("n"): - _widgets_json.update(existing_widgets_json) - elif merge_prompt.lower().startswith("i"): - _widgets_json = existing_widgets_json - - if merge_prompt is None or not merge_prompt.lower().startswith("i"): - try: - with open(widgets_json_path, "w", encoding="utf-8") as f: - json.dump(_widgets_json, f, ensure_ascii=False, indent=4) - except Exception as e: # pylint: disable=broad-exception-caught - print( # noqa - f"Error writing widgets.json: {e}. Loading from memory instead." - ) - _widgets_json = ( - existing_widgets_json - if existing_widgets_json - else build_json(_openapi, widget_exclude_filter) - ) - else: - _widgets_json = build_json(_openapi, widget_exclude_filter) - - if PATH_WIDGETS: - for k in PATH_WIDGETS: - if k in widget_exclude_filter or k + "*" in widget_exclude_filter: - continue - - for widget_id, widget in PATH_WIDGETS[k].items(): - if widget_id not in widget_exclude_filter: - _widgets_json[widget_id] = widget - - return _widgets_json - - -def import_app(app_path: str, name: str = "app", factory: bool = False): - """Import the FastAPI app instance from a local file or module.""" - # pylint: disable=import-outside-toplevel - from fastapi.middleware.cors import CORSMiddleware # noqa - from importlib import import_module, util - from openbb_core.api.app_loader import AppLoader - from openbb_core.api.rest_api import system - - def _is_module_colon_notation(app_path: str) -> bool: - """Check if the path uses module:name notation vs a Windows path.""" - if ":" not in app_path: - return False - # Windows absolute path check (e.g., C:\path or D:/path) - if len(app_path) >= 2 and app_path[1] == ":" and app_path[0].isalpha(): - # Could still have colon notation: C:\path\file.py:app - parts = app_path.split(":") - return len(parts) > 2 # More than just drive letter colon - return True - - def _load_module_from_file_path(file_path: str): - spec_name = os.path.basename(file_path).split(".")[0] - spec = util.spec_from_file_location(spec_name, file_path) - - if spec is None: - raise RuntimeError(f"Failed to load the file specs for '{file_path}'") - - module = util.module_from_spec(spec) # type: ignore - sys.modules[spec_name] = module # type: ignore - spec.loader.exec_module(module) # type: ignore - return module - - if _is_module_colon_notation(app_path): - module_path, name = app_path.rsplit(":", 1) - try: # First try to import as a module - module = import_module(module_path) - except ImportError: # If module import fails, try to load as a local file - if not module_path.endswith(".py"): - module_path += ".py" - - if not Path(module_path).is_absolute(): - cwd = Path.cwd() - file_path = str(cwd.joinpath(module_path).resolve()) - else: - file_path = module_path - - if not Path(file_path).exists(): - raise FileNotFoundError( # pylint: disable=raise-missing-from - f"Error: Neither module '{module_path}' could be imported nor file '{file_path}' exists" - ) - - module = _load_module_from_file_path(file_path) - - # Case 2: File path (e.g., "main.py" or "my_app/main.py") - else: - if not Path(app_path).is_absolute(): - cwd = Path.cwd() - app_path = str(cwd.joinpath(app_path).resolve()) - - if not Path(app_path).exists(): - raise FileNotFoundError(f"Error: The app file '{app_path}' does not exist") - - module = _load_module_from_file_path(app_path) - - if not hasattr(module, name): - raise AttributeError( - f"Error: The app file '{app_path}' does not contain an '{name}' instance" - ) - - app_or_factory = getattr(module, name) - - # Here we use the same approach as uvicorn to handle factory functions. - # This prevents us from relying on explicit type annotations. - # See: https://github.com/encode/uvicorn/blob/master/uvicorn/config.py - try: - app = app_or_factory() - if not factory: - print( # noqa: T201 - "\n\n[WARNING] " - "App factory detected. Using it, but please consider setting the --factory flag explicitly.\n" - ) - except TypeError: - if factory: - raise TypeError( # pylint: disable=raise-missing-from - f"Error: The {name} instance in '{app_path}' appears not to be a callable factory function" - ) - app = app_or_factory - - if not isinstance(app, FastAPI): - raise TypeError( - f"Error: The {name} instance in '{app_path}' is not an instance of FastAPI" - ) - - app.add_middleware( - CORSMiddleware, - allow_origins=system.api_settings.cors.allow_origins, - allow_methods=system.api_settings.cors.allow_methods, - allow_headers=system.api_settings.cors.allow_headers, - ) - - AppLoader.add_exception_handlers(app) - - return app - - -def parse_args(): # noqa: PLR0912 # pylint: disable=too-many-branches - """Parse the launch script command line arguments.""" - args = sys.argv[1:].copy() - cwd = Path.cwd() - _kwargs: dict = {} - for i, arg in enumerate(args): - if arg == "--help": - print(LAUNCH_SCRIPT_DESCRIPTION) # noqa: T201 - sys.exit(0) - if arg.startswith("--"): - key = arg[2:] - if key in ["no-use-colors", "use-colors"]: - _kwargs["use_colors"] = key == "use-colors" - elif i + 1 < len(args) and not args[i + 1].startswith("--"): - value = args[i + 1] - if isinstance(value, str) and value.lower() in ["false", "true"]: - _kwargs[key] = value.lower() == "true" - elif key == "exclude": - _kwargs[key] = json.loads(value) - else: - _kwargs[key] = value - else: - _kwargs[key] = True - - if _kwargs.get("app"): - _app_path = _kwargs.pop("app", None) - _name = _kwargs.pop("name", "app") - _factory = _kwargs.pop("factory", False) - - if ":" in _app_path: - _app_instance_name = _app_path.split(":")[-1] - _name = _app_instance_name if _app_instance_name else _name - - if _factory and not _name: - raise ValueError( - "Error: The factory function name must be provided to the --name parameter when the factory flag is set." - ) - _kwargs["app"] = import_app(_app_path, _name, _factory) - - if isinstance(_kwargs.get("exclude"), str): - _kwargs["exclude"] = [_kwargs["exclude"]] - - if _kwargs.get("agents-json") or _kwargs.get("copilots-path"): - _agents_path = _kwargs.pop("agents-json", None) or _kwargs.pop( - "copilots-path", None - ) - - if not str(_agents_path).endswith(".json"): - _agents_path = ( - f"{_agents_path}{'' if _agents_path.endswith('/') else '/'}agents.json" - ) - - if str(_agents_path).startswith("./"): - _agents_path = str(cwd.joinpath(_agents_path).resolve()) - - _kwargs["agents-json"] = _agents_path - - if _kwargs.get("widgets-json") or _kwargs.get("widgets-path"): - _widgets_path = _kwargs.pop("widgets-json", None) or _kwargs.pop( - "widgets-path", None - ) - - # If it's a file (endswith .json), use as is; else treat as directory and append widgets.json - if str(_widgets_path).endswith(".json"): - widgets_file_path = _widgets_path - else: - widgets_file_path = f"{_widgets_path}{'' if str(_widgets_path).endswith('/') else '/'}widgets.json" - - # Resolve relative paths to absolute - if str(widgets_file_path).startswith("./"): - widgets_file_path = str(cwd.joinpath(widgets_file_path).resolve()) - - _kwargs["widgets-json"] = widgets_file_path - - if _kwargs.get("widgets-json"): - _kwargs["editable"] = True - # If the file already exists, we assume that it is already built. - if os.path.exists(_kwargs["widgets-json"]): - _kwargs["no-build"] = True - - # Handle apps-json and templates-path in the same way as widgets-path - if _kwargs.get("apps-json") or _kwargs.get("templates-path"): - _apps_path = _kwargs.pop("apps-json", None) or _kwargs.pop( - "templates-path", None - ) - - # If it's a file (endswith .json), use as is; else treat as directory and append apps.json - if str(_apps_path).endswith(".json"): - apps_file_path = _apps_path - else: - # Check if "workspace_apps.json" exists in the given path - possible_workspace_file = f"{_apps_path}{'' if str(_apps_path).endswith('/') else '/'}workspace_apps.json" - if os.path.isfile(possible_workspace_file): - apps_file_path = possible_workspace_file - else: - apps_file_path = f"{_apps_path}{'' if str(_apps_path).endswith('/') else '/'}apps.json" - - # Resolve relative paths to absolute - if str(apps_file_path).startswith("./"): - apps_file_path = str(cwd.joinpath(apps_file_path).resolve()) - - _kwargs["apps-json"] = apps_file_path - - return _kwargs +"""Backwards-compatibility shim. + +The launcher's helpers were split across purpose-specific modules in +the V5 layout reorganization: + +* ``parse_args`` and ``LAUNCH_SCRIPT_DESCRIPTION`` moved to + ``openbb_platform_api.app.args``. +* ``import_app`` moved to ``openbb_platform_api.app.bootstrap``. +* ``check_port`` and ``get_user_settings`` moved to + ``openbb_platform_api.utils.network``. +* ``get_widgets_json`` plus its module-level ``FIRST_RUN`` / + ``PATH_WIDGETS`` state moved to + ``openbb_platform_api.service.widgets_service``. + +This module preserves every legacy import path so external callers +keep working — new code should import from the dedicated modules. +""" + +from importlib import import_module + +#: ``shim_attr -> (source_module, source_attr)`` mapping. Every legacy +#: name lives here; ``__getattr__`` consults this on each access. +_LAZY_TARGETS: dict[str, tuple[str, str]] = { + "LAUNCH_SCRIPT_DESCRIPTION": ( + "openbb_platform_api.app.args", + "LAUNCH_SCRIPT_DESCRIPTION", + ), + "parse_args": ("openbb_platform_api.app.args", "parse_args"), + "import_app": ("openbb_platform_api.app.bootstrap", "import_app"), + "get_widgets_json": ( + "openbb_platform_api.service.widgets_service", + "get_widgets_json", + ), + "logger": ("openbb_platform_api.service.widgets_service", "logger"), + "FIRST_RUN": ("openbb_platform_api.service.widgets_service", "FIRST_RUN"), + "PATH_WIDGETS": ( + "openbb_platform_api.service.widgets_service", + "PATH_WIDGETS", + ), + "check_port": ("openbb_platform_api.utils.network", "check_port"), + "get_user_settings": ( + "openbb_platform_api.utils.network", + "get_user_settings", + ), +} + + +def __getattr__(name): + """Resolve a legacy name against its current source module. + + Each access re-imports the source module and returns the fresh + attribute. ``importlib.import_module`` returns the cached module + when nothing's been mutated, so the cost is a dict lookup in the + common case — and a real re-import in the test-pollution case + (which is exactly when the lazy resolution matters). + """ + target = _LAZY_TARGETS.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + module_path, attr = target + return getattr(import_module(module_path), attr) + + +def __dir__() -> list[str]: + """Surface the lazy names for ``dir()`` and IDE autocomplete.""" + return sorted(_LAZY_TARGETS) + + +# ``noqa: F822`` because every name is provided lazily through +# ``__getattr__`` — ruff's static check doesn't see the indirection, +# but ``import openbb_platform_api.utils.api as api; api.parse_args`` +# resolves correctly at runtime. +__all__ = [ # noqa: F822 + "FIRST_RUN", + "LAUNCH_SCRIPT_DESCRIPTION", + "PATH_WIDGETS", + "check_port", + "get_user_settings", + "get_widgets_json", + "import_app", + "logger", + "parse_args", +] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_agents.py b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_agents.py index fb38d0bfec6..afd6983b46a 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_agents.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_agents.py @@ -1,55 +1,14 @@ -"""Helper module for merging multiple Fast API endpoints returning agents.json""" +"""Backwards-compatibility shim. -from fastapi import FastAPI -from fastapi.routing import APIRoute +The router-attached ``agents.json`` discovery / merge flow moved to +``openbb_platform_api.service.agents_service`` in the V5 layout +reorganization. This module preserves the legacy import path; new +code should import from the ``service`` subpackage directly. +""" +from openbb_platform_api.service.agents_service import ( + get_additional_agents, + has_additional_agents, +) -def has_additional_agents(app: FastAPI) -> bool: - """Check for the existence of additional agents.json endpoints.""" - for route in app.routes: - if not isinstance(route, APIRoute): - continue - path = getattr(route, "path", "") - if path == "/agents.json": - continue - if path.endswith("agents.json"): - return True - return False - - -async def get_additional_agents(app: FastAPI) -> dict: - """Collect agents.json from non-root endpoints.""" - # pylint: disable=import-outside-toplevel - from starlette.routing import BaseRoute - - if not has_additional_agents(app): - return {} - - agents_routes: list[BaseRoute] = [] - for d in app.routes: - d_path = getattr(d, "path", "") - if d_path not in {"/agents.json", ""} and d_path.endswith("agents.json"): - agents_routes.append(d) - - path_agents: dict = {} - - for r in agents_routes: - if not getattr(r, "endpoint", None) or getattr(r, "path", "") == "/agents.json": - continue - - agents = await r.endpoint() # type: ignore - - if not isinstance(agents, dict): - continue - - path = getattr(r, "path", "").replace("agents.json", "") - for k, v in agents.copy().items(): - endpoints = v.get("endpoints", {}) - for name, endpoint in endpoints.items(): - if endpoint.startswith("/") and not endpoints.startwith(path): - new_endpoint = path + endpoint[1:] - agents[k][v]["endpoints"][name] = new_endpoint - - path_agents[path] = agents - - return path_agents +__all__ = ["get_additional_agents", "has_additional_agents"] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_apps.py b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_apps.py index c8c82cd5b0c..694b3754d06 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_apps.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_apps.py @@ -1,46 +1,14 @@ -"""Helper module for merging multiple Fast API endpoints returning apps.json""" +"""Backwards-compatibility shim. -from fastapi import FastAPI -from fastapi.routing import APIRoute -from starlette.routing import BaseRoute +The router-attached ``apps.json`` discovery / merge flow moved to +``openbb_platform_api.service.apps_service`` in the V5 layout +reorganization. This module preserves the legacy import path; new +code should import from the ``service`` subpackage directly. +""" +from openbb_platform_api.service.apps_service import ( + get_additional_apps, + has_additional_apps, +) -def has_additional_apps(app: FastAPI) -> bool: - """Check for the existence of additional apps.json endpoints.""" - for route in app.routes: - if not isinstance(route, APIRoute): - continue - path = getattr(route, "path", "") - if path == "/apps.json": - continue - if path.endswith("apps.json"): - return True - return False - - -async def get_additional_apps(app: FastAPI) -> dict: - """Collect apps.json from non-root endpoints.""" - if not has_additional_apps(app): - return {} - - apps_routes: list[BaseRoute] = [] - for d in app.routes: - d_path = getattr(d, "path", "") - if d_path not in {"/apps.json", ""} and d_path.endswith("apps.json"): - apps_routes.append(d) - - path_apps: dict = {} - - for r in apps_routes: - if not getattr(r, "endpoint", None) or getattr(r, "path", "") == "/apps.json": - continue - - apps = await r.endpoint() # type: ignore - - if not isinstance(apps, list): - continue - - path = getattr(r, "path", "") - path_apps[path.replace("apps.json", "")] = apps - - return path_apps +__all__ = ["get_additional_apps", "has_additional_apps"] diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_widgets.py b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_widgets.py index 1c2b4ba5ea0..151a5bce61f 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_widgets.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/merge_widgets.py @@ -1,15 +1,12 @@ """Helper module for merging multiple Fast API endpoints returning widgets.json""" from fastapi import FastAPI -from fastapi.routing import APIRoute -from starlette.routing import BaseRoute +from openbb_core.app.route_iter import iter_api_routes def has_additional_widgets(app: FastAPI) -> bool: """Check for the existence of additional widgets.json endpoints.""" - for route in app.routes: - if not isinstance(route, APIRoute): - continue + for route in iter_api_routes(app): path = getattr(route, "path", "") if path == "/widgets.json": continue @@ -23,27 +20,27 @@ async def get_additional_widgets(app: FastAPI) -> dict: if not has_additional_widgets(app): return {} - widget_routes: list[BaseRoute] = [] - for d in app.routes: - d_path = getattr(d, "path", "") - if d_path not in {"/widgets.json", ""} and d_path.endswith("widgets.json"): - widget_routes.append(d) - path_widgets: dict = {} - for r in widget_routes: - if ( - not getattr(r, "endpoint", None) - or getattr(r, "path", "") == "/widgets.json" - ): + # ``iter_api_routes`` resolves routes attached via ``include_router``, + # which FastAPI 0.137+ wraps in ``_IncludedRouter`` rather than + # flattening into ``app.routes``. ``original_route`` is the leaf + # ``APIRoute`` whose ``endpoint`` we invoke. + for route in iter_api_routes(app): + path = getattr(route, "path", "") + if path in {"/widgets.json", ""} or not path.endswith("widgets.json"): continue - widgets = await r.endpoint() # type: ignore + leaf = getattr(route, "original_route", route) + endpoint = getattr(leaf, "endpoint", None) + if endpoint is None: + continue + + widgets = await endpoint() if not isinstance(widgets, dict): continue - path = getattr(r, "path", "") path_widgets[path.replace("widgets.json", "")] = dict(widgets.items()) return path_widgets diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/network.py b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/network.py new file mode 100644 index 00000000000..ab04c97c165 --- /dev/null +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/network.py @@ -0,0 +1,53 @@ +"""Small infrastructure helpers shared across the API extension. + +Kept deliberately tiny — these are the bits that don't belong with the +FastAPI app boot path (``app/``), the higher-level service flows +(``service/``), or the spec-shaping utilities (``openapi.py``, +``widgets.py``). Pure functions only, no side effects beyond the +network probe. +""" + +import json +import socket +from pathlib import Path + + +def check_port(host: str, port: int | str) -> int: + """Return the first free port at or above ``port`` on ``host``. + + Walks upward one port at a time until ``socket.connect_ex`` reports + "nothing listening" (a non-zero result code). Used by ``launch_api`` + to gracefully fall through to the next port when the user's + requested one is already taken. + """ + port = int(port) + not_free = True + while not_free: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + res = sock.connect_ex((host, port)) + if res != 0: + not_free = False + else: + port += 1 + return port + + +def get_user_settings(current_user_settings: str) -> dict: + """Load the user's ``user_settings.json`` (or return a clean default). + + The Workspace launcher needs ``preferences.data_directory`` to + decide where ``apps.json`` lives. When the file doesn't exist + (fresh install, or running in a sandbox) we hand back the + canonical empty shape so callers can still ``.get("preferences", {})`` + without ``KeyError``. + """ + if Path(current_user_settings).exists(): + with open(current_user_settings, encoding="utf-8") as f: + user_settings = json.load(f) + else: + user_settings = { + "credentials": {}, + "preferences": {}, + "defaults": {"commands": {}}, + } + return user_settings diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/openapi.py b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/openapi.py index 3b2a32e653b..11b8936888f 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/openapi.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/openapi.py @@ -1,6 +1,5 @@ """OpenAPI parsing Utils.""" -# pylint: disable=C0302,R0912 # flake8: noqa: PLR0912 from openbb_core.provider.utils.helpers import to_snake_case @@ -63,6 +62,81 @@ TO_CAPS_STRINGS = [ ] +SSRM_REQUEST_SCHEMA = "AgGridRowsRequest" +SSRM_RESPONSE_PREFIX = "AgGridRowsResponse" + + +def _schema_inherits( + schema: dict | None, openapi_json: dict, target: str, _seen: set | None = None +) -> bool: + """Return True iff ``schema`` is — or transitively ``allOf``-extends — ``target``. + + Walks the OpenAPI ``allOf`` chain, which is how Pydantic emits + subclass relationships in the generated schema. + """ + if not isinstance(schema, dict): + return False + if schema.get("title") == target: + return True + seen = _seen if _seen is not None else set() + for parent in schema.get("allOf", []) or []: + ref = parent.get("$ref") + if not ref: + continue + parent_name = ref.split("/")[-1] + if parent_name == target: + return True + if parent_name in seen: + continue + seen.add(parent_name) + parent_schema = ( + openapi_json.get("components", {}).get("schemas", {}).get(parent_name) + ) + if _schema_inherits(parent_schema, openapi_json, target, seen): + return True + return False + + +def is_ssrm_route(openapi_json: dict, route: str, method: str) -> bool: + """Detect SSRM by request body or response shape. + + Either signal alone is sufficient — both is redundant. The contract: + + * Request body is (or subclasses) ``AgGridRowsRequest``. + * 200 response is ``AgGridRowsResponse[_]`` (Pydantic generic — emitted + with the row model name baked into the title), or has the + structural ``{rowData, rowCount}`` shape. + """ + op = (openapi_json.get("paths", {}) or {}).get(route, {}).get(method, {}) or {} + schemas = openapi_json.get("components", {}).get("schemas", {}) or {} + + body_ref = ( + op.get("requestBody", {}) + .get("content", {}) + .get("application/json", {}) + .get("schema", {}) + .get("$ref", "") + ) + if body_ref: + body_schema = schemas.get(body_ref.split("/")[-1]) + if _schema_inherits(body_schema, openapi_json, SSRM_REQUEST_SCHEMA): + return True + + resp = ( + op.get("responses", {}) + .get("200", {}) + .get("content", {}) + .get("application/json", {}) + .get("schema", {}) + ) + resp_name = resp.get("$ref", "").split("/")[-1] if "$ref" in resp else "" + if resp_name.startswith(SSRM_RESPONSE_PREFIX): + return True + resp_schema = schemas.get(resp_name) if resp_name else None + props = resp_schema.get("properties", {}) if isinstance(resp_schema, dict) else {} + return "rowData" in props and "rowCount" in props + + def extract_providers(params: list[dict]) -> list[str]: """ Extract provider options from parameters. @@ -129,7 +203,7 @@ def set_parameter_type(p: dict, p_schema: dict): return p -def set_parameter_options( # noqa: PLR0912 # pylint: disable=too-many-branches +def set_parameter_options( # noqa: PLR0912 p: dict, p_schema: dict, providers: list[str] ) -> dict: """ @@ -152,7 +226,9 @@ def set_parameter_options( # noqa: PLR0912 # pylint: disable=too-many-branches choices: dict[str, list[dict[str, str]]] = ( p.get("options", {}) if p.get("options") - else p_schema.get("options", {}) if p_schema.get("options") else {} + else p_schema.get("options", {}) + if p_schema.get("options") + else {} ) widget_configs: dict[str, dict] = {} multiple_items_allowed_dict: dict = {} @@ -274,7 +350,13 @@ def set_parameter_options( # noqa: PLR0912 # pylint: disable=too-many-branches key=lambda x: x["label"], ) if not is_provider_specific: - if len(providers) == 1: + if len(providers) == 1: # pragma: no cover — see note below + # Logically unreachable: the for-loop at lines 257-262 + # unconditionally sets ``is_provider_specific = True`` + # whenever ``len(providers) == 1`` (the second clause + # of ``provider in p_schema or (len(providers) == 1)``). + # Kept defensively as a safety net in case that loop's + # invariant ever changes. choices[providers[0]] = unique_general_choices multiple_items_allowed_dict[providers[0]] = p_schema.get( "multiple_items_allowed", False @@ -330,7 +412,7 @@ def _extract_provider_description(full_description: str, provider: str) -> str: Description format: "desc1 (provider: prov1);\n desc2 (provider: prov2)" """ - # pylint: disable=import-outside-toplevel + import re if not full_description: @@ -338,9 +420,9 @@ def _extract_provider_description(full_description: str, provider: str) -> str: # Check if this is a multi-provider description if "(provider:" not in full_description: - return full_description.split("Multiple comma separated items allowed")[ - 0 - ].strip() + return full_description.split( + "Multiple comma separated items allowed", maxsplit=1 + )[0].strip() # Handle semicolons embedded in the description text parts = re.split(r"(\(provider:\s*[^)]+\))", full_description) @@ -368,7 +450,7 @@ def _extract_provider_description(full_description: str, provider: str) -> str: return desc # If no specific provider section found, return first section (general description) - first_desc = full_description.split("(provider:")[0].strip() + first_desc = full_description.split("(provider:", maxsplit=1)[0].strip() first_desc = first_desc.split("Multiple comma separated items allowed")[0].strip() return first_desc @@ -471,14 +553,14 @@ def process_parameter( p["multiple_items_allowed"] = multiple_items_allowed_dict - # Safe check for description - if ( - p.get("description", "") - and "Multiple comma separated items allowed" in p["description"] # type: ignore - ): - p["description"] = ( - p["description"].split("Multiple comma separated items allowed")[0].strip() # type: ignore - ) + # Safe check for description — pull through a local so ty narrows + # the ``str | None`` from ``p.get`` to ``str`` after the truthy + # check. (Reading ``p["description"]`` again would re-widen.) + description = p.get("description", "") + if description and "Multiple comma separated items allowed" in description: + p["description"] = description.split("Multiple comma separated items allowed")[ + 0 + ].strip() if x_widget_config := param.get( "x-widget_config", param.get("schema", {}).get("x-widget_config", {}) @@ -489,9 +571,7 @@ def process_parameter( # Initialize provider specificity tracking provider_specific = False - available_providers_list = ( - [] - ) # Start with empty list - only add providers that match + available_providers_list = [] # Start with empty list - only add providers that match # Extract providers from title if p_schema.get("title"): @@ -527,7 +607,7 @@ def process_parameter( p["options"] = {} if providers else [] if providers: for provider in providers: - p["options"][provider] = [] # type: ignore + p["options"][provider] = [] # Handle widget config if _widget_config := p_schema.get("x-widget_config", {}): @@ -565,7 +645,16 @@ def process_parameter( ) # If parameter is provider-specific but not valid for any of our current providers, skip it - if not valid_for_current_providers: + if not valid_for_current_providers: # pragma: no cover — see note + # Logically unreachable: ``valid_provider_list`` (non-empty + # by the enclosing ``if`` guard) is the intersection of + # available_providers_list and current providers, and + # ``effective_providers`` is either ``valid_provider_list`` + # itself or ``existing_providers`` from + # ``set_parameter_options`` — which is always built from + # current providers + desc-providers and so contains at + # least one current provider. Kept defensively in case + # an upstream invariant ever changes. return {} return p @@ -618,7 +707,9 @@ def get_query_schema_for_widget( return route_params, has_chart -def get_data_schema_for_widget(openapi_json, operation_id, route: str | None = None): +def get_data_schema_for_widget( # noqa: PLR0911 + openapi_json, operation_id, route: str | None = None +): """ Get the data schema for a widget based on its operationId. @@ -638,7 +729,9 @@ def get_data_schema_for_widget(openapi_json, operation_id, route: str | None = N route = path break - _route = openapi_json["paths"].get(route, {}).get("get", {}) + path_item = openapi_json["paths"].get(route, {}) or {} + # SSRM routes are POST-only; fall back to POST when GET is missing. + _route = path_item.get("get", {}) or path_item.get("post", {}) or {} if ( schema := _route.get("responses", {}) @@ -647,6 +740,59 @@ def get_data_schema_for_widget(openapi_json, operation_id, route: str | None = N .get("application/json", {}) .get("schema", {}) ): + # Inline-schema fast paths. Spec-driven launches synthesize the + # schema dict directly under ``content.application/json.schema`` + # (no ``components.schemas`` $ref roundtrip), so we can descend + # without going through the named-schema lookup below. Three + # shapes matter here: + # + # * ``{"type": "object", "properties": {"results": {"type": + # "array", "items": {"type": "object", ...}}}}`` — the + # Socrata / OBBject envelope. Descend to the row-shape under + # ``items``. + # * ``{"type": "object", "properties": {"rowData": ..., "rowCount": + # ...}}`` — SSRM. Descend to the row schema under + # ``rowData.items``. + # * Any other inline ``{"type": "object", "properties": {...}}`` + # — treat the schema as the row directly (single-record + # shape). + if isinstance(schema, dict) and not schema.get("$ref"): + inline_props = ( + schema.get("properties") + if (schema.get("type") == "object" or "properties" in schema) + else None + ) + if isinstance(inline_props, dict): + if "results" in inline_props: + results = inline_props["results"] + if isinstance(results, dict): + items = results.get("items") + if isinstance(items, dict) and items.get("$ref"): + ref_name = items["$ref"].split("/")[-1] + return ( + openapi_json.get("components", {}) + .get("schemas", {}) + .get(ref_name, items) + ) + if isinstance(items, dict): + return items + return results + if "rowData" in inline_props and "rowCount" in inline_props: + row_data = inline_props["rowData"] + if isinstance(row_data, dict): + items = row_data.get("items", {}) or {} + if isinstance(items, dict) and items.get("$ref"): + ref_name = items["$ref"].split("/")[-1] + return ( + openapi_json.get("components", {}) + .get("schemas", {}) + .get(ref_name, items) + ) + return items + # Bare object schema with non-envelope properties — the + # schema itself describes one row. + return schema + # Get the reference to the schema from the successful response if "items" in schema: @@ -671,13 +817,33 @@ def get_data_schema_for_widget(openapi_json, operation_id, route: str | None = N ) if props and "results" in props: return props["results"] + # SSRM responses wrap the row model in + # ``rowData: list[Row]`` — descend one level so column + # auto-detection sees the row's fields, not the envelope. + if props and "rowData" in props and "rowCount" in props: + row_items = props["rowData"].get("items", {}) or {} + row_ref = row_items.get("$ref") + if row_ref: + row_name = row_ref.split("/")[-1] + return openapi_json["components"]["schemas"].get( + row_name, row_items + ) + return row_items - return openapi_json["components"]["schemas"].get(schema_name, schema_name) + # Defensive ``.get()`` chain — a FastAPI app with no + # Pydantic models (e.g. the spec-driven proxy launcher) + # produces an openapi document without a ``components`` + # block at all. Fall back to ``schema_name`` when the + # lookup misses, mirroring the line above. + return ( + openapi_json.get("components", {}) + .get("schemas", {}) + .get(schema_name, schema_name) + ) # Return None if the schema is not found return None -# pylint: disable=too-many-branches,too-many-statements def data_schema_to_columns_defs( # noqa: PLR0912 openapi_json, operation_id, @@ -769,6 +935,16 @@ def data_schema_to_columns_defs( # noqa: PLR0912 formatterFn = None prop = target_schema.get("properties", {}).get(key) + # Distinguish ``format: "date"`` from ``format: "date-time"`` so + # the resulting column tells ag-grid the right cellDataType. + # ``"dateString"`` keeps ``YYYY-MM-DD`` values verbatim (no + # timezone parsing, no implicit ``T00:00:00`` rendering); ``"date"`` + # is for full timestamps that should be parsed and locale-rendered. + def _date_cell_type(formats: list[str]) -> str: + if "date-time" in formats: + return "date" + return "dateString" + # Handle prop types for both when there's a single prop type or multiple if "items" in prop: items = prop.get("items", {}) @@ -777,38 +953,41 @@ def data_schema_to_columns_defs( # noqa: PLR0912 types = [ sub_prop.get("type") for sub_prop in prop["anyOf"] if "type" in sub_prop ] + formats = [ + sub_prop.get("format") + for sub_prop in prop["anyOf"] + if sub_prop.get("format") in {"date", "date-time"} + ] if "number" in types or "integer" in types or "float" in types: cell_data_type = "number" - elif "string" in types and any( - sub_prop.get("format") in ["date", "date-time"] - for sub_prop in prop["anyOf"] - if "format" in sub_prop - ): - cell_data_type = "date" + elif "string" in types and formats: + cell_data_type = _date_cell_type(formats) else: cell_data_type = "text" elif "anyOf" in prop: types = [ sub_prop.get("type") for sub_prop in prop["anyOf"] if "type" in sub_prop ] + formats = [ + sub_prop.get("format") + for sub_prop in prop["anyOf"] + if sub_prop.get("format") in {"date", "date-time"} + ] if "number" in types or "integer" in types or "float" in types: cell_data_type = "number" - elif "string" in types and any( - sub_prop.get("format") in ["date", "date-time"] - for sub_prop in prop["anyOf"] - if "format" in sub_prop - ): - cell_data_type = "date" + elif "string" in types and formats: + cell_data_type = _date_cell_type(formats) else: cell_data_type = "text" else: prop_type = prop.get("type", None) + prop_format = prop.get("format") if prop_type in ["number", "integer", "float"]: cell_data_type = "number" if prop_type == "integer": formatterFn = "int" - elif "format" in prop and prop["format"] in ["date", "date-time"]: - cell_data_type = "date" + elif prop_format in ["date", "date-time"]: + cell_data_type = _date_cell_type([prop_format]) else: cell_data_type = "text" @@ -835,6 +1014,15 @@ def data_schema_to_columns_defs( # noqa: PLR0912 "description", prop.get("title", key.title()) ) column_def["cellDataType"] = cell_data_type + + # Time-series convention: a column literally named ``date`` is + # the temporal axis of the dataset. Pre-sort descending so the + # most recent observation lands at the top — every dashboard + # we ship expects this default. Authors can override via + # ``widget_config.data.table.columnsDefs[].sort``. + if k == "date" and cell_data_type in {"date", "dateString"}: + column_def["sort"] = "desc" + measurement = prop.get("x-unit_measurement") if measurement == "percent": @@ -865,7 +1053,13 @@ def data_schema_to_columns_defs( # noqa: PLR0912 column_def["headerName"] = column_def["headerName"].upper() if k in ["fiscal_year", "year", "year_born", "calendar_year"]: - column_def["cellDataType"] = "number" + # ``cellDataType: "text"`` is the only reliable way to + # suppress ag-grid's locale-based thousands separator + # ("2,023" → "2023"). ``formatterFn: "none"`` alone + # doesn't override the type-driven formatting. 4-digit + # year strings sort lexicographically identical to the + # numeric sort, so no semantic loss. + column_def["cellDataType"] = "text" column_def["formatterFn"] = "none" if ( @@ -997,7 +1191,9 @@ def post_query_schema_for_widget( new_params[k]["type"] = ( "text" if v.get("type") == "object" - else "date" if "date" in v.get("format", "") else v.get("type", "text") + else "date" + if "date" in v.get("format", "") + else v.get("type", "text") ) new_params[k]["title"] = v.get("title") new_params[k]["description"] = v.get("description") diff --git a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/widgets.py b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/widgets.py index 22f878b12a3..fe78bd03a22 100644 --- a/openbb_platform/extensions/platform_api/openbb_platform_api/utils/widgets.py +++ b/openbb_platform/extensions/platform_api/openbb_platform_api/utils/widgets.py @@ -1,6 +1,7 @@ """Utils for building the widgets.json file.""" from copy import deepcopy +from typing import Any def deep_merge_configs( @@ -87,7 +88,7 @@ def deep_merge_configs( def modify_query_schema(query_schema: list[dict], provider_value: str): """Modify query_schema and the description for the current provider.""" - # pylint: disable=import-outside-toplevel + from .openapi import ( TO_CAPS_STRINGS, ) @@ -230,17 +231,18 @@ def get_form_input_paths(openapi: dict) -> dict: } -def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many-locals, too-many-statements +def build_json( # noqa: PLR0912 openapi: dict, widget_exclude_filter: list ): """Build the widgets.json file.""" - # pylint: disable=import-outside-toplevel + from openbb_core.app.service.system_service import SystemService # noqa from .openapi import ( TO_CAPS_STRINGS, data_schema_to_columns_defs, extract_providers, get_query_schema_for_widget, + is_ssrm_route, post_query_schema_for_widget, ) @@ -280,6 +282,18 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many- # If a GET exists, it's the primary. Otherwise, it's a POST. route_method = "get" if "get" in route_api else "post" + # A `text/event-stream` response cannot be rendered as a widget; + response_content: dict = {} + for status, body in ( + route_api.get(route_method, {}).get("responses", {}).items() + ): + if str(status).startswith("2"): + response_content.update(body.get("content", {}) or {}) + if any( + "event-stream" in str(content_type) for content_type in response_content + ): + continue + skip = False for starred in starred_list: if route.startswith( @@ -361,7 +375,9 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many- subcat = ( _cats[1].title().replace("_", " ") if len(_cats) > 2 - else _cats[1].replace("_", " ").title() if len(_cats) > 1 else None + else _cats[1].replace("_", " ").title() + if len(_cats) > 1 + else None ) name = ( widget_id.replace("fixedincome", "fixed income") @@ -398,7 +414,14 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many- .get("schema", {}) ): schema_name = _schema.get("$ref", "").split("/")[-1] - var_schema = openapi["components"]["schemas"].get(schema_name, {}) + # Defensive lookup — synthetic apps (e.g. the + # spec-driven proxy) can produce an openapi + # document with no ``components`` block at all. + var_schema = ( + openapi.get("components", {}) + .get("schemas", {}) + .get(schema_name, {}) + ) if var_schema: var_props = var_schema.get("properties", {}) @@ -409,11 +432,14 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many- if param_names: for _param in param_names: post_params = post_query_schema_for_widget( - openapi, post_body_source.get("operationId"), route, _param + openapi, + post_body_source.get("operationId"), + form_endpoint_path if has_form_endpoint else route, + _param, ) modified_post_params = modify_query_schema( - post_params, # type: ignore - provider, # type: ignore + post_params, + provider, ) if has_form_endpoint: @@ -460,8 +486,8 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many- form_endpoint_path if has_form_endpoint else route, ) modified_post_params = modify_query_schema( - post_params, # type: ignore - provider, # type: ignore + post_params, + provider, ) if has_form_endpoint: @@ -591,7 +617,26 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many- and response_schema.get("type") == "string" else "table" ) - widget_config = { + + # SSRM auto-promotion: a POST route whose body is + # ``AgGridRowsRequest`` (or subclass) and/or whose response is + # ``AgGridRowsResponse`` is an ag-grid SSRM endpoint. The + # type signature is the opt-in — the author doesn't need + # ``widget_config={"type": "ssrm_table"}`` on the route. + ssrm = is_ssrm_route(openapi, route, route_method) + if ssrm: + widget_type = "ssrm_table" + # SSRM responses are not OBBject-wrapped; the response + # IS the data, so ``dataKey`` must stay empty. + data_key = "" + + grid_data: dict[str, Any] = {"w": 40, "h": 15} + table_data: dict[str, Any] = {"showAll": True} + data_block: dict[str, Any] = { + "dataKey": data_key, + "table": table_data, + } + widget_config: dict[str, Any] = { "name": f"{name}" if name else route_api[route_method].get("summary"), "description": route_api[route_method].get("description", ""), "category": category.replace("_", " ").title(), @@ -603,14 +648,9 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many- }, "params": modified_query_schema, "endpoint": route, - "runButton": False, - "gridData": {"w": 40, "h": 15}, - "data": { - "dataKey": data_key, - "table": { - "showAll": True, - }, - }, + "runButton": bool(ssrm), + "gridData": grid_data, + "data": data_block, "source": [provider_name], } @@ -636,13 +676,13 @@ def build_json( # noqa: PLR0912 # pylint: disable=too-many-branches, too-many- if data_config := data_schema_to_columns_defs( openapi, widget_id, provider, route, True ): - for key, value in data_config.copy().items(): # type: ignore + for key, value in data_config.copy().items(): if key.startswith("$."): data_var_key[key] = value widget_config["data"] = deep_merge_configs( widget_config["data"], - {k: v for k, v in data_config.items() if not k.startswith("$.")}, # type: ignore + {k: v for k, v in data_config.items() if not k.startswith("$.")}, ) if data_var_key: diff --git a/openbb_platform/extensions/platform_api/pyproject.toml b/openbb_platform/extensions/platform_api/pyproject.toml index 53daa8c2365..f4f20279874 100644 --- a/openbb_platform/extensions/platform_api/pyproject.toml +++ b/openbb_platform/extensions/platform_api/pyproject.toml @@ -1,23 +1,129 @@ -[tool.poetry] +[project] name = "openbb-platform-api" -version = "1.3.6" -description = "OpenBB Platform API: Launch script and widgets builder for the Open Data Platform REST API and Workspace Backend Connector." -authors = ["OpenBB "] -license = "AGPL-3.0-only" +version = "2.0.0" +description = "OpenBB Platform API: launcher and widgets builder for the Open Data Platform REST API and OpenBB Workspace custom-backend connector." +authors = [{ name = "OpenBB Team", email = "hello@openbb.co" }] +license = { text = "AGPL-3.0-only" } readme = "README.md" -homepage = "https://openbb.co" -repository = "https://github.com/openbb-finance/openbb" -documentation = "https://docs.openbb.co/odp/python/extensions/interface/openbb-api" -packages = [{ include = "openbb_platform_api" }] +requires-python = ">=3.10,<4" +dependencies = [ + "openbb-core>=2.0.0", + "deepdiff>=8.6.2", + # Backport of stdlib ``tomllib`` for py3.10. + "tomli>=2.0;python_version<'3.11'", +] -[tool.poetry.scripts] +[project.scripts] openbb-api = "openbb_platform_api.main:main" -[tool.poetry.dependencies] -python = ">=3.10,<4" -openbb-core = "^1.6.10" -deepdiff = ">=8.6.2" +[project.urls] +Homepage = "https://openbb.co" +Repository = "https://github.com/OpenBB-finance/OpenBB" +Documentation = "https://docs.openbb.co/odp/python/extensions/interface/openbb-api" + +[dependency-groups] +dev = [ + "openbb-devtools>=2.0.0", + "httpx>=0.28.0,<1.0.0", +] [build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["openbb_platform_api"] + +[tool.hatch.build.targets.sdist] +include = [ + "openbb_platform_api", + "README.md", + "pyproject.toml", +] + +# openbb-core is path-sourced because v5's has not yet been published. +[tool.uv.sources] +openbb-core = { path = "../../core", editable = true } +openbb-devtools = { path = "../devtools", editable = true } + +[tool.ruff] +line-length = 88 +target-version = "py310" +fix = true + +[tool.ruff.lint] +select = [ + "D", + "E", + "W", + "F", + "S", + "UP", + "I", + "PLC", + "PLE", + "PLR", + "PLW", + "SIM", + "T20", +] +ignore = [ + "PLR2004", # magic-value-comparison + "PLR0913", # too-many-arguments + "PLR0915", # too-many-statements + "PLC0415", # import-outside-top-level + "E402", # module-import-not-at-top-of-file + "E501", # line-too-long (formatter handles this on best-effort) + "D202", + "D203", + "D205", + "D206", # docstring-tab-indentation (conflicts with formatter) + "D210", + "D212", + "D213", + "D214", + "D215", + "D300", # triple-single-quotes (conflicts with formatter) + "D400", + "D404", + "D405", + "D406", + "D407", + "D408", + "D409", + "D410", + "D411", + "D413", + "D415", + "D416", + "D417", +] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["S101", "S108", "D", "SIM105", "SIM117"] +"integration/**" = ["S101", "S108", "D", "SIM105", "SIM117"] +"**/__init__.py" = ["F401"] + +[tool.ruff.lint.isort] +combine-as-imports = true +force-wrap-aliases = true + +[tool.ruff.lint.pylint] +max-args = 8 +max-branches = 26 +max-returns = 9 +max-statements = 30 + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.ty] +[tool.ty.environment] +python-version = "3.10" + +[tool.ty.src] +include = ["openbb_platform_api"] +exclude = ["openbb_platform_api/assets", "tests", "integration"] + +[tool.ty.rules] +unresolved-import = "warn" diff --git a/openbb_platform/extensions/platform_api/tests/test_api.py b/openbb_platform/extensions/platform_api/tests/app/test_app.py similarity index 91% rename from openbb_platform/extensions/platform_api/tests/test_api.py rename to openbb_platform/extensions/platform_api/tests/app/test_app.py index a887fbbc5a1..b0ca7fa5ea7 100644 --- a/openbb_platform/extensions/platform_api/tests/test_api.py +++ b/openbb_platform/extensions/platform_api/tests/app/test_app.py @@ -6,8 +6,9 @@ import sys import types from unittest.mock import AsyncMock, MagicMock, mock_open, patch -import openbb_platform_api.utils.api as api_utils import pytest + +import openbb_platform_api.utils.api as api_utils from openbb_platform_api.utils.api import ( check_port, get_user_settings, @@ -26,7 +27,7 @@ def mock_env_vars(monkeypatch, tmp_path_factory): def _load_main_with_mocks(): - # pylint: disable=import-outside-toplevel + from fastapi import FastAPI stub_app = FastAPI() @@ -35,7 +36,7 @@ def _load_main_with_mocks(): api_module = types.ModuleType("openbb_core.api") api_module.__path__ = [] rest_api_module = types.ModuleType("openbb_core.api.rest_api") - rest_api_module.app = stub_app # type: ignore + rest_api_module.app = stub_app app_module = types.ModuleType("openbb_core.app") app_module.__path__ = [] app_service_module = types.ModuleType("openbb_core.app.service") @@ -48,15 +49,24 @@ def _load_main_with_mocks(): python_settings=types.SimpleNamespace( model_dump=lambda: {"uvicorn": {}} ), - cors=types.SimpleNamespace( - allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] + # CORS lives under ``api_settings`` in the real + # SystemSettings model. The launcher's CORS helper + # reads ``system.api_settings.cors`` — keep the + # nesting accurate so the helper doesn't blow up + # during the module reload. + api_settings=types.SimpleNamespace( + prefix="/api", + cors=types.SimpleNamespace( + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], + ), ), - api_settings=types.SimpleNamespace(prefix="/api"), ) system_service_module.SystemService = DummySystemService # type:ignore env_module = types.ModuleType("openbb_core.env") - env_module.Env = lambda: None # type: ignore + env_module.Env = lambda: None provider_module = types.ModuleType("openbb_core.provider") provider_module.__path__ = [] @@ -82,8 +92,8 @@ def _load_main_with_mocks(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) if loop.is_running(): - return asyncio.ensure_future(result) # type: ignore - return loop.run_until_complete(result) # type: ignore + return asyncio.ensure_future(result) + return loop.run_until_complete(result) return result def _to_snake_case_stub(value: str) -> str: @@ -95,8 +105,8 @@ def _load_main_with_mocks(): value = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", value) return re.sub(r"[\s\-]+", "_", value).lower() - provider_utils_helpers_module.run_async = _run_async_stub # type: ignore - provider_utils_helpers_module.to_snake_case = _to_snake_case_stub # type: ignore + provider_utils_helpers_module.run_async = _run_async_stub + provider_utils_helpers_module.to_snake_case = _to_snake_case_stub modules = { "openbb_core": core_module, @@ -111,12 +121,13 @@ def _load_main_with_mocks(): "openbb_core.provider.utils.helpers": provider_utils_helpers_module, } - for name in list(modules): - sys.modules.pop(name, None) - with patch.dict(sys.modules, modules): - sys.modules.pop("openbb_platform_api.main", None) - return importlib.import_module("openbb_platform_api.main") + for cached in ( + "openbb_platform_api.main", + "openbb_platform_api.app.app", + ): + sys.modules.pop(cached, None) + return importlib.import_module("openbb_platform_api.app.app") @pytest.mark.parametrize("port_input", [6900, "6900", 6901, "6901"]) @@ -151,7 +162,7 @@ def test_get_user_settings_no_login(): def test_get_widgets_json_no_build(): dummy_main = types.ModuleType("openbb_platform_api.main") - dummy_main.FIRST_RUN = False # type: ignore + dummy_main.FIRST_RUN = False with ( patch("builtins.open", mock_open(read_data="{}")), @@ -186,8 +197,35 @@ def test_parse_args(): assert args == {"flag": True} +def test_parse_args_factory_no_name_error_with_windows_drive_path(): + """Regression: Windows drive-letter colon is not module:attr. + + ``--app C:\\path\\app.py --factory true --name ""`` must still + raise the factory-needs-name ``ValueError`` instead of treating + the drive-letter colon as ``module:attr`` notation, splitting on + it, and using the path tail as the FastAPI attribute name. + """ + test_args = [ + "script.py", + "--app", + r"C:\Users\runner\AppData\Local\Temp\pytest\some_app.py", + "--factory", + "true", + "--name", + "", + ] + with ( + patch("sys.argv", test_args), + pytest.raises( + ValueError, + match="The factory function name must be provided to the --name parameter", + ), + ): + parse_args() + + def test_import_module_app(): - # pylint: disable=import-outside-toplevel + from fastapi import FastAPI as RealFastAPI class MockFastAPI(RealFastAPI): @@ -235,7 +273,7 @@ def test_import_module_app(): def test_import_file_app(tmp_path): - # pylint: disable=import-outside-toplevel + from fastapi import FastAPI as RealFastAPI class MockFastAPI(RealFastAPI): @@ -286,7 +324,7 @@ def test_import_file_app(tmp_path): def test_import_factory_app(): - # pylint: disable=import-outside-toplevel + from fastapi import FastAPI as RealFastAPI class MockFastAPI(RealFastAPI): @@ -349,7 +387,7 @@ class TestPathHandling: @pytest.fixture def mock_fastapi_env(self): """Set up common mocks for FastAPI import tests.""" - # pylint: disable=import-outside-toplevel + from fastapi import FastAPI as RealFastAPI class MockFastAPI(RealFastAPI): @@ -716,8 +754,16 @@ class TestPathHandling: class TestPathDetectionHelpers: """Test path detection logic for cross-platform compatibility.""" + @pytest.mark.skipif( + sys.platform == "win32", reason="Unix-style absolute paths only" + ) def test_is_absolute_path_detection_unix(self): - """Test absolute path detection for Unix paths.""" + """Test absolute path detection for Unix paths. + + ``Path('/home/user/app.py').is_absolute()`` only returns ``True`` + on POSIX — on Windows ``WindowsPath`` requires a drive letter + for absoluteness, so we gate this case to non-Windows platforms. + """ from pathlib import Path # Unix absolute paths @@ -796,9 +842,9 @@ async def test_get_apps_json_creates_missing_file(tmp_path): patch.object(main, "DEFAULT_APPS_PATH", str(default_path)), patch.object(main, "get_widgets", AsyncMock(return_value={})), patch( - "openbb_platform_api.main.os.path.exists", side_effect=exists_side_effect + "openbb_platform_api.app.app.os.path.exists", side_effect=exists_side_effect ), - patch("openbb_platform_api.main.os.makedirs") as mock_makedirs, + patch("openbb_platform_api.app.app.os.makedirs") as mock_makedirs, patch("builtins.open", mock_open(read_data="[]")) as mocked_open, ): response = await main.get_apps_json() @@ -840,7 +886,7 @@ async def test_get_apps_json_merges_templates_with_additional_sources(tmp_path): "extra": {}, }, ), - patch("openbb_platform_api.main.os.path.exists", return_value=True), + patch("openbb_platform_api.app.app.os.path.exists", return_value=True), patch.object( main, "get_widgets", @@ -853,8 +899,8 @@ async def test_get_apps_json_merges_templates_with_additional_sources(tmp_path): return_value={"good": [{"id": "extra"}], "bad": {"id": "invalid"}} ), ), - patch("openbb_platform_api.main.has_additional_apps", return_value=True), - patch("openbb_platform_api.main.logger.error") as mock_log_error, + patch("openbb_platform_api.app.app.has_additional_apps", return_value=True), + patch("openbb_platform_api.app.app.logger.error") as mock_log_error, patch("builtins.open", mocked_open), ): response = await main.get_apps_json() @@ -876,17 +922,27 @@ async def test_get_apps_json_merges_templates_with_additional_sources(tmp_path): def test_get_widgets_json_merges_with_additional_sources(monkeypatch): + """Router-attached widgets get merged into the auto-generated set + when their path isn't excluded. State (``FIRST_RUN`` / + ``PATH_WIDGETS``) lives on the canonical service module — the + legacy ``api_utils`` re-exports route through ``__getattr__`` and + can't be monkey-patched to feed the function, so target the source + module directly. + """ + + from openbb_platform_api.service import widgets_service + base_widgets = {"default": {"name": "Default Widget"}} additional_widgets = {"extra": {"name": "Extra Widget"}} - monkeypatch.setattr(api_utils, "FIRST_RUN", False, raising=False) + monkeypatch.setattr(widgets_service, "FIRST_RUN", False, raising=False) monkeypatch.setattr( "openbb_platform_api.utils.widgets.build_json", MagicMock(return_value=base_widgets.copy()), raising=False, ) monkeypatch.setattr( - api_utils, + widgets_service, "PATH_WIDGETS", {"custom": {"extra": additional_widgets["extra"]}}, raising=False, diff --git a/openbb_platform/extensions/platform_api/tests/app/test_app_routes.py b/openbb_platform/extensions/platform_api/tests/app/test_app_routes.py new file mode 100644 index 00000000000..b090e6a8532 --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/app/test_app_routes.py @@ -0,0 +1,1049 @@ +"""Targeted tests for ``openbb_platform_api.app.app`` route handlers +and ``launch_api`` / ``main``. + +The legacy ``tests/app/test_app.py`` exercises ``get_apps_json`` and +``parse_args`` already; this file fills the remaining gaps: + +* ``root`` (landing-page handler) +* ``get_widgets`` in all three flow modes +* ``get_agents`` and ``get_agents_json`` variants +* ``launch_api`` env / port / host normalization + ``uvicorn.run`` +""" + +import json +import os +from unittest.mock import AsyncMock, MagicMock, patch + + +def test_root_serves_landing_page_html(tmp_path): + """The root handler reads ``assets/landing_page.html`` and returns + its contents as ``HTMLResponse``. + """ + import asyncio + + from openbb_platform_api.app import app as app_module + + html_path = tmp_path / "landing_page.html" + html_path.write_text("hello") + + fake_module_file = tmp_path / "child" / "app.py" + fake_module_file.parent.mkdir() + fake_module_file.write_text("") + # Patch the file the route reads from. The handler builds the path + # via ``Path(__file__).parents[1] / "assets" / ...``; mock the file + # contents instead of relocating the lookup. + real_file = ( + os.path.dirname(os.path.abspath(app_module.__file__)) + + "/../assets/landing_page.html" + ) + if not os.path.exists(real_file): + # Real assets dir not present in this checkout — skip. + import pytest + + pytest.skip("landing_page.html not present in this checkout") + response = asyncio.run(app_module.root()) + assert response.status_code == 200 + assert b"`` module is + loaded in ``sys.modules``, its prefix is appended to the exclude + filter. + """ + from openbb_platform_api.app.bootstrap import check_for_platform_extensions + + # Stub a fake econometrics module into sys.modules so the loaded-check + # passes. + fake_module = types.ModuleType("openbb_econometrics") + sys.modules["openbb_econometrics"] = fake_module + try: + app = _MockFastAPI() + app.openapi_tags = [{"name": "econometrics"}, {"name": "unrelated"}] + + with patch( + "openbb_core.app.service.system_service.SystemService" + ) as mock_system: + mock_system.return_value.system_settings.api_settings.prefix = "/api/v1" + out = check_for_platform_extensions(app, []) + finally: + sys.modules.pop("openbb_econometrics", None) + assert "/api/v1/econometrics/*" in out + + +def test_check_for_platform_extensions_skips_when_module_not_loaded( + mock_openbb_core, +): + """If the tag matches but the module isn't loaded, no append happens + — we don't exclude routes for an extension that isn't actually + contributing endpoints. + """ + from openbb_platform_api.app.bootstrap import check_for_platform_extensions + + # Make sure the module ISN'T loaded. + sys.modules.pop("openbb_econometrics", None) + sys.modules.pop("openbb_quantitative", None) + sys.modules.pop("openbb_technical", None) + app = _MockFastAPI() + app.openapi_tags = [{"name": "econometrics"}] + out = check_for_platform_extensions(app, []) + assert out == [] + + +def test_check_for_platform_extensions_no_matching_tags(mock_openbb_core): + """No tag mentions any data-processing extension → exclude filter + untouched. + """ + from openbb_platform_api.app.bootstrap import check_for_platform_extensions + + app = _MockFastAPI() + app.openapi_tags = [{"name": "equity"}, {"name": "currency"}] + out = check_for_platform_extensions(app, ["existing"]) + assert out == ["existing"] + + +def test_check_for_platform_extensions_handles_empty_openapi_tags(mock_openbb_core): + """Defensive: ``openapi_tags=None`` (no tags declared) doesn't crash.""" + from openbb_platform_api.app.bootstrap import check_for_platform_extensions + + app = _MockFastAPI() + app.openapi_tags = None + out = check_for_platform_extensions(app, []) + assert out == [] + + +def test_import_app_factory_without_explicit_flag_emits_warning( + tmp_path, mock_openbb_core, capsys +): + """A FastAPI factory function (callable) detected without + ``--factory true`` emits a soft warning to stdout but still uses + it. Exercises lines 117-121. + """ + from openbb_platform_api.app.bootstrap import import_app + + app_file = tmp_path / "factory_app.py" + app_file.write_text( + "from fastapi import FastAPI\n\ndef app():\n return FastAPI()\n" + ) + + with patch("fastapi.FastAPI", new=_MockFastAPI): + result = import_app(str(app_file), "app", False) + sys.modules.pop(app_file.stem, None) + assert isinstance(result, _MockFastAPI) + out = capsys.readouterr().out + assert "App factory detected" in out + + +# --------------------------------------------------------------------------- +# apply_cors_from_system_service — CORS wiring + dedup +# --------------------------------------------------------------------------- + + +def test_apply_cors_adds_middleware_drawing_from_system_service(): + """A bare FastAPI app gets CORSMiddleware installed using the + origins/methods/headers from ``SystemService``. Without this, + OPTIONS preflight requests return 405 Method Not Allowed. + """ + from fastapi import FastAPI + from fastapi.middleware.cors import CORSMiddleware + + from openbb_platform_api.app.bootstrap import apply_cors_from_system_service + + app = FastAPI() + with patch("openbb_core.app.service.system_service.SystemService") as mock_system: + mock_system.return_value.system_settings.api_settings.cors.allow_origins = [ + "https://app.example.com" + ] + mock_system.return_value.system_settings.api_settings.cors.allow_methods = [ + "GET", + "POST", + "OPTIONS", + ] + mock_system.return_value.system_settings.api_settings.cors.allow_headers = [ + "Authorization", + "Content-Type", + ] + result = apply_cors_from_system_service(app) + + assert result is True + cors_layers = [m for m in app.user_middleware if m.cls is CORSMiddleware] + assert len(cors_layers) == 1 + # The settings flowed through into the middleware kwargs. + kwargs = cors_layers[0].kwargs + assert kwargs["allow_origins"] == ["https://app.example.com"] + assert "OPTIONS" in kwargs["allow_methods"] + + +def test_apply_cors_is_idempotent_when_already_installed(): + """Calling the helper twice (or against an app whose underlying + framework already added CORS) is a no-op the second time. Important + because ``app.py``'s module-level call runs unconditionally — the + ``--app`` and default-app paths might already have CORS wired. + """ + from fastapi import FastAPI + from fastapi.middleware.cors import CORSMiddleware + + from openbb_platform_api.app.bootstrap import apply_cors_from_system_service + + app = FastAPI() + app.add_middleware(CORSMiddleware, allow_origins=["*"]) + + with patch("openbb_core.app.service.system_service.SystemService") as mock_system: + mock_system.return_value.system_settings.api_settings.cors.allow_origins = ["*"] + mock_system.return_value.system_settings.api_settings.cors.allow_methods = ["*"] + mock_system.return_value.system_settings.api_settings.cors.allow_headers = ["*"] + result = apply_cors_from_system_service(app) + + assert result is False + cors_layers = [m for m in app.user_middleware if m.cls is CORSMiddleware] + assert len(cors_layers) == 1 + + +def test_apply_cors_handles_options_preflight_end_to_end(): + """End-to-end via TestClient: a route that only declares GET + responds 200 to OPTIONS once CORSMiddleware is installed. This is + the regression that motivated the unconditional helper call — + Workspace makes a preflight OPTIONS to ``/widgets.json`` and the + bare route returns 405 without CORS. Build the app + apply CORS + BEFORE the first TestClient request because Starlette freezes the + middleware stack on first dispatch. + """ + from fastapi import FastAPI + from fastapi.testclient import TestClient + + from openbb_platform_api.app.bootstrap import apply_cors_from_system_service + + # Baseline: a vanilla app rejects OPTIONS preflight on a GET-only + # route with 405 (the symptom the user reported). + bare = FastAPI() + + @bare.get("/widgets.json") + async def widgets(): + return {} + + pre_response = TestClient(bare).options( + "/widgets.json", + headers={ + "Origin": "https://workspace.example.com", + "Access-Control-Request-Method": "GET", + }, + ) + assert pre_response.status_code == 405 + + # Fresh app with CORS applied BEFORE TestClient construction. + app = FastAPI() + + @app.get("/widgets.json") + async def widgets_cors(): + return {} + + with patch("openbb_core.app.service.system_service.SystemService") as mock_system: + mock_system.return_value.system_settings.api_settings.cors.allow_origins = ["*"] + mock_system.return_value.system_settings.api_settings.cors.allow_methods = ["*"] + mock_system.return_value.system_settings.api_settings.cors.allow_headers = ["*"] + apply_cors_from_system_service(app) + + response = TestClient(app).options( + "/widgets.json", + headers={ + "Origin": "https://workspace.example.com", + "Access-Control-Request-Method": "GET", + }, + ) + assert response.status_code == 200 + assert "access-control-allow-origin" in response.headers diff --git a/openbb_platform/extensions/platform_api/tests/app/test_config.py b/openbb_platform/extensions/platform_api/tests/app/test_config.py new file mode 100644 index 00000000000..3665402d7eb --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/app/test_config.py @@ -0,0 +1,553 @@ +"""Tests for ``openbb_platform_api.app.config`` — launcher TOML cascade, +``[env]`` injection, and ``[launcher]``-section override semantics. +""" + +import os +import sys +from unittest.mock import patch + +import pytest + +# --------------------------------------------------------------------------- +# extract_config_file_from_argv — sniffing the CLI flag without importing args +# --------------------------------------------------------------------------- + + +def test_extract_config_file_returns_none_when_flag_absent(): + """No ``--config-file`` in argv → ``None``.""" + from openbb_platform_api.app.config import extract_config_file_from_argv + + assert extract_config_file_from_argv(["--port", "6900"]) is None + + +def test_extract_config_file_picks_value_after_flag(): + """``--config-file /path`` form.""" + from openbb_platform_api.app.config import extract_config_file_from_argv + + assert ( + extract_config_file_from_argv(["--config-file", "/etc/openbb.toml"]) + == "/etc/openbb.toml" + ) + + +def test_extract_config_file_picks_value_with_equals_form(): + """``--config-file=/path`` form (POSIX-style).""" + from openbb_platform_api.app.config import extract_config_file_from_argv + + assert ( + extract_config_file_from_argv(["--config-file=/etc/openbb.toml"]) + == "/etc/openbb.toml" + ) + + +def test_extract_config_file_returns_none_when_value_missing(): + """``--config-file`` followed by another flag (no value) → ``None``.""" + from openbb_platform_api.app.config import extract_config_file_from_argv + + assert extract_config_file_from_argv(["--config-file", "--port", "6900"]) is None + + +def test_extract_config_file_returns_none_when_equals_value_empty(): + """``--config-file=`` (empty value after equals) → ``None``.""" + from openbb_platform_api.app.config import extract_config_file_from_argv + + assert extract_config_file_from_argv(["--config-file="]) is None + + +def test_extract_config_file_defaults_to_sys_argv(): + """Omitting ``argv`` falls back to ``sys.argv[1:]``.""" + from openbb_platform_api.app.config import extract_config_file_from_argv + + with patch.object(sys, "argv", ["openbb-api", "--config-file", "/x/y.toml"]): + assert extract_config_file_from_argv() == "/x/y.toml" + + +# --------------------------------------------------------------------------- +# resolve_explicit_config_path — priority order +# --------------------------------------------------------------------------- + + +def test_resolve_explicit_config_cli_wins_over_envs(): + """CLI value beats every env-var slot.""" + from openbb_platform_api.app.config import resolve_explicit_config_path + + fake_env = {"OPENBB_API_CONFIG": "/api.toml", "OPENBB_CONFIG": "/core.toml"} + assert resolve_explicit_config_path("/cli.toml", env=fake_env) == "/cli.toml" + + +def test_resolve_explicit_config_api_env_wins_over_core_env(): + """``OPENBB_API_CONFIG`` is launcher-specific and wins over the + generic ``OPENBB_CONFIG`` slot — explicit beats general. + """ + from openbb_platform_api.app.config import resolve_explicit_config_path + + fake_env = {"OPENBB_API_CONFIG": "/api.toml", "OPENBB_CONFIG": "/core.toml"} + assert resolve_explicit_config_path(env=fake_env) == "/api.toml" + + +def test_resolve_explicit_config_falls_back_to_core_env(): + """When ``OPENBB_API_CONFIG`` is absent, ``OPENBB_CONFIG`` is honored + so a single env var can configure the whole stack. + """ + from openbb_platform_api.app.config import resolve_explicit_config_path + + fake_env = {"OPENBB_CONFIG": "/core.toml"} + assert resolve_explicit_config_path(env=fake_env) == "/core.toml" + + +def test_resolve_explicit_config_returns_none_when_nothing_set(): + """No CLI, no env vars → ``None`` (cascade falls back to discovery).""" + from openbb_platform_api.app.config import resolve_explicit_config_path + + assert resolve_explicit_config_path(env={}) is None + + +def test_resolve_explicit_config_ignores_empty_env_values(): + """Empty string env values are equivalent to unset.""" + from openbb_platform_api.app.config import resolve_explicit_config_path + + fake_env = {"OPENBB_API_CONFIG": "", "OPENBB_CONFIG": ""} + assert resolve_explicit_config_path(env=fake_env) is None + + +# --------------------------------------------------------------------------- +# apply_launcher_env — [env] table → os.environ (no clobber) +# --------------------------------------------------------------------------- + + +def test_apply_launcher_env_pushes_keys_to_environ(): + """New keys get pushed in; the function reports what it applied.""" + from openbb_platform_api.app.config import apply_launcher_env + + target: dict[str, str] = {} + applied = apply_launcher_env( + {"OPENBB_API_KEY": "abc", "OPENBB_API_HOST": "0.0.0.0"}, # noqa: S104 — container deploy fixture + env=target, + ) + assert sorted(applied) == ["OPENBB_API_HOST", "OPENBB_API_KEY"] + assert target["OPENBB_API_KEY"] == "abc" + assert target["OPENBB_API_HOST"] == "0.0.0.0" # noqa: S104 — container deploy fixture + + +def test_apply_launcher_env_does_not_clobber_existing_shell_vars(): + """Real shell env vars always win — TOML never overwrites them.""" + from openbb_platform_api.app.config import apply_launcher_env + + target = {"OPENBB_API_HOST": "from-shell"} + applied = apply_launcher_env( + {"OPENBB_API_HOST": "from-toml", "OPENBB_API_PORT": "6901"}, env=target + ) + assert applied == ["OPENBB_API_PORT"] + assert target["OPENBB_API_HOST"] == "from-shell" + assert target["OPENBB_API_PORT"] == "6901" + + +def test_apply_launcher_env_handles_none_section_gracefully(): + """No ``[env]`` table at all → no-op.""" + from openbb_platform_api.app.config import apply_launcher_env + + target: dict[str, str] = {} + assert apply_launcher_env(None, env=target) == [] + assert target == {} + + +def test_apply_launcher_env_coerces_values_to_strings(): + """TOML can produce ints / bools / floats — env vars must be strings.""" + from openbb_platform_api.app.config import apply_launcher_env + + target: dict[str, str] = {} + apply_launcher_env({"PORT": 6900, "DEBUG": True, "RATIO": 0.5}, env=target) + assert target == {"PORT": "6900", "DEBUG": "True", "RATIO": "0.5"} + + +def test_apply_launcher_env_skips_non_string_keys(): + """A malformed TOML with non-string keys is tolerated — those entries + are silently skipped rather than crashing the launcher. + """ + from openbb_platform_api.app.config import apply_launcher_env + + target: dict[str, str] = {} + apply_launcher_env({"GOOD": "ok", 42: "bad"}, env=target) # type: ignore[dict-item] + assert target == {"GOOD": "ok"} + + +def test_apply_launcher_env_expands_bare_dollar_var_reference(): + """``"$GITHUB_TOKEN"`` resolves against the current env — the + container scenario where the orchestrator injects the secret as a + shell env var and the TOML maps it to the application's name. + """ + from openbb_platform_api.app.config import apply_launcher_env + + target = {"GITHUB_TOKEN": "ghp_secret_value"} # noqa: S105 — synthetic test fixture + apply_launcher_env({"OPENBB_GITHUB_TOKEN": "$GITHUB_TOKEN"}, env=target) + assert ( + target["OPENBB_GITHUB_TOKEN"] == "ghp_secret_value" # noqa: S105 — synthetic test fixture + ) + + +def test_apply_launcher_env_expands_braced_dollar_var_reference(): + """``${HOST}`` is the disambiguating form — needed when the + reference is followed by an identifier character. + """ + from openbb_platform_api.app.config import apply_launcher_env + + target = {"HOST": "0.0.0.0"} # noqa: S104 — container deploy fixture + apply_launcher_env({"OPENBB_API_HOST": "${HOST}"}, env=target) + assert target["OPENBB_API_HOST"] == "0.0.0.0" # noqa: S104 — container deploy fixture + + +def test_apply_launcher_env_expands_within_string_template(): + """References embedded in larger strings work — useful for URL + templating like ``"https://$HOST:$PORT"``. + """ + from openbb_platform_api.app.config import apply_launcher_env + + target = {"HOST": "api.example.com", "PORT": "8443"} + apply_launcher_env({"OPENBB_API_URL": "https://$HOST:$PORT/v1"}, env=target) + assert target["OPENBB_API_URL"] == "https://api.example.com:8443/v1" + + +def test_apply_launcher_env_skips_entry_when_reference_unresolved(caplog): + """A reference to an unset variable means the operator's + deployment is misconfigured. Skip the entry (don't set a literal + ``$MISSING`` value) and log a warning so the missing var surfaces. + """ + import logging + + from openbb_platform_api.app.config import apply_launcher_env + + target = {"HOST": "example.com"} # no GITHUB_TOKEN here + with caplog.at_level(logging.WARNING, logger="openbb_platform_api.config"): + applied = apply_launcher_env( + { + "OPENBB_API_HOST": "$HOST", # resolves + "OPENBB_GITHUB_TOKEN": "$GITHUB_TOKEN", # missing + }, + env=target, + ) + assert applied == ["OPENBB_API_HOST"] + assert "OPENBB_API_HOST" in target + assert "OPENBB_GITHUB_TOKEN" not in target + # Warning surfaces the missing name so ops can debug fast. + assert any( + "OPENBB_GITHUB_TOKEN" in rec.message and "GITHUB_TOKEN" in rec.message + for rec in caplog.records + ) + + +def test_apply_launcher_env_chained_references_resolve_in_order(): + """TOML preserves insertion order. An earlier ``[env]`` entry that + lands in ``os.environ`` becomes resolvable for a later entry — + so a single TOML can build composite values without needing a + pre-existing shell var. + """ + from openbb_platform_api.app.config import apply_launcher_env + + target: dict[str, str] = {} + apply_launcher_env( + { + "BASE_URL": "https://api.example.com", + "FULL_URL": "$BASE_URL/v1/data", + }, + env=target, + ) + assert target["BASE_URL"] == "https://api.example.com" + assert target["FULL_URL"] == "https://api.example.com/v1/data" + + +def test_apply_launcher_env_dollar_not_followed_by_identifier_is_literal(): + """A bare ``$`` followed by punctuation, digits-only, or + whitespace is left as-is — not flagged as a missing reference. + Lets values like ``"price > $5"`` pass through unchanged. + """ + from openbb_platform_api.app.config import apply_launcher_env + + target: dict[str, str] = {} + apply_launcher_env( + {"NOTE": "price > $5", "EMOTE": "$@!"}, + env=target, + ) + assert target["NOTE"] == "price > $5" + assert target["EMOTE"] == "$@!" + + +def test_apply_launcher_env_repeated_missing_ref_reported_once(caplog): + """``"$X-$X"`` should log ``X`` as missing only once — keeps + container startup logs clean when the same secret name is + referenced multiple places. + """ + import logging + + from openbb_platform_api.app.config import apply_launcher_env + + target: dict[str, str] = {} + with caplog.at_level(logging.WARNING, logger="openbb_platform_api.config"): + apply_launcher_env({"COMBO_KEY": "$MISSING-$MISSING-$MISSING"}, env=target) + warning_records = [r for r in caplog.records if "COMBO_KEY" in r.message] + assert len(warning_records) == 1 + # The missing name appears exactly once in that warning, not three. + assert warning_records[0].message.count("MISSING") == 1 + + +def test_apply_launcher_env_defaults_to_real_os_environ(monkeypatch): + """Without an explicit ``env`` arg, the function operates on + ``os.environ`` directly. Use monkeypatch so the test's env stays + isolated. + """ + from openbb_platform_api.app.config import apply_launcher_env + + monkeypatch.delenv("LAUNCHER_TEST_KEY", raising=False) + apply_launcher_env({"LAUNCHER_TEST_KEY": "yes"}) + assert os.environ.get("LAUNCHER_TEST_KEY") == "yes" + + +# --------------------------------------------------------------------------- +# merge_launcher_kwargs — [launcher] under CLI (CLI wins) +# --------------------------------------------------------------------------- + + +def test_merge_launcher_kwargs_cli_wins_for_overlapping_keys(): + """CLI values always override the TOML defaults — the whole point + of the layered cascade. + """ + from openbb_platform_api.app.config import merge_launcher_kwargs + + cli = {"port": 7000} + launcher = {"port": 6900, "host": "0.0.0.0"} # noqa: S104 — container deploy fixture + out = merge_launcher_kwargs(cli, launcher) + assert out == {"port": 7000, "host": "0.0.0.0"} # noqa: S104 — container deploy fixture + + +def test_merge_launcher_kwargs_fills_only_missing_keys(): + """Keys absent on CLI take the TOML default; CLI-provided keys are + untouched even when the TOML has the same key. + """ + from openbb_platform_api.app.config import merge_launcher_kwargs + + cli = {"agents-json": "/cli/agents.json"} + launcher = { + "agents-json": "/toml/agents.json", + "host": "0.0.0.0", # noqa: S104 — container deploy fixture + "port": 6900, + } + out = merge_launcher_kwargs(cli, launcher) + assert out["agents-json"] == "/cli/agents.json" + assert out["host"] == "0.0.0.0" # noqa: S104 — container deploy fixture + assert out["port"] == 6900 + + +def test_merge_launcher_kwargs_returns_cli_unchanged_when_no_section(): + """Empty / missing ``[launcher]`` table is a no-op.""" + from openbb_platform_api.app.config import merge_launcher_kwargs + + cli = {"port": 7000} + assert merge_launcher_kwargs(cli, None) == cli + assert merge_launcher_kwargs(cli, {}) == cli + + +def test_merge_launcher_kwargs_replaces_lists_wholesale(): + """No deep-merge for lists — the CLI's list completely replaces the + TOML's. Keeps the exclude-filter semantics simple and predictable. + """ + from openbb_platform_api.app.config import merge_launcher_kwargs + + cli = {"exclude": ["/api/cli/*"]} + launcher = {"exclude": ["/api/toml/*", "/api/other/*"]} + out = merge_launcher_kwargs(cli, launcher) + assert out["exclude"] == ["/api/cli/*"] + + +# --------------------------------------------------------------------------- +# load_launcher_config — uses the core layered loader +# --------------------------------------------------------------------------- + + +def test_load_launcher_config_returns_merged_dict_from_explicit_path(tmp_path): + """Explicit ``--config-file`` path produces a merged dict with + ``[launcher]`` and ``[env]`` tables intact. + """ + from openbb_platform_api.app.config import load_launcher_config + + cfg = tmp_path / "openbb.toml" + cfg.write_text( + '[launcher]\nhost = "0.0.0.0"\nport = 6900\n\n[env]\nOPENBB_API_KEY = "abc"\n' + ) + out = load_launcher_config( + explicit_path=str(cfg), + apply_to_services=False, + apply_to_env=False, + ) + assert out["launcher"] == { + "host": "0.0.0.0", # noqa: S104 — container deploy fixture + "port": 6900, + } + assert out["env"] == {"OPENBB_API_KEY": "abc"} + + +def test_load_launcher_config_no_file_returns_empty_dict(tmp_path, monkeypatch): + """When no TOML exists in the cascade and no explicit path is set, + ``load_launcher_config`` returns an empty / default dict — the + launcher should still boot cleanly. Container-compat check. + """ + from openbb_platform_api.app.config import load_launcher_config + + # CWD is empty + HOME points at empty tmp dir → no cascade hits. + empty_home = tmp_path / "empty_home" + empty_home.mkdir() + empty_cwd = tmp_path / "empty_cwd" + empty_cwd.mkdir() + monkeypatch.setenv("HOME", str(empty_home)) + monkeypatch.chdir(empty_cwd) + + out = load_launcher_config(apply_to_services=False, apply_to_env=False) + assert out.get("launcher") in (None, {}) + assert out.get("env") in (None, {}) + + +# --------------------------------------------------------------------------- +# bootstrap_launcher_config — one-call entry used by main.py +# --------------------------------------------------------------------------- + + +def test_bootstrap_launcher_config_applies_env_section(tmp_path, monkeypatch): + """End-to-end: a config file with ``[env]`` keys + a CLI flag + pointing at it ends up populating ``os.environ``. + """ + from openbb_platform_api.app.config import bootstrap_launcher_config + + cfg = tmp_path / "openbb.toml" + cfg.write_text('[env]\nOPENBB_BOOT_TEST = "yes"\n') + + monkeypatch.delenv("OPENBB_BOOT_TEST", raising=False) + monkeypatch.delenv("OPENBB_API_CONFIG", raising=False) + monkeypatch.delenv("OPENBB_CONFIG", raising=False) + + bootstrap_launcher_config(["--config-file", str(cfg)]) + assert os.environ.get("OPENBB_BOOT_TEST") == "yes" + + +def test_bootstrap_launcher_config_picks_up_api_config_env(tmp_path, monkeypatch): + """Container scenario: no CLI flag, but ``OPENBB_API_CONFIG`` is + set in the container env. Bootstrap must pick that up. + """ + from openbb_platform_api.app.config import bootstrap_launcher_config + + cfg = tmp_path / "container.toml" + cfg.write_text('[env]\nOPENBB_CONTAINER_TEST = "yes"\n') + + monkeypatch.setenv("OPENBB_API_CONFIG", str(cfg)) + monkeypatch.delenv("OPENBB_CONTAINER_TEST", raising=False) + + bootstrap_launcher_config([]) + assert os.environ.get("OPENBB_CONTAINER_TEST") == "yes" + + +def test_load_launcher_config_explicit_missing_path_is_tolerated(tmp_path): + """A missing file at the explicit path is tolerated — the cascade + just falls through to the next layer. Only parse failures escalate. + Important for setups where the operator templated a path that + doesn't always exist (e.g. multi-stage container builds). + """ + from openbb_platform_api.app.config import load_launcher_config + + missing = tmp_path / "not_here.toml" + out = load_launcher_config( + explicit_path=str(missing), + apply_to_services=False, + apply_to_env=False, + ) + # No crash; returns whatever the cascade has (likely empty). + assert isinstance(out, dict) + + +def test_bootstrap_launcher_config_malformed_toml_raises_loudly(tmp_path): + """A malformed TOML must surface an error — silent corruption + is worse than a clear startup crash. + """ + from openbb_platform_api.app.config import bootstrap_launcher_config + + cfg = tmp_path / "broken.toml" + cfg.write_text("[launcher\nhost = bad") + + with pytest.raises(Exception): + bootstrap_launcher_config(["--config-file", str(cfg)]) + + +# --------------------------------------------------------------------------- +# parse_args integration — [launcher] flows through as fallback values +# --------------------------------------------------------------------------- + + +def test_parse_args_uses_launcher_section_for_missing_cli_flags(tmp_path, monkeypatch): + """A ``[launcher]`` section in the TOML provides defaults for any + CLI flag not explicitly set. CLI-supplied flags still win. + """ + from openbb_platform_api.app.args import parse_args + + cfg = tmp_path / "openbb.toml" + cfg.write_text( + '[launcher]\nhost = "0.0.0.0"\nport = 7000\nagents-json = "/toml/agents.json"\n' + ) + monkeypatch.setattr( + sys, + "argv", + [ + "openbb-api", + "--config-file", + str(cfg), + "--port", + "9999", # CLI override + ], + ) + + out = parse_args() + assert out["host"] == "0.0.0.0" # noqa: S104 — from TOML container deploy fixture + assert out["port"] == "9999" # CLI wins + # ``agents-json`` flows through the TOML (path may be normalized). + assert "/toml/agents.json" in out.get("agents-json", "") + + +def test_parse_args_strips_config_file_from_returned_kwargs(tmp_path, monkeypatch): + """``--config-file`` is consumed by the config layer; it must NOT + leak into the kwargs that get passed to ``uvicorn.run``. + """ + from openbb_platform_api.app.args import parse_args + + cfg = tmp_path / "openbb.toml" + cfg.write_text('[launcher]\nhost = "0.0.0.0"\n') + monkeypatch.setattr(sys, "argv", ["openbb-api", "--config-file", str(cfg)]) + + out = parse_args() + assert "config-file" not in out + assert "config_file" not in out + + +def test_bootstrapped_config_cache_round_trips(tmp_path, monkeypatch): + """``bootstrap_launcher_config`` stashes the merged config so + later boot phases (``parse_args``, middleware registration) can + read the same value without re-running cascade discovery — + important because only the bootstrap call sniffs ``--config-file`` + from argv. Without this cache, ``app.py`` would have no way to + find the same explicit path. + """ + from openbb_platform_api.app.config import ( + bootstrap_launcher_config, + get_bootstrapped_config, + reset_bootstrapped_config, + ) + + cfg = tmp_path / "openbb.toml" + cfg.write_text('[middleware]\nhooks = ["my_pkg:my_mw"]\n[launcher]\nport = 9999\n') + monkeypatch.delenv("OPENBB_API_CONFIG", raising=False) + monkeypatch.delenv("OPENBB_CONFIG", raising=False) + + reset_bootstrapped_config() + assert get_bootstrapped_config() == {} + + bootstrap_launcher_config(["--config-file", str(cfg)]) + cached = get_bootstrapped_config() + assert cached["middleware"]["hooks"] == ["my_pkg:my_mw"] + assert cached["launcher"]["port"] == 9999 + + reset_bootstrapped_config() + assert get_bootstrapped_config() == {} diff --git a/openbb_platform/extensions/platform_api/tests/app/test_middleware.py b/openbb_platform/extensions/platform_api/tests/app/test_middleware.py new file mode 100644 index 00000000000..d4cf1f3415c --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/app/test_middleware.py @@ -0,0 +1,267 @@ +"""Tests for ``openbb_platform_api.app.middleware`` — HTTP middleware +hook entrypoints loaded from the launcher TOML. +""" + +from __future__ import annotations + +import sys +import types +from unittest.mock import MagicMock + +import pytest + +# --------------------------------------------------------------------------- +# Helpers — register a module on sys.modules so module:attr resolution works +# --------------------------------------------------------------------------- + + +@pytest.fixture +def fake_middleware_module(request): + """Register a test module under a unique name so each test gets a + clean slate. Cleans up sys.modules afterwards. + """ + name = f"test_mw_{request.node.name}" + module = types.ModuleType(name) + sys.modules[name] = module + yield name, module + sys.modules.pop(name, None) + + +# --------------------------------------------------------------------------- +# _resolve_entrypoint — module:attr lookup +# --------------------------------------------------------------------------- + + +def test_resolve_entrypoint_picks_module_attr(fake_middleware_module): + """``module:attr`` resolves to the named attribute.""" + from openbb_platform_api.app.middleware import _resolve_entrypoint + + name, module = fake_middleware_module + sentinel = object() + module.my_callable = sentinel # type: ignore[attr-defined] + assert _resolve_entrypoint(f"{name}:my_callable") is sentinel + + +def test_resolve_entrypoint_handles_dotted_attribute(fake_middleware_module): + """``module:Outer.inner`` walks the dotted attribute path — + method-on-class references work without a separate import. + """ + from openbb_platform_api.app.middleware import _resolve_entrypoint + + name, module = fake_middleware_module + + class Outer: + @staticmethod + async def inner(request, call_next): + return await call_next(request) + + module.Outer = Outer # type: ignore[attr-defined] + fn = _resolve_entrypoint(f"{name}:Outer.inner") + assert fn is Outer.inner + + +def test_resolve_entrypoint_rejects_path_without_colon(): + """The entrypoint format is enforced — a missing colon is a + config error, not silently treated as a bare module name. + """ + from openbb_platform_api.app.middleware import _resolve_entrypoint + + with pytest.raises(ValueError, match="module:attr"): + _resolve_entrypoint("just_a_module") + + +def test_resolve_entrypoint_raises_helpful_import_error(): + """Missing module → ImportError with the entrypoint string in the + message so deploy logs name the bad reference. + """ + from openbb_platform_api.app.middleware import _resolve_entrypoint + + with pytest.raises(ImportError, match="nonexistent_pkg"): + _resolve_entrypoint("nonexistent_pkg.module:fn") + + +def test_resolve_entrypoint_raises_helpful_attribute_error( + fake_middleware_module, +): + """Module loads but attribute is missing → AttributeError naming + the offending entrypoint string. + """ + from openbb_platform_api.app.middleware import _resolve_entrypoint + + name, _module = fake_middleware_module + with pytest.raises(AttributeError, match="missing_attr"): + _resolve_entrypoint(f"{name}:missing_attr") + + +# --------------------------------------------------------------------------- +# _validate_middleware_callable — signature checks +# --------------------------------------------------------------------------- + + +def test_validate_middleware_callable_accepts_async_two_arg(): + """The canonical Starlette HTTP middleware signature passes.""" + from openbb_platform_api.app.middleware import _validate_middleware_callable + + async def good(request, call_next): + return await call_next(request) + + _validate_middleware_callable(good, "tests:good") + + +def test_validate_middleware_callable_rejects_non_callable(): + """A plain object that happens to be referenced as middleware + fails with a clear type message. + """ + from openbb_platform_api.app.middleware import _validate_middleware_callable + + with pytest.raises(TypeError, match="non-callable"): + _validate_middleware_callable(object(), "tests:bad") + + +def test_validate_middleware_callable_rejects_sync_function(): + """Sync middleware silently breaks the Starlette chain — reject + at config-load time so the failure mode is a startup error, + not a runtime mystery. + """ + from openbb_platform_api.app.middleware import _validate_middleware_callable + + def sync_fn(request, call_next): + return call_next(request) + + with pytest.raises(TypeError, match="async function"): + _validate_middleware_callable(sync_fn, "tests:sync_fn") + + +def test_validate_middleware_callable_rejects_too_few_args(): + """Single-arg callable can't match ``(request, call_next)``.""" + from openbb_platform_api.app.middleware import _validate_middleware_callable + + async def too_few(request): + return None + + with pytest.raises(TypeError, match="positional parameter"): + _validate_middleware_callable(too_few, "tests:too_few") + + +# --------------------------------------------------------------------------- +# apply_http_middleware_hooks — registration + ordering +# --------------------------------------------------------------------------- + + +def test_apply_hooks_returns_empty_for_no_hooks(): + """Empty / None hooks list is a no-op, returns ``[]``.""" + from openbb_platform_api.app.middleware import apply_http_middleware_hooks + + app = MagicMock() + assert apply_http_middleware_hooks(app, None) == [] + assert apply_http_middleware_hooks(app, []) == [] + app.middleware.assert_not_called() + + +def test_apply_hooks_rejects_non_list_input(): + """A scalar ``hooks`` value (string, dict, …) is a config error + that should fail loudly rather than be silently ignored. + """ + from openbb_platform_api.app.middleware import apply_http_middleware_hooks + + app = MagicMock() + with pytest.raises(TypeError, match="must be a list"): + apply_http_middleware_hooks(app, "single_hook") # type: ignore[arg-type] + + +def test_apply_hooks_rejects_non_string_entries(): + """Mixed-type entries fail loudly — keeps TOML parse errors from + silently producing partial registrations. + """ + from openbb_platform_api.app.middleware import apply_http_middleware_hooks + + app = MagicMock() + with pytest.raises(TypeError, match="must be strings"): + apply_http_middleware_hooks(app, [123]) # type: ignore[list-item] + + +def test_apply_hooks_registers_in_outer_to_inner_order(fake_middleware_module): + """TOML list order = outermost to innermost. Internally the + launcher registers in REVERSE so Starlette's stack ends up with + the first entry on top (where it sees requests first). + """ + from openbb_platform_api.app.middleware import apply_http_middleware_hooks + + name, module = fake_middleware_module + + async def outer(request, call_next): + return await call_next(request) + + async def inner(request, call_next): + return await call_next(request) + + module.outer = outer # type: ignore[attr-defined] + module.inner = inner # type: ignore[attr-defined] + + app = MagicMock() + decorator = MagicMock(side_effect=lambda fn: fn) + app.middleware.return_value = decorator + + out = apply_http_middleware_hooks(app, [f"{name}:outer", f"{name}:inner"]) + assert out == [f"{name}:outer", f"{name}:inner"] + # ``app.middleware("http")`` called twice (once per hook). + assert app.middleware.call_count == 2 + # Registration order is REVERSED — inner first, outer last — + # so Starlette's ``add_middleware`` stack ends up with ``outer`` + # on top. + registered_funcs = [call.args[0] for call in decorator.call_args_list] + assert registered_funcs == [inner, outer] + + +def test_apply_hooks_end_to_end_on_real_fastapi_app(fake_middleware_module): + """Integration: register a hook on a real FastAPI app and verify + it actually intercepts the request via TestClient. This is the + only assurance that the registration call shape is right. + """ + from fastapi import FastAPI + from fastapi.testclient import TestClient + + from openbb_platform_api.app.middleware import apply_http_middleware_hooks + + name, module = fake_middleware_module + + invocations: list[str] = [] + + async def tag_middleware(request, call_next): + invocations.append("middleware-in") + response = await call_next(request) + invocations.append("middleware-out") + response.headers["X-Tagged"] = "yes" + return response + + module.tag_middleware = tag_middleware # type: ignore[attr-defined] + + app = FastAPI() + + @app.get("/ping") + async def ping(): + invocations.append("handler") + return {"ok": True} + + apply_http_middleware_hooks(app, [f"{name}:tag_middleware"]) + + client = TestClient(app) + response = client.get("/ping") + assert response.status_code == 200 + assert response.headers.get("X-Tagged") == "yes" + assert invocations == ["middleware-in", "handler", "middleware-out"] + + +def test_apply_hooks_failure_during_registration_raises_immediately(): + """A bad reference in the middle of the list aborts the whole + registration — preserves "all-or-nothing" semantics so we never + end up with a partially configured app at startup. + """ + from openbb_platform_api.app.middleware import apply_http_middleware_hooks + + app = MagicMock() + with pytest.raises(ImportError): + apply_http_middleware_hooks(app, ["nonexistent_pkg.never_loaded:fn"]) + # Nothing got registered — the resolution failed before any + # ``app.middleware`` calls. + app.middleware.assert_not_called() diff --git a/openbb_platform/extensions/platform_api/tests/app/test_spec.py b/openbb_platform/extensions/platform_api/tests/app/test_spec.py new file mode 100644 index 00000000000..cbb05ad9bf0 --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/app/test_spec.py @@ -0,0 +1,2127 @@ +"""Tests for ``openbb_platform_api.app.spec`` — spec loading, OpenAPI +synthesis, proxy app construction, and end-to-end widgets.json +generation against a spec-built app. +""" + +import json +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from openbb_platform_api.app.spec import _content_hash + + +def _stamp(spec: dict) -> dict: + """Stamp ``spec`` with a fresh ``content_sha256``. + + Every spec carries a ``content_sha256`` — the launcher refuses + to load specs without one. This helper computes the canonical + hash AFTER any field edits the test makes, matching the + openbb-cli generator's stamp-at-write semantics. + """ + spec = {k: v for k, v in spec.items() if k != "content_sha256"} + spec["content_sha256"] = _content_hash(spec) + return spec + + +SAMPLE_SPEC = { + "version": 5, + "base_url": "https://upstream.example.com/api/v1", + "api_prefix": "/api/v1", + "api_version": "3.1.0", + "generated_at": "2026-05-07T00:00:00Z", + "commands": { + "equity.price.historical": { + "url_path": "/api/v1/equity/price/historical", + "method": "get", + "description": "Historical OHLCV bars.", + "providers": ["fmp"], + "parameters": [ + { + "name": "symbol", + "in": "query", + "type": "string", + "is_list": False, + "required": True, + "default": None, + "choices": [], + "example": "AAPL", + "help": "The ticker symbol.", + "providers": ["fmp"], + }, + { + "name": "interval", + "in": "query", + "type": "string", + "is_list": False, + "required": False, + "default": "1d", + "choices": ["1m", "5m", "1h", "1d"], + "example": None, + "help": "Bar size.", + "providers": ["fmp"], + }, + ], + "request_body_schema": None, + "response_schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date", + "title": "Date", + }, + "close": { + "type": "number", + "title": "Close", + }, + }, + }, + } + }, + }, + }, + "user.preferences.update": { + "url_path": "/api/v1/user/preferences", + "method": "post", + "description": "Update user preferences.", + "providers": [], + "parameters": [], + "request_body_schema": { + "type": "object", + "properties": {"theme": {"type": "string", "enum": ["light", "dark"]}}, + }, + "response_schema": {"type": "boolean"}, + }, + }, + "routers": {}, + "reference": {}, +} +SAMPLE_SPEC["content_sha256"] = _content_hash(SAMPLE_SPEC) + + +# --------------------------------------------------------------------------- +# load_spec — parse + structural validation +# --------------------------------------------------------------------------- + + +def test_load_spec_parses_well_formed_file(tmp_path): + """Round-trip a well-formed spec through ``load_spec``.""" + from openbb_platform_api.app.spec import load_spec + + p = tmp_path / "cli.spec" + p.write_text(json.dumps(SAMPLE_SPEC)) + out = load_spec(p) + assert out["base_url"] == SAMPLE_SPEC["base_url"] + assert "equity.price.historical" in out["commands"] + + +def test_load_spec_raises_for_missing_file(tmp_path): + """A missing path yields ``FileNotFoundError`` with the path in the + message — operations error that needs to surface fast. + """ + from openbb_platform_api.app.spec import load_spec + + with pytest.raises(FileNotFoundError, match="missing"): + load_spec(tmp_path / "missing.spec") + + +def test_load_spec_raises_for_invalid_json(tmp_path): + """Malformed JSON → ``ValueError`` (not ``json.JSONDecodeError``) + so callers can treat all spec errors uniformly. + """ + from openbb_platform_api.app.spec import load_spec + + p = tmp_path / "broken.spec" + p.write_text("{not json") + with pytest.raises(ValueError, match="not valid JSON"): + load_spec(p) + + +def test_load_spec_raises_for_unsupported_version(tmp_path): + """Spec from a future / unknown version is rejected with a clear + ``regenerate`` hint — protects against silent breakage when the + spec format evolves. + """ + from openbb_platform_api.app.spec import load_spec + + p = tmp_path / "future.spec" + spec = {**SAMPLE_SPEC, "version": 999} + p.write_text(json.dumps(spec)) + with pytest.raises(ValueError, match="Unsupported spec version"): + load_spec(p) + + +def test_load_spec_raises_for_missing_base_url(tmp_path): + """Without ``base_url`` the proxy has nowhere to forward — refuse + to load instead of silently producing a non-functional launcher. + """ + from openbb_platform_api.app.spec import load_spec + + p = tmp_path / "no_base.spec" + bad = {k: v for k, v in SAMPLE_SPEC.items() if k != "base_url"} + p.write_text(json.dumps(bad)) + with pytest.raises(ValueError, match="base_url"): + load_spec(p) + + +def test_load_spec_raises_for_missing_commands_table(tmp_path): + """Without ``commands`` there's nothing to proxy — refuse load.""" + from openbb_platform_api.app.spec import load_spec + + p = tmp_path / "no_cmds.spec" + bad = {k: v for k, v in SAMPLE_SPEC.items() if k != "commands"} + p.write_text(json.dumps(bad)) + with pytest.raises(ValueError, match="commands"): + load_spec(p) + + +def test_load_spec_rejects_non_object_top_level(tmp_path): + """A top-level array (or any non-object) is structurally wrong.""" + from openbb_platform_api.app.spec import load_spec + + p = tmp_path / "list.spec" + p.write_text("[]") + with pytest.raises(ValueError, match="top-level"): + load_spec(p) + + +# --------------------------------------------------------------------------- +# Spec provenance + integrity verification +# --------------------------------------------------------------------------- + + +def test_load_spec_rejects_structurally_invalid_command(tmp_path): + """Pydantic schema rejects a command missing ``url_path``/``method``.""" + from openbb_platform_api.app.spec import load_spec + + p = tmp_path / "bad.spec" + p.write_text( + json.dumps( + { + "version": 5, + "base_url": "https://x", + "commands": {"oops": {"description": "no url_path/method"}}, + } + ) + ) + with pytest.raises(ValueError, match="does not conform to the expected schema"): + load_spec(p) + + +def test_load_spec_accepts_optional_provenance_fields(tmp_path): + """``generator``/``generated_at``/``source_url``/``api_version`` are + optional; their presence is preserved on the returned dict. + """ + from openbb_platform_api.app.spec import load_spec + + payload = _stamp( + { + "version": 5, + "base_url": "https://upstream.example.com", + "api_version": "3.1.0", + "generator": "openbb-cli==2.0.0", + "generated_at": "2026-05-08T00:00:00Z", + "source_url": "https://upstream.example.com/openapi.json", + "commands": { + "ping": {"url_path": "/v1/ping", "method": "get", "parameters": []} + }, + } + ) + p = tmp_path / "p.spec" + p.write_text(json.dumps(payload)) + spec = load_spec(p) + assert spec["generator"] == "openbb-cli==2.0.0" + assert spec["generated_at"] == "2026-05-08T00:00:00Z" + assert spec["source_url"] == "https://upstream.example.com/openapi.json" + assert spec["api_version"] == "3.1.0" + + +def test_load_spec_verifies_matching_content_hash(tmp_path): + """A spec carrying its own correctly-computed SHA-256 loads cleanly.""" + from openbb_platform_api.app.spec import load_spec + + payload = _stamp( + { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + p = tmp_path / "h.spec" + p.write_text(json.dumps(payload)) + out = load_spec(p) + assert out["content_sha256"] == payload["content_sha256"] + + +def test_load_spec_rejects_tampered_content_hash(tmp_path): + """A spec whose ``content_sha256`` no longer matches its body fails.""" + from openbb_platform_api.app.spec import load_spec + + payload = { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + "content_sha256": "0" * 64, # deliberately wrong + } + p = tmp_path / "tampered.spec" + p.write_text(json.dumps(payload)) + with pytest.raises(ValueError, match="failed integrity check"): + load_spec(p) + + +def test_load_spec_rejects_spec_missing_content_sha256(tmp_path): + """``content_sha256`` is REQUIRED — every openbb-cli spec carries + one, so an absent field indicates corruption / forgery and the + launcher refuses to load. + """ + from openbb_platform_api.app.spec import load_spec + + payload = { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + p = tmp_path / "no-hash.spec" + p.write_text(json.dumps(payload)) + with pytest.raises(ValueError, match="content_sha256"): + load_spec(p) + + +def test_content_hash_is_stable_across_key_order(): + """Hashing is canonical (sorted keys) so payload dict ordering doesn't + move the digest. + """ + from openbb_platform_api.app.spec import _content_hash + + a = {"version": 5, "base_url": "x", "commands": {}} + b = {"commands": {}, "base_url": "x", "version": 5} + assert _content_hash(a) == _content_hash(b) + + +def test_content_hash_excludes_sha256_field(): + """Adding/removing the ``content_sha256`` field doesn't change the hash.""" + from openbb_platform_api.app.spec import _content_hash + + a = {"version": 5, "base_url": "x", "commands": {}} + b = {**a, "content_sha256": "ignored"} + assert _content_hash(a) == _content_hash(b) + + +def test_load_spec_accepts_deploy_config_hash_pin(tmp_path): + """``expected_content_sha256`` supplied (matching) → loads cleanly.""" + from openbb_platform_api.app.spec import load_spec + + payload = _stamp( + { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + p = tmp_path / "deploy-pinned.spec" + p.write_text(json.dumps(payload)) + spec = load_spec(p, expected_content_sha256=payload["content_sha256"]) + assert spec is not None + + +def test_load_spec_rejects_when_deploy_config_pin_mismatches(tmp_path): + """``expected_content_sha256`` mismatch → ValueError with explicit + ``deploy-config pin check`` wording so ops can distinguish a + config-pin failure from in-file tampering. + """ + from openbb_platform_api.app.spec import load_spec + + payload = _stamp( + { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + p = tmp_path / "drift.spec" + p.write_text(json.dumps(payload)) + with pytest.raises(ValueError, match="deploy-config pin check"): + load_spec(p, expected_content_sha256="0" * 64) + + +def test_load_spec_deploy_pin_works_alongside_in_file_hash(tmp_path): + """In-file ``content_sha256`` (always present) AND + ``expected_content_sha256`` matching → both checks pass. + """ + from openbb_platform_api.app.spec import load_spec + + payload = _stamp( + { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + p = tmp_path / "double-checked.spec" + p.write_text(json.dumps(payload)) + out = load_spec(p, expected_content_sha256=payload["content_sha256"]) + assert out["content_sha256"] == payload["content_sha256"] + + +# --------------------------------------------------------------------------- +# build_apps_from_specs — multi-spec mounting +# --------------------------------------------------------------------------- + + +def _make_spec_dict(base_url: str = "https://x", cmd_path: str = "/p") -> dict: + """Helper: build + stamp a minimal valid spec for multi-spec tests.""" + return _stamp( + { + "version": 5, + "base_url": base_url, + "commands": { + "p": {"url_path": cmd_path, "method": "get", "parameters": []} + }, + } + ) + + +def test_build_apps_from_specs_mounts_each_spec_at_named_prefix(): + """Each entry mounts under its dict-key prefix by default.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + parent = build_apps_from_specs( + { + "equity": {"spec": _make_spec_dict()}, + "crypto": {"spec": _make_spec_dict()}, + } + ) + assert "/equity" in parent.state.openbb_specs + assert "/crypto" in parent.state.openbb_specs + + +def test_build_apps_from_specs_explicit_mount_overrides_default(): + """``mount`` in the entry overrides the default ``/`` prefix.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + parent = build_apps_from_specs( + {"equity": {"spec": _make_spec_dict(), "mount": "/markets/equity"}} + ) + assert "/markets/equity" in parent.state.openbb_specs + + +def test_build_apps_from_specs_normalizes_mount_without_leading_slash(): + """``mount = "stocks"`` (no leading slash) gets normalized to ``/stocks``.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + parent = build_apps_from_specs( + {"x": {"spec": _make_spec_dict(), "mount": "stocks"}} + ) + assert "/stocks" in parent.state.openbb_specs + + +def test_build_apps_from_specs_rejects_mount_collision(): + """Two specs at the same mount → ValueError.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + with pytest.raises(ValueError, match="mount collision"): + build_apps_from_specs( + { + "a": {"spec": _make_spec_dict(), "mount": "/dup"}, + "b": {"spec": _make_spec_dict(), "mount": "/dup"}, + } + ) + + +def test_build_apps_from_specs_rejects_empty_config(): + """An empty specs dict raises with a clear message.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + with pytest.raises(ValueError, match="at least one spec entry"): + build_apps_from_specs({}) + + +def test_build_apps_from_specs_rejects_non_dict_entry(): + """Non-dict entries fail fast.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + with pytest.raises(TypeError, match="must be a dict"): + build_apps_from_specs({"x": "not a dict"}) # type: ignore[arg-type] + + +def test_build_apps_from_specs_rejects_entry_missing_spec_field(): + """Entry without the required ``spec`` key fails.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + with pytest.raises(ValueError, match="missing the required 'spec' field"): + build_apps_from_specs({"x": {"mount": "/x"}}) + + +def test_build_apps_from_specs_state_carries_provenance(): + """Each mount's state snapshot carries the per-spec provenance.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + spec_a = _stamp( + { + "version": 5, + "base_url": "https://a.example.com", + "generator": "openbb-cli==2.0.0", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + parent = build_apps_from_specs({"a": {"spec": spec_a, "spec_name": "a.spec"}}) + snapshot = parent.state.openbb_specs["/a"] + assert snapshot["name"] == "a" + assert snapshot["spec_name"] == "a.spec" + assert snapshot["base_url"] == "https://a.example.com" + assert snapshot["generator"] == "openbb-cli==2.0.0" + assert snapshot["content_sha256"] == spec_a["content_sha256"] + + +def test_build_apps_from_specs_per_spec_middleware_hooks_applied(monkeypatch): + """Per-spec ``middleware_hooks`` flow through to the sub-app's + middleware stack. + """ + import sys + import types + + from openbb_platform_api.app.spec import build_apps_from_specs + + mod = types.ModuleType("test_multi_spec_hooks_mw_papi") + + async def mw_hook(request, call_next): # pragma: no cover + return await call_next(request) + + mod.mw_hook = mw_hook + monkeypatch.setitem(sys.modules, "test_multi_spec_hooks_mw_papi", mod) + + parent = build_apps_from_specs( + { + "a": { + "spec": _make_spec_dict(), + "middleware_hooks": ["test_multi_spec_hooks_mw_papi:mw_hook"], + } + } + ) + from starlette.routing import Mount + + for route in parent.routes: + if isinstance(route, Mount) and route.name == "a": + sub_app = route.app + assert any( + getattr(m, "kwargs", {}).get("dispatch") is mw_hook + for m in sub_app.user_middleware + ) + break + else: + raise AssertionError("Expected a mounted sub-app named 'a'.") + + +def test_build_apps_from_specs_per_spec_auth_hooks_applied(monkeypatch): + """Per-spec ``auth_hooks`` flow through identically.""" + import sys + import types + + from openbb_platform_api.app.spec import build_apps_from_specs + + mod = types.ModuleType("test_multi_spec_hooks_auth_papi") + + async def auth_hook(request, call_next): # pragma: no cover + return await call_next(request) + + mod.auth_hook = auth_hook + monkeypatch.setitem(sys.modules, "test_multi_spec_hooks_auth_papi", mod) + + parent = build_apps_from_specs( + { + "a": { + "spec": _make_spec_dict(), + "auth_hooks": ["test_multi_spec_hooks_auth_papi:auth_hook"], + } + } + ) + from starlette.routing import Mount + + for route in parent.routes: + if isinstance(route, Mount) and route.name == "a": + sub_app = route.app + assert any( + getattr(m, "kwargs", {}).get("dispatch") is auth_hook + for m in sub_app.user_middleware + ) + break + else: + raise AssertionError("Expected a mounted sub-app named 'a'.") + + +def test_build_apps_from_specs_rejects_non_list_hook_table(): + """``middleware_hooks`` must be a list — non-list raises TypeError.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + with pytest.raises(TypeError, match="must be a list"): + build_apps_from_specs( + {"a": {"spec": _make_spec_dict(), "middleware_hooks": "not a list"}} + ) + + +def test_build_apps_from_specs_rejects_non_string_hook_entry(): + """Non-string hook entries inside the list raise TypeError.""" + from openbb_platform_api.app.spec import build_apps_from_specs + + with pytest.raises(TypeError, match="hook entries must be strings"): + build_apps_from_specs({"a": {"spec": _make_spec_dict(), "auth_hooks": [123]}}) + + +# --------------------------------------------------------------------------- +# parse_args wiring — multi-spec +# --------------------------------------------------------------------------- + + +def test_collect_multi_spec_entries_filters_non_spec_subtables(): + """Sibling tables under ``[spec]`` (``headers``/``auth``/...) + aren't mistaken for nested spec entries. + """ + from openbb_platform_api.app.args import _collect_multi_spec_entries + + section = { + "path": "/single.spec", + "headers": {"Authorization": "x"}, + "equity": {"path": "/eq.spec"}, + "scalar_value": "ignored", + } + out = _collect_multi_spec_entries(section) + assert out == {"equity": {"path": "/eq.spec"}} + + +def test_parse_args_loads_multi_spec_from_named_subtables(tmp_path): + """``[spec.NAME]`` subtables build a parent with each spec mounted.""" + import sys + from unittest.mock import patch + + from openbb_platform_api.app.args import parse_args + from openbb_platform_api.app.config import reset_bootstrapped_config + + payload_a = _stamp( + { + "version": 5, + "base_url": "https://a.example.com", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + payload_b = _stamp( + { + "version": 5, + "base_url": "https://b.example.com", + "commands": {"q": {"url_path": "/q", "method": "get", "parameters": []}}, + } + ) + spec_a = tmp_path / "a.spec" + spec_a.write_text(json.dumps(payload_a)) + spec_b = tmp_path / "b.spec" + spec_b.write_text(json.dumps(payload_b)) + + cfg = tmp_path / "openbb.toml" + cfg.write_text( + f""" +[spec.equity] +path = "{spec_a.as_posix()}" + +[spec.crypto] +path = "{spec_b.as_posix()}" +""" + ) + + reset_bootstrapped_config() + try: + with patch.object(sys, "argv", ["openbb-api", "--config-file", str(cfg)]): + out = parse_args() + state = out["app"].state + assert "/equity" in state.openbb_specs + assert "/crypto" in state.openbb_specs + finally: + reset_bootstrapped_config() + + +def test_parse_args_multi_spec_relative_path_resolved_against_cwd( + tmp_path, monkeypatch +): + """Per-spec relative paths resolve against CWD.""" + import sys + from unittest.mock import patch + + from openbb_platform_api.app.args import parse_args + from openbb_platform_api.app.config import reset_bootstrapped_config + + payload = _stamp( + { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + spec = tmp_path / "rel.spec" + spec.write_text(json.dumps(payload)) + cfg = tmp_path / "openbb.toml" + cfg.write_text( + """ +[spec.rel] +path = "rel.spec" +""" + ) + monkeypatch.chdir(tmp_path) + + reset_bootstrapped_config() + try: + with patch.object(sys, "argv", ["openbb-api", "--config-file", str(cfg)]): + out = parse_args() + assert "/rel" in out["app"].state.openbb_specs + finally: + reset_bootstrapped_config() + + +def test_parse_args_multi_spec_per_spec_content_sha256_pin(tmp_path): + """Each ``[spec.NAME]`` subtable's ``content_sha256`` flows to that + spec's ``load_spec`` — a mismatch on one entry kills startup. + """ + import sys + from unittest.mock import patch + + from openbb_platform_api.app.args import parse_args + from openbb_platform_api.app.config import reset_bootstrapped_config + + payload = _stamp( + { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + spec = tmp_path / "pinned.spec" + spec.write_text(json.dumps(payload)) + cfg = tmp_path / "openbb.toml" + cfg.write_text( + f""" +[spec.equity] +path = "{spec.as_posix()}" +content_sha256 = "{"f" * 64}" +""" + ) + + reset_bootstrapped_config() + try: + with patch.object(sys, "argv", ["openbb-api", "--config-file", str(cfg)]): + with pytest.raises(ValueError, match="deploy-config pin check"): + parse_args() + finally: + reset_bootstrapped_config() + + +def test_parse_args_multi_spec_with_per_spec_hooks(tmp_path, monkeypatch): + """Per-spec auth + middleware hooks land on the sub-app's middleware + stack via the full parse_args → load_spec → build_apps_from_specs + chain. + """ + import sys + import types + from unittest.mock import patch + + from openbb_platform_api.app.args import parse_args + from openbb_platform_api.app.config import reset_bootstrapped_config + + payload = _stamp( + { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + spec = tmp_path / "hooks.spec" + spec.write_text(json.dumps(payload)) + + mod = types.ModuleType("test_multi_spec_args_hooks_papi") + + async def auth(request, call_next): # pragma: no cover + return await call_next(request) + + async def mw(request, call_next): # pragma: no cover + return await call_next(request) + + mod.auth = auth + mod.mw = mw + monkeypatch.setitem(sys.modules, "test_multi_spec_args_hooks_papi", mod) + + cfg = tmp_path / "openbb.toml" + cfg.write_text( + f""" +[spec.equity] +path = "{spec.as_posix()}" +[spec.equity.auth] +hooks = ["test_multi_spec_args_hooks_papi:auth"] +[spec.equity.middleware] +hooks = ["test_multi_spec_args_hooks_papi:mw"] +""" + ) + + reset_bootstrapped_config() + try: + with patch.object(sys, "argv", ["openbb-api", "--config-file", str(cfg)]): + out = parse_args() + finally: + reset_bootstrapped_config() + + from starlette.routing import Mount + + for route in out["app"].routes: + if isinstance(route, Mount) and route.name == "equity": + sub_app = route.app + dispatchers = { + getattr(m, "kwargs", {}).get("dispatch") + for m in sub_app.user_middleware + } + assert auth in dispatchers + assert mw in dispatchers + break + else: + raise AssertionError("Expected mounted sub-app 'equity'.") + + +def test_parse_args_passes_deploy_config_hash_pin_to_load_spec(tmp_path): + """``[spec].content_sha256`` from the deploy TOML is forwarded to + ``load_spec`` — when it mismatches, ``parse_args`` raises with the + pin-check error so a misconfigured deployment fails at startup. + """ + import sys + from unittest.mock import patch + + from openbb_platform_api.app.args import parse_args + from openbb_platform_api.app.config import reset_bootstrapped_config + + # Build a fully-stamped spec; the deploy TOML pins a deliberately + # wrong expected hash to trigger the pin-check failure. + payload = _stamp( + { + "version": 5, + "base_url": "https://x", + "commands": {"p": {"url_path": "/p", "method": "get", "parameters": []}}, + } + ) + spec_file = tmp_path / "pinned.spec" + spec_file.write_text(json.dumps(payload)) + + cfg_file = tmp_path / "openbb.toml" + cfg_file.write_text( + f""" +[spec] +path = "{spec_file.as_posix()}" +content_sha256 = "{"f" * 64}" +""" + ) + + reset_bootstrapped_config() + try: + with patch.object(sys, "argv", ["openbb-api", "--config-file", str(cfg_file)]): + with pytest.raises(ValueError, match="deploy-config pin check"): + parse_args() + finally: + reset_bootstrapped_config() + + +def test_build_app_from_spec_exposes_provenance_metadata(tmp_path): + """Every recorded provenance field flows onto ``app.state`` so + callers (widgets builder, telemetry, custom middleware) can + fingerprint the active spec. + """ + from openbb_platform_api.app.spec import ( + _content_hash, + build_app_from_spec, + load_spec, + ) + + payload = { + "version": 5, + "base_url": "https://upstream.example.com", + "api_version": "3.1.0", + "generator": "openbb-cli==2.0.0", + "generated_at": "2026-05-08T00:00:00Z", + "source_url": "https://upstream.example.com/openapi.json", + "commands": { + "ping": {"url_path": "/v1/ping", "method": "get", "parameters": []} + }, + } + payload["content_sha256"] = _content_hash(payload) + p = tmp_path / "prov.spec" + p.write_text(json.dumps(payload)) + + app = build_app_from_spec(load_spec(p), spec_name="prov.spec") + state = app.state + assert state.openbb_spec_version == 5 + assert state.openbb_spec_generator == "openbb-cli==2.0.0" + assert state.openbb_spec_generated_at == "2026-05-08T00:00:00Z" + assert state.openbb_spec_source_url == "https://upstream.example.com/openapi.json" + assert state.openbb_spec_content_sha256 == payload["content_sha256"] + assert state.openbb_spec_api_version == "3.1.0" + + +# --------------------------------------------------------------------------- +# synthesize_openapi_from_spec — build_json fodder +# --------------------------------------------------------------------------- + + +def test_synthesize_openapi_produces_paths_for_each_command(): + """Every command becomes a ``paths[url_path][method]`` entry.""" + from openbb_platform_api.app.spec import synthesize_openapi_from_spec + + out = synthesize_openapi_from_spec(SAMPLE_SPEC) + assert "/api/v1/equity/price/historical" in out["paths"] + assert "get" in out["paths"]["/api/v1/equity/price/historical"] + assert "post" in out["paths"]["/api/v1/user/preferences"] + + +def test_synthesize_openapi_renests_parameters_into_schema_block(): + """Spec hoists ``type``/``default``/``choices`` to the top of the + parameter dict; OpenAPI nests them under ``schema``. The synthesizer + re-nests so the launcher's widget builder reads them where it + expects. + """ + from openbb_platform_api.app.spec import synthesize_openapi_from_spec + + out = synthesize_openapi_from_spec(SAMPLE_SPEC) + op = out["paths"]["/api/v1/equity/price/historical"]["get"] + interval = next(p for p in op["parameters"] if p["name"] == "interval") + assert interval["schema"]["type"] == "string" + assert interval["schema"]["default"] == "1d" + assert interval["schema"]["enum"] == ["1m", "5m", "1h", "1d"] + assert interval["required"] is False + + +def test_synthesize_openapi_marks_required_params(): + """``required: true`` round-trips.""" + from openbb_platform_api.app.spec import synthesize_openapi_from_spec + + out = synthesize_openapi_from_spec(SAMPLE_SPEC) + op = out["paths"]["/api/v1/equity/price/historical"]["get"] + symbol = next(p for p in op["parameters"] if p["name"] == "symbol") + assert symbol["required"] is True + + +def test_synthesize_openapi_translates_is_list_to_array_schema(): + """A ``is_list: true`` spec param becomes ``type: "array"`` with + ``items`` carrying the original scalar type. + """ + from openbb_platform_api.app.spec import synthesize_openapi_from_spec + + spec = { + **SAMPLE_SPEC, + "commands": { + "x": { + "url_path": "/x", + "method": "get", + "parameters": [ + { + "name": "tickers", + "in": "query", + "type": "string", + "is_list": True, + "required": False, + "default": None, + "choices": [], + } + ], + } + }, + } + out = synthesize_openapi_from_spec(spec) + p = out["paths"]["/x"]["get"]["parameters"][0] + assert p["schema"] == {"type": "array", "items": {"type": "string"}} + + +def test_synthesize_openapi_skips_command_without_url_path(): + """A malformed command (missing ``url_path``) is dropped instead + of crashing the synthesis — defensive against partial specs. + """ + from openbb_platform_api.app.spec import synthesize_openapi_from_spec + + spec = { + **SAMPLE_SPEC, + "commands": {"bad": {"method": "get"}, **SAMPLE_SPEC["commands"]}, + } + out = synthesize_openapi_from_spec(spec) + # Only the well-formed entries land. + paths = list(out["paths"]) + assert "/api/v1/equity/price/historical" in paths + # No path for the bad command. + assert all("bad" not in p for p in paths) + + +def test_synthesize_openapi_skips_command_with_unknown_method(): + """``method: delete`` (or anything other than get/post) is dropped + — the launcher only synthesizes Workspace-relevant verbs. + """ + from openbb_platform_api.app.spec import synthesize_openapi_from_spec + + spec = { + **SAMPLE_SPEC, + "commands": {"del": {"url_path": "/x", "method": "delete", "parameters": []}}, + } + out = synthesize_openapi_from_spec(spec) + assert "/x" not in out["paths"] + + +def test_synthesize_openapi_carries_response_schema_into_responses_block(): + """The spec's ``response_schema`` lands at + ``paths[...].get.responses["200"].content["application/json"].schema`` + — exactly where the launcher's column auto-detection reads it. + """ + from openbb_platform_api.app.spec import synthesize_openapi_from_spec + + out = synthesize_openapi_from_spec(SAMPLE_SPEC) + op = out["paths"]["/api/v1/equity/price/historical"]["get"] + schema = op["responses"]["200"]["content"]["application/json"]["schema"] + assert "properties" in schema + assert "results" in schema["properties"] + + +def test_synthesize_openapi_handles_post_request_body_schema(): + """POST commands get their ``request_body_schema`` wired into + ``requestBody.content."application/json".schema``. + """ + from openbb_platform_api.app.spec import synthesize_openapi_from_spec + + out = synthesize_openapi_from_spec(SAMPLE_SPEC) + op = out["paths"]["/api/v1/user/preferences"]["post"] + body = op["requestBody"]["content"]["application/json"]["schema"] + assert body["properties"]["theme"]["enum"] == ["light", "dark"] + + +# --------------------------------------------------------------------------- +# build_app_from_spec — FastAPI synthesis +# --------------------------------------------------------------------------- + + +def test_build_app_registers_one_route_per_command(): + """Each spec command becomes one FastAPI route at its ``url_path``.""" + from openbb_platform_api.app.spec import build_app_from_spec + + app = build_app_from_spec(SAMPLE_SPEC) + paths = {r.path for r in app.routes if hasattr(r, "path")} + assert "/api/v1/equity/price/historical" in paths + assert "/api/v1/user/preferences" in paths + + +def test_build_app_skips_malformed_commands(): + """Commands missing ``url_path`` or with an unsupported method are + silently dropped — partial / forward-compatible specs shouldn't + crash the launcher. + """ + from openbb_platform_api.app.spec import build_app_from_spec + + spec = { + **SAMPLE_SPEC, + "commands": { + "no_url": {"method": "get", "parameters": []}, + "delete_method": { + "url_path": "/api/v1/x", + "method": "delete", + "parameters": [], + }, + **SAMPLE_SPEC["commands"], + }, + } + app = build_app_from_spec(spec) + paths = {r.path for r in app.routes if hasattr(r, "path")} + # The well-formed entries are still present. + assert "/api/v1/equity/price/historical" in paths + # The malformed ones produced no routes. + assert "/api/v1/x" not in paths + + +def test_build_app_records_spec_on_app_state(): + """The original spec + base_url are stashed on ``app.state`` so + callers can introspect what was built. + """ + from openbb_platform_api.app.spec import build_app_from_spec + + app = build_app_from_spec(SAMPLE_SPEC) + assert app.state.openbb_spec is SAMPLE_SPEC + assert app.state.openbb_spec_base_url == SAMPLE_SPEC["base_url"] + + +def test_build_app_openapi_parameters_drive_widgets_json(): + """End-to-end: a spec-built app's generated OpenAPI feeds the + launcher's ``build_json``, which produces a widget per command. + Regression guard for the whole reason the spec → openapi_extra + bridge exists. + """ + from openbb_platform_api.app.spec import build_app_from_spec + from openbb_platform_api.utils.widgets import build_json + + app = build_app_from_spec(SAMPLE_SPEC) + # Trigger FastAPI's lazy openapi build. + openapi = app.openapi() + out = build_json(openapi, []) + # The historical-price command surfaces as a widget (custom + # provider since the spec doesn't carry the OpenBB provider param + # mechanism — that's fine, the launcher treats it as ``custom``). + historical_widgets = [w for w_id, w in out.items() if "historical" in w_id] + assert historical_widgets, f"No historical widget found in {list(out)}" + widget = historical_widgets[0] + # Parameter metadata flowed through unchanged. + param_names = {p["paramName"] for p in widget.get("params", [])} + assert "symbol" in param_names + assert "interval" in param_names + + +def test_build_app_post_routes_marked_excluded_unless_special_type(): + """POST routes (other than ssrm/omni/multi-file) get added to the + exclude filter by ``build_json`` — same treatment as a real + in-process app's POST endpoints. Regression guard against the spec + proxy generating ghost POST widgets. + """ + from openbb_platform_api.app.spec import build_app_from_spec + from openbb_platform_api.utils.widgets import build_json + + app = build_app_from_spec(SAMPLE_SPEC) + openapi = app.openapi() + exclude_filter: list = [] + build_json(openapi, exclude_filter) + # The POST command's widget id is filtered out. + assert any("user_preferences" in entry for entry in exclude_filter) + + +# --------------------------------------------------------------------------- +# _substitute_path_params — path-template resolution before forwarding +# --------------------------------------------------------------------------- + + +def test_substitute_path_params_replaces_placeholders(): + """``{name}`` placeholders are replaced with the resolved value.""" + from openbb_platform_api.app.spec import _substitute_path_params + + out = _substitute_path_params("/breakdown/{axis}", {"axis": "asset_class"}) + assert out == "/breakdown/asset_class" + + +def test_substitute_path_params_url_quotes_special_characters(): + """Values with slashes, spaces, etc. are URL-quoted (segment-safe).""" + from openbb_platform_api.app.spec import _substitute_path_params + + out = _substitute_path_params("/items/{name}", {"name": "a b/c"}) + assert out == "/items/a%20b%2Fc" + + +def test_substitute_path_params_no_op_when_template_lacks_placeholders(): + """Templates without ``{...}`` flow through unchanged.""" + from openbb_platform_api.app.spec import _substitute_path_params + + assert _substitute_path_params("/static/path", {"x": "y"}) == "/static/path" + + +def test_substitute_path_params_handles_multiple_placeholders(): + """Multi-segment templates substitute all matching keys.""" + from openbb_platform_api.app.spec import _substitute_path_params + + out = _substitute_path_params( + "/{cat}/{sub}/leaf", {"cat": "equity", "sub": "price"} + ) + assert out == "/equity/price/leaf" + + +def test_substitute_path_params_ignores_extra_kwargs(): + """Kwargs without a matching placeholder are silently dropped.""" + from openbb_platform_api.app.spec import _substitute_path_params + + out = _substitute_path_params("/{x}", {"x": "1", "extra": "ignored"}) + assert out == "/1" + + +def test_build_app_resolves_path_params_before_forwarding(monkeypatch): + """End-to-end regression: a route with a ``{axis}`` template hits the + upstream URL with the param value substituted (not the template). + + Caught against the BlackRock spec during the live integration test + of ``openbb-mcp`` — the same proxy machinery lives here. + """ + from fastapi.testclient import TestClient + + from openbb_platform_api.app.spec import build_app_from_spec + + spec = { + "version": 5, + "base_url": "https://upstream.example.com", + "commands": { + "breakdown": { + "url_path": "/breakdown/{axis}", + "method": "get", + "parameters": [ + { + "name": "axis", + "in": "path", + "type": "string", + "required": True, + } + ], + } + }, + } + app = build_app_from_spec(spec) + + captured: dict = {} + + class _StubUpstreamCM: + def __init__(self): + self.headers = {"Content-Type": "application/json"} + self.status = 200 + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + return None + + async def read(self): + return b"{}" + + class _StubSession: + def __init__(self, *args, **kwargs): + pass + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + return None + + def request(self, method, url, *, data=None, headers=None): + captured["url"] = url + return _StubUpstreamCM() + + import aiohttp + + monkeypatch.setattr(aiohttp, "ClientSession", _StubSession) + + client = TestClient(app) + response = client.get("/breakdown/asset_class") + assert response.status_code == 200 + # Upstream URL has the resolved value, not ``{axis}``. + assert captured["url"].endswith("/breakdown/asset_class") + assert "{axis}" not in captured["url"] + + +# --------------------------------------------------------------------------- +# Proxy handler — request forwarding +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_proxy_request_forwards_method_query_and_body(): + """The proxy replays method, query string, body, and headers + against ``upstream_url``, and streams the response back. + """ + from fastapi.testclient import TestClient + + from openbb_platform_api.app.spec import build_app_from_spec + + fake_upstream_response = MagicMock() + fake_upstream_response.read = AsyncMock( + return_value=b'{"results": [{"date": "2024-01-01", "close": 100.0}]}' + ) + fake_upstream_response.headers = {"Content-Type": "application/json"} + fake_upstream_response.status = 200 + + fake_session_ctx = MagicMock() + fake_session_ctx.__aenter__ = AsyncMock(return_value=fake_upstream_response) + fake_session_ctx.__aexit__ = AsyncMock(return_value=None) + + fake_session = MagicMock() + fake_session.request = MagicMock(return_value=fake_session_ctx) + fake_session.__aenter__ = AsyncMock(return_value=fake_session) + fake_session.__aexit__ = AsyncMock(return_value=None) + + app = build_app_from_spec(SAMPLE_SPEC) + client = TestClient(app) + + with patch("aiohttp.ClientSession", return_value=fake_session): + response = client.get( + "/api/v1/equity/price/historical?symbol=AAPL&interval=1d", + headers={"Authorization": "Bearer xyz"}, + ) + + assert response.status_code == 200 + assert response.json() == {"results": [{"date": "2024-01-01", "close": 100.0}]} + # Verify forward used the right URL + method + headers. + call_args, call_kwargs = fake_session.request.call_args + assert call_args[0] == "GET" + assert call_args[1].startswith(SAMPLE_SPEC["base_url"]) + assert "symbol=AAPL" in call_args[1] + assert call_kwargs["headers"].get("authorization") == "Bearer xyz" + + +def test_proxy_filters_hop_by_hop_request_headers(): + """Hop-by-hop headers (``Connection``, ``Transfer-Encoding``, + etc.) must NOT cross the proxy — RFC 7230 §6.1. + """ + from openbb_platform_api.app.spec import _filter_request_headers + + incoming = { + "Authorization": "Bearer xyz", + "Connection": "keep-alive", + "Host": "client.example.com", + "Transfer-Encoding": "chunked", + "X-Custom": "preserve-me", + } + out = _filter_request_headers(incoming) + assert "Authorization" in out + assert "X-Custom" in out + assert "Connection" not in out + assert "Host" not in out + assert "Transfer-Encoding" not in out + + +def test_proxy_filters_hop_by_hop_response_headers(): + """Same filter on the response side — let the launcher's response + infrastructure set Content-Length / Content-Encoding rather than + echoing potentially mismatched upstream values. + """ + from openbb_platform_api.app.spec import _filter_response_headers + + incoming = { + "Content-Type": "application/json", + "Content-Length": "9999", + "Content-Encoding": "gzip", + "X-Trace-Id": "abc", + } + out = _filter_response_headers(incoming) + assert "Content-Type" in out + assert "X-Trace-Id" in out + assert "Content-Length" not in out + assert "Content-Encoding" not in out + + +# --------------------------------------------------------------------------- +# parse_args integration — --spec hooks into the launcher +# --------------------------------------------------------------------------- + + +def test_parse_args_loads_spec_into_app_kwarg(tmp_path, monkeypatch): + """``--spec /path/to/cli.spec`` results in ``kwargs["app"]`` being + a synthesized FastAPI instance (same slot ``--app`` populates). + The spec file's full name (extension included) is captured on + ``app.state.openbb_spec_source`` for use as the widget citation + label. + """ + import sys + + from openbb_platform_api.app.args import parse_args + + spec_file = tmp_path / "fertilizer.spec" + spec_file.write_text(json.dumps(SAMPLE_SPEC)) + monkeypatch.setattr(sys, "argv", ["openbb-api", "--spec", str(spec_file)]) + out = parse_args() + # The synthesized app is in the ``app`` slot — uvicorn-ready. + app = out.get("app") + assert app is not None + paths = {r.path for r in app.routes if hasattr(r, "path")} + assert "/api/v1/equity/price/historical" in paths + # Full filename (extension included) flows through as the spec + # source — ``widgets_service`` uses this to override the + # auto-generated ``["Custom"]`` source on every widget. + assert app.state.openbb_spec_source == "fertilizer.spec" + + +def test_build_app_base_url_override_replaces_spec_recorded_url(): + """``base_url_override`` swaps the spec's recorded ``base_url``. + Lets a single spec serve staging/prod or reroute internal hosts + without regenerating the file. + """ + from openbb_platform_api.app.spec import build_app_from_spec + + app = build_app_from_spec( + SAMPLE_SPEC, base_url_override="https://elsewhere.example.com/api/v1" + ) + assert app.state.openbb_spec_base_url == "https://elsewhere.example.com/api/v1" + + +def test_build_app_spec_name_captured_on_app_state(): + """``spec_name`` is stashed on ``app.state.openbb_spec_source`` so + ``widgets_service.get_widgets_json`` can use it as the citation + label that overrides the auto-generated ``["Custom"]`` source. + Stored verbatim — the caller (``parse_args``) decides on the + full filename vs. stem; this layer just relays. + """ + from openbb_platform_api.app.spec import build_app_from_spec + + app = build_app_from_spec(SAMPLE_SPEC, spec_name="fertilizer.spec") + assert app.state.openbb_spec_source == "fertilizer.spec" + + +def test_build_app_spec_name_defaults_to_none_for_legacy_callers(): + """Legacy callers that don't pass ``spec_name`` get ``None`` on + ``app.state.openbb_spec_source``, which the override helper treats + as a no-op signal. + """ + from openbb_platform_api.app.spec import build_app_from_spec + + app = build_app_from_spec(SAMPLE_SPEC) + assert app.state.openbb_spec_source is None + + +def test_build_app_extra_headers_recorded_on_app_state(): + """Static headers from ``[spec.headers]`` are captured on + ``app.state`` so the proxy handlers can reference the same dict + without per-request closure copies. + """ + from openbb_platform_api.app.spec import build_app_from_spec + + headers = {"Authorization": "Bearer abc", "X-API-Key": "xyz"} + app = build_app_from_spec(SAMPLE_SPEC, extra_headers=headers) + assert app.state.openbb_spec_extra_headers == headers + + +def test_proxy_extra_headers_override_incoming_request_headers(): + """Config-supplied headers OVERRIDE matching incoming-request + headers. Reasoning: ``[spec.headers]`` is the credential + injection point — a misbehaving client can't leak its own auth + value upstream by sending the same header name. + """ + from fastapi.testclient import TestClient + + from openbb_platform_api.app.spec import build_app_from_spec + + fake_upstream_response = MagicMock() + fake_upstream_response.read = AsyncMock(return_value=b"{}") + fake_upstream_response.headers = {"Content-Type": "application/json"} + fake_upstream_response.status = 200 + + fake_session_ctx = MagicMock() + fake_session_ctx.__aenter__ = AsyncMock(return_value=fake_upstream_response) + fake_session_ctx.__aexit__ = AsyncMock(return_value=None) + fake_session = MagicMock() + fake_session.request = MagicMock(return_value=fake_session_ctx) + fake_session.__aenter__ = AsyncMock(return_value=fake_session) + fake_session.__aexit__ = AsyncMock(return_value=None) + + app = build_app_from_spec( + SAMPLE_SPEC, + extra_headers={"Authorization": "Bearer SERVER-CREDENTIAL"}, + ) + client = TestClient(app) + + with patch("aiohttp.ClientSession", return_value=fake_session): + client.get( + "/api/v1/equity/price/historical?symbol=AAPL", + headers={"Authorization": "Bearer CLIENT-LEAKED"}, + ) + + forwarded = fake_session.request.call_args.kwargs["headers"] + # Server-supplied credential wins. Client-supplied auth header + # never reaches upstream. + assert forwarded.get("Authorization") == "Bearer SERVER-CREDENTIAL" + + +def test_parse_args_resolves_relative_spec_path_against_cwd(tmp_path, monkeypatch): + """A relative ``[spec] path`` is resolved against ``cwd``. Lets a + config file in the project root point at a sibling ``./cli.spec`` + without absolute paths. + """ + import sys + + from openbb_platform_api.app.args import parse_args + + spec_file = tmp_path / "cli.spec" + spec_file.write_text(json.dumps(SAMPLE_SPEC)) + + config_file = tmp_path / "openbb.toml" + config_file.write_text('[spec]\npath = "cli.spec"\n') + + monkeypatch.chdir(tmp_path) + monkeypatch.setattr(sys, "argv", ["openbb-api", "--config-file", str(config_file)]) + + out = parse_args() + app = out["app"] + # App was successfully built — relative path resolved against cwd. + assert hasattr(app.state, "openbb_spec") + + +def test_expand_spec_headers_skips_non_string_keys(): + """A malformed ``[spec.headers]`` entry with a non-string key + (impossible from real TOML, but defensive against programmatic + misuse) is silently dropped — TOML libraries can produce + surprising shapes when configs are merged. + """ + from openbb_platform_api.app.args import _expand_spec_headers + + out = _expand_spec_headers({42: "bad-key", "X-Good": "ok"}) + assert out == {"X-Good": "ok"} + + +def test_expand_spec_headers_returns_empty_for_none_or_empty(): + """Missing / empty headers table → empty dict, no crash.""" + from openbb_platform_api.app.args import _expand_spec_headers + + assert _expand_spec_headers(None) == {} + assert _expand_spec_headers({}) == {} + + +def test_parse_args_loads_spec_table_with_headers_and_base_url(tmp_path, monkeypatch): + """``[spec]`` table populates path, base_url override, and the + headers table. ``[spec.headers]`` resolves ``$VAR`` references + against the (already-applied) ``[env]`` table — credential + injection in the single-config-file deployment scenario. + """ + import sys + + from openbb_platform_api.app.args import parse_args + + spec_file = tmp_path / "cli.spec" + spec_file.write_text(json.dumps(SAMPLE_SPEC)) + + config_file = tmp_path / "openbb.toml" + # ``.as_posix()`` keeps forward slashes on Windows so the TOML + # parser doesn't interpret ``\U`` / ``\r`` / etc. inside the + # interpolated path as hex/escape sequences. Forward slashes are + # accepted by Python's ``os.path``/``Path`` on every platform. + config_file.write_text( + f''' +[env] +OPENBB_UPSTREAM_TOKEN = "tok-from-env" + +[spec] +path = "{spec_file.as_posix()}" +base_url = "https://prod.example.com" + +[spec.headers] +Authorization = "Bearer $OPENBB_UPSTREAM_TOKEN" +X-Static = "literal-value" +''' + ) + + monkeypatch.setattr(sys, "argv", ["openbb-api", "--config-file", str(config_file)]) + # Make sure no leftover env causes a collision. + monkeypatch.delenv("OPENBB_UPSTREAM_TOKEN", raising=False) + + # main.py's bootstrap normally applies [env] before parse_args + # runs. Mimic that here so the $VAR refs in [spec.headers] + # resolve against an os.environ that already has the value. + from openbb_platform_api.app.config import bootstrap_launcher_config + + bootstrap_launcher_config(["--config-file", str(config_file)]) + + out = parse_args() + app = out["app"] + assert app.state.openbb_spec_base_url == "https://prod.example.com" + headers = app.state.openbb_spec_extra_headers + assert headers["Authorization"] == "Bearer tok-from-env" + assert headers["X-Static"] == "literal-value" + + +def test_parse_args_skips_spec_header_with_unresolved_var( + tmp_path, monkeypatch, caplog +): + """A ``[spec.headers]`` entry referencing an unset variable is + SKIPPED (not set to the literal ``$MISSING``) and a warning + surfaces — same semantics as ``[env]`` injection. + """ + import logging + import sys + + from openbb_platform_api.app.args import parse_args + + spec_file = tmp_path / "cli.spec" + spec_file.write_text(json.dumps(SAMPLE_SPEC)) + + config_file = tmp_path / "openbb.toml" + # ``.as_posix()`` keeps forward slashes on Windows so the TOML + # parser doesn't interpret ``\U`` / ``\r`` / etc. inside the + # interpolated path as hex/escape sequences. + config_file.write_text( + f''' +[spec] +path = "{spec_file.as_posix()}" + +[spec.headers] +Authorization = "Bearer $NEVER_SET_ANYWHERE" +X-Good = "just-text" +''' + ) + + monkeypatch.setattr(sys, "argv", ["openbb-api", "--config-file", str(config_file)]) + monkeypatch.delenv("NEVER_SET_ANYWHERE", raising=False) + + with caplog.at_level(logging.WARNING, logger="openbb_platform_api.spec.headers"): + out = parse_args() + app = out["app"] + headers = app.state.openbb_spec_extra_headers + # Bad header dropped; good one passes through. + assert "Authorization" not in headers + assert headers.get("X-Good") == "just-text" + # Warning surfaces the missing var name + header name. + assert any( + "Authorization" in r.message and "NEVER_SET_ANYWHERE" in r.message + for r in caplog.records + ) + + +def test_parse_args_rejects_spec_and_app_combined(tmp_path, monkeypatch): + """``--spec`` and ``--app`` can't both populate the app slot. Fail + loudly at startup instead of silently using one and ignoring the + other. + """ + import sys + + from openbb_platform_api.app.args import parse_args + + spec_file = tmp_path / "cli.spec" + spec_file.write_text(json.dumps(SAMPLE_SPEC)) + monkeypatch.setattr( + sys, + "argv", + [ + "openbb-api", + "--spec", + str(spec_file), + "--app", + "/some/app.py", + ], + ) + with pytest.raises(ValueError, match="mutually exclusive"): + parse_args() + + +# --------------------------------------------------------------------------- +# wire_name translation — Socrata $limit/$offset case +# --------------------------------------------------------------------------- + + +def test_rewrite_query_string_translates_friendly_to_wire_names(): + """``wire_name`` rewrites the friendly param name to whatever the + upstream expects. Without this, Socrata responds ``Unrecognized + arguments [limit]`` because the user sees ``limit`` in the widget + UI but the actual SoQL parameter is ``$limit``. + """ + from openbb_platform_api.app.spec import _rewrite_query_string + + out = _rewrite_query_string( + "limit=1000&offset=50®ion=Asia", + {"limit": "$limit", "offset": "$offset"}, + ) + # Order preserved; values untouched; mapped keys swapped; unmapped + # keys (``region``) flow through verbatim. + assert out == "%24limit=1000&%24offset=50®ion=Asia" + + +def test_rewrite_query_string_passthrough_when_no_map(): + """``None`` / empty map → no rewrite; query string returned + unchanged. Keeps the common no-translation path cheap. + """ + from openbb_platform_api.app.spec import _rewrite_query_string + + qs = "symbol=AAPL&interval=1d" + assert _rewrite_query_string(qs, None) == qs + assert _rewrite_query_string(qs, {}) == qs + + +def test_rewrite_query_string_empty_input(): + """Empty input is a no-op even with a non-empty map — avoid + constructing ``urlencode`` output for nothing. + """ + from openbb_platform_api.app.spec import _rewrite_query_string + + assert _rewrite_query_string("", {"limit": "$limit"}) == "" + + +def test_rewrite_query_string_preserves_blank_values(): + """``?foo=`` round-trips correctly — ``parse_qsl`` is invoked with + ``keep_blank_values=True`` so deliberately-blank filter args + aren't silently dropped. + """ + from openbb_platform_api.app.spec import _rewrite_query_string + + out = _rewrite_query_string("limit=&offset=10", {"limit": "$limit"}) + # Blank ``limit`` value preserved (urlencode emits ``key=``). + assert out == "%24limit=&offset=10" + + +def test_build_app_captures_wire_name_map_from_spec_parameters(): + """A spec command that declares ``wire_name`` on its params builds + a route whose handler closes over the friendly→wire map. + Verified end-to-end via the proxy: the upstream sees ``$limit`` + even though the client sent ``limit``. + """ + from fastapi.testclient import TestClient + + from openbb_platform_api.app.spec import build_app_from_spec + + socrata_spec = { + "version": 5, + "base_url": "https://data.example.com", + "api_prefix": "", + "commands": { + "fertilizer.prices": { + "url_path": "/resource/abcd-1234.json", + "method": "get", + "description": "Fertilizer prices.", + "providers": [], + "parameters": [ + { + "name": "region", + "in": "query", + "type": "string", + "is_list": False, + "required": False, + "default": None, + "choices": [], + "providers": [], + }, + { + "name": "limit", + "in": "query", + "type": "integer", + "is_list": False, + "required": False, + "default": 1000, + "choices": [], + "providers": [], + "wire_name": "$limit", + }, + { + "name": "offset", + "in": "query", + "type": "integer", + "is_list": False, + "required": False, + "default": None, + "choices": [], + "providers": [], + "wire_name": "$offset", + }, + ], + "request_body_schema": None, + "response_schema": None, + } + }, + "routers": {}, + "reference": {}, + } + + fake_resp = MagicMock() + fake_resp.read = AsyncMock(return_value=b"[]") + fake_resp.headers = {"Content-Type": "application/json"} + fake_resp.status = 200 + ctx = MagicMock() + ctx.__aenter__ = AsyncMock(return_value=fake_resp) + ctx.__aexit__ = AsyncMock(return_value=None) + fake_session = MagicMock() + fake_session.request = MagicMock(return_value=ctx) + fake_session.__aenter__ = AsyncMock(return_value=fake_session) + fake_session.__aexit__ = AsyncMock(return_value=None) + + app = build_app_from_spec(socrata_spec) + client = TestClient(app) + with patch("aiohttp.ClientSession", return_value=fake_session): + client.get("/resource/abcd-1234.json?limit=1000&offset=50®ion=Asia") + + # The upstream URL forwarded to aiohttp uses the WIRE names — the + # whole point of the rewrite. Without this, Socrata returns + # ``Unrecognized arguments [limit]``. + target_url = fake_session.request.call_args.args[1] + assert "%24limit=1000" in target_url + assert "%24offset=50" in target_url + assert "region=Asia" in target_url + # And the friendly names DON'T leak into the upstream call. + assert "&limit=" not in target_url + assert "?limit=" not in target_url + + +def test_build_app_skips_non_dict_parameter_entries(): + """A parameter entry that isn't a dict (corrupt / partial spec) + is silently skipped during the wire_name map build — keeps the + launcher tolerant of malformed specs without crashing on import. + """ + from openbb_platform_api.app.spec import build_app_from_spec + + spec = { + **SAMPLE_SPEC, + "commands": { + "x": { + "url_path": "/x", + "method": "get", + # Mix of valid dict + a bare string (malformed) — the + # string is skipped, the dict is processed. + "parameters": [ + "not-a-dict", + { + "name": "limit", + "in": "query", + "type": "integer", + "is_list": False, + "required": False, + "default": 1000, + "choices": [], + "wire_name": "$limit", + }, + ], + } + }, + } + # Builds without crashing. + app = build_app_from_spec(spec) + paths = {r.path for r in app.routes if hasattr(r, "path")} + assert "/x" in paths + + +def test_build_app_no_wire_name_means_passthrough(): + """When a spec command's params have no ``wire_name`` field, the + proxy forwards friendly names verbatim — preserves the existing + spec-mode behavior for non-Socrata backends. + """ + from fastapi.testclient import TestClient + + from openbb_platform_api.app.spec import build_app_from_spec + + fake_resp = MagicMock() + fake_resp.read = AsyncMock(return_value=b"{}") + fake_resp.headers = {"Content-Type": "application/json"} + fake_resp.status = 200 + ctx = MagicMock() + ctx.__aenter__ = AsyncMock(return_value=fake_resp) + ctx.__aexit__ = AsyncMock(return_value=None) + fake_session = MagicMock() + fake_session.request = MagicMock(return_value=ctx) + fake_session.__aenter__ = AsyncMock(return_value=fake_session) + fake_session.__aexit__ = AsyncMock(return_value=None) + + app = build_app_from_spec(SAMPLE_SPEC) + client = TestClient(app) + with patch("aiohttp.ClientSession", return_value=fake_session): + client.get("/api/v1/equity/price/historical?symbol=AAPL&interval=1d") + + target_url = fake_session.request.call_args.args[1] + # Friendly names go through unchanged. + assert "symbol=AAPL" in target_url + assert "interval=1d" in target_url + + +# --------------------------------------------------------------------------- +# _trim_uniform_zero_time_columns — runtime data-driven date normalization +# --------------------------------------------------------------------------- + + +def test_trim_uniform_zero_time_strips_when_every_row_is_midnight(): + """When EVERY row's value for a column ends with ``T00:00:00*``, + the time portion carries no information. Strip it across the whole + column. Other columns (numbers, text) are untouched. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = [ + {"date": "2023-01-01T00:00:00.000", "year": 2023, "region": "California"}, + {"date": "2023-02-01T00:00:00.000", "year": 2023, "region": "California"}, + {"date": "2023-03-01T00:00:00.000", "year": 2023, "region": "California"}, + ] + out = _trim_uniform_zero_time_columns(payload) + assert out == [ + {"date": "2023-01-01", "year": 2023, "region": "California"}, + {"date": "2023-02-01", "year": 2023, "region": "California"}, + {"date": "2023-03-01", "year": 2023, "region": "California"}, + ] + + +def test_trim_uniform_zero_time_leaves_column_alone_when_any_row_has_real_time(): + """A single non-midnight row in a column means the time portion + IS meaningful — every row's time stays, including the midnight + ones. Midnight is a legitimate time when the upstream actually + records it. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = [ + {"ts": "2023-01-01T00:00:00.000"}, + {"ts": "2023-01-02T13:45:00.000"}, # real time-of-day + {"ts": "2023-01-03T00:00:00.000"}, + ] + out = _trim_uniform_zero_time_columns(payload) + # Returned object is the SAME identity — function detected no-op + # so the caller skips the JSON re-encode. + assert out is payload + + +def test_trim_uniform_zero_time_per_column_independence(): + """The unanimity check is per-column. ``date`` may be uniformly + midnight (trim) while ``ts`` has real time-of-day (don't trim) in + the same response. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = [ + {"date": "2023-01-01T00:00:00.000", "ts": "2023-01-01T13:45:00.000"}, + {"date": "2023-01-02T00:00:00.000", "ts": "2023-01-02T09:00:00.000"}, + ] + out = _trim_uniform_zero_time_columns(payload) + assert out[0]["date"] == "2023-01-01" # trimmed + assert out[1]["date"] == "2023-01-02" + assert out[0]["ts"] == "2023-01-01T13:45:00.000" # left alone + assert out[1]["ts"] == "2023-01-02T09:00:00.000" + + +def test_trim_uniform_zero_time_handles_obbject_envelope(): + """Top-level dict with a ``results`` list (OBBject envelope) gets + the trim applied to the inner list. Other top-level keys flow + through unchanged. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = { + "results": [ + {"date": "2023-01-01T00:00:00.000", "value": 100}, + {"date": "2023-01-02T00:00:00.000", "value": 110}, + ], + "provider": "fred", + "warnings": None, + } + out = _trim_uniform_zero_time_columns(payload) + assert out["results"] == [ + {"date": "2023-01-01", "value": 100}, + {"date": "2023-01-02", "value": 110}, + ] + assert out["provider"] == "fred" + assert out["warnings"] is None + + +def test_trim_uniform_zero_time_handles_alt_envelope_keys(): + """``data`` / ``rows`` / ``records`` are all recognized envelope + keys — covers payload shapes from non-OBBject backends. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + for key in ("data", "rows", "records"): + payload = {key: [{"date": "2023-01-01T00:00:00.000"}]} + out = _trim_uniform_zero_time_columns(payload) + assert out[key][0]["date"] == "2023-01-01" + + +def test_trim_uniform_zero_time_passes_through_unrecognized_dict(): + """A plain dict (single record) without an envelope key passes + through unchanged — with only one row there's no cross-row signal, + and midnight on a lone record could be meaningful. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = {"date": "2023-01-01T00:00:00.000", "value": 100} + out = _trim_uniform_zero_time_columns(payload) + assert out is payload # no-op, identity preserved + + +def test_trim_uniform_zero_time_passes_through_scalars_and_empty(): + """Non-list / non-dict inputs and empty lists are no-ops.""" + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + assert _trim_uniform_zero_time_columns("not a payload") == "not a payload" + assert _trim_uniform_zero_time_columns(None) is None + assert _trim_uniform_zero_time_columns([]) == [] + # List of non-dicts: nothing to scan. + assert _trim_uniform_zero_time_columns([1, 2, 3]) == [1, 2, 3] + + +def test_trim_uniform_zero_time_ignores_columns_with_no_string_values(): + """A column where all values are numeric / None has nothing to + trim and shouldn't be flagged. Mixed columns where strings ARE + present and uniformly zero-time get trimmed. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = [ + {"date": "2023-01-01T00:00:00.000", "value": 100, "extra": None}, + {"date": "2023-01-02T00:00:00.000", "value": 110, "extra": None}, + ] + out = _trim_uniform_zero_time_columns(payload) + assert out[0]["date"] == "2023-01-01" + assert out[0]["value"] == 100 + # ``extra`` was all-None — nothing to trim, but also nothing to + # disqualify. It just stays None. + assert out[0]["extra"] is None + + +def test_trim_uniform_zero_time_handles_optional_nulls_in_date_column(): + """A null in a date column doesn't disqualify the column — the + unanimity check looks at present values only. Nulls flow through + unchanged. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = [ + {"date": "2023-01-01T00:00:00.000"}, + {"date": None}, + {"date": "2023-01-03T00:00:00.000"}, + ] + out = _trim_uniform_zero_time_columns(payload) + assert out[0]["date"] == "2023-01-01" + assert out[1]["date"] is None + assert out[2]["date"] == "2023-01-03" + + +def test_trim_uniform_zero_time_recognizes_zero_offset_variants(): + """Various ``T00:00:00`` shapes — bare, with milliseconds, with + Z, with explicit offset — all match the zero-time pattern. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = [ + {"date": "2023-01-01T00:00:00"}, + {"date": "2023-01-02T00:00:00.000"}, + {"date": "2023-01-03T00:00:00Z"}, + {"date": "2023-01-04T00:00:00.000Z"}, + {"date": "2023-01-05T00:00:00+00:00"}, + ] + out = _trim_uniform_zero_time_columns(payload) + assert [r["date"] for r in out] == [ + "2023-01-01", + "2023-01-02", + "2023-01-03", + "2023-01-04", + "2023-01-05", + ] + + +def test_trim_uniform_zero_time_preserves_non_dict_records_in_list(): + """A list mixing dict records with scalars (rare but possible in + heterogeneous responses) keeps the scalars intact while trimming + the dict records' uniform-zero-time columns. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = [ + {"date": "2023-01-01T00:00:00.000", "value": 100}, + "scalar-not-a-dict", # passes through untouched + {"date": "2023-01-02T00:00:00.000", "value": 110}, + ] + out = _trim_uniform_zero_time_columns(payload) + assert out[0] == {"date": "2023-01-01", "value": 100} + assert out[1] == "scalar-not-a-dict" + assert out[2] == {"date": "2023-01-02", "value": 110} + + +def test_trim_uniform_zero_time_does_not_strip_real_milliseconds(): + """``T00:00:00.001`` has a real (non-zero) sub-second component — + that's a real time, the column has data variance, leave alone. + """ + from openbb_platform_api.app.spec import _trim_uniform_zero_time_columns + + payload = [ + {"date": "2023-01-01T00:00:00.001"}, + {"date": "2023-01-02T00:00:00.000"}, + ] + out = _trim_uniform_zero_time_columns(payload) + # Mixed shapes (one zero, one non-zero ms) → not unanimous → no trim. + assert out is payload + + +def test_proxy_response_trim_through_real_app(tmp_path): + """End-to-end integration: spec proxy synthesizes routes, fetches + a fake upstream JSON, and the trimmed response reaches the client. + Mirrors the original screenshot scenario — Socrata-shaped payload + with T00:00:00.000 across every row gets normalized to YYYY-MM-DD. + """ + from fastapi.testclient import TestClient + + from openbb_platform_api.app.spec import build_app_from_spec + + fake_resp = MagicMock() + fake_resp.read = AsyncMock( + return_value=json.dumps( + [ + {"date": "2023-01-01T00:00:00.000", "value": 835}, + {"date": "2023-02-01T00:00:00.000", "value": 835}, + {"date": "2023-03-01T00:00:00.000", "value": 813}, + ] + ).encode() + ) + fake_resp.headers = {"Content-Type": "application/json"} + fake_resp.status = 200 + ctx = MagicMock() + ctx.__aenter__ = AsyncMock(return_value=fake_resp) + ctx.__aexit__ = AsyncMock(return_value=None) + fake_session = MagicMock() + fake_session.request = MagicMock(return_value=ctx) + fake_session.__aenter__ = AsyncMock(return_value=fake_session) + fake_session.__aexit__ = AsyncMock(return_value=None) + + app = build_app_from_spec(SAMPLE_SPEC) + client = TestClient(app) + with patch("aiohttp.ClientSession", return_value=fake_session): + response = client.get("/api/v1/equity/price/historical?symbol=AAPL") + + assert response.status_code == 200 + body = response.json() + assert [r["date"] for r in body] == [ + "2023-01-01", + "2023-02-01", + "2023-03-01", + ] + + +def test_proxy_response_no_trim_for_non_json(tmp_path): + """Non-JSON responses (CSV, binary, etc.) bypass the trim entirely + — the proxy doesn't attempt to parse and never mutates them. + """ + from fastapi.testclient import TestClient + + from openbb_platform_api.app.spec import build_app_from_spec + + csv_bytes = ( + b"date,value\n2023-01-01T00:00:00.000,835\n2023-02-01T00:00:00.000,835\n" + ) + fake_resp = MagicMock() + fake_resp.read = AsyncMock(return_value=csv_bytes) + fake_resp.headers = {"Content-Type": "text/csv"} + fake_resp.status = 200 + ctx = MagicMock() + ctx.__aenter__ = AsyncMock(return_value=fake_resp) + ctx.__aexit__ = AsyncMock(return_value=None) + fake_session = MagicMock() + fake_session.request = MagicMock(return_value=ctx) + fake_session.__aenter__ = AsyncMock(return_value=fake_session) + fake_session.__aexit__ = AsyncMock(return_value=None) + + app = build_app_from_spec(SAMPLE_SPEC) + client = TestClient(app) + with patch("aiohttp.ClientSession", return_value=fake_session): + response = client.get("/api/v1/equity/price/historical?symbol=AAPL") + + # CSV bytes flow through verbatim — including the T00:00:00.000 + # values that the JSON path would have trimmed. + assert response.status_code == 200 + assert b"2023-01-01T00:00:00.000" in response.content + + +def test_proxy_response_trim_skipped_on_invalid_json(): + """Malformed JSON in an ``application/json`` response must not + crash the proxy — the trim is best-effort and falls back to + pass-through on parse failure. + """ + from fastapi.testclient import TestClient + + from openbb_platform_api.app.spec import build_app_from_spec + + # Truncated / broken JSON. + bad_bytes = b'{"results": [{"date": "2023-01-01T00:00' + fake_resp = MagicMock() + fake_resp.read = AsyncMock(return_value=bad_bytes) + fake_resp.headers = {"Content-Type": "application/json"} + fake_resp.status = 200 + ctx = MagicMock() + ctx.__aenter__ = AsyncMock(return_value=fake_resp) + ctx.__aexit__ = AsyncMock(return_value=None) + fake_session = MagicMock() + fake_session.request = MagicMock(return_value=ctx) + fake_session.__aenter__ = AsyncMock(return_value=fake_session) + fake_session.__aexit__ = AsyncMock(return_value=None) + + app = build_app_from_spec(SAMPLE_SPEC) + client = TestClient(app) + with patch("aiohttp.ClientSession", return_value=fake_session): + response = client.get("/api/v1/equity/price/historical?symbol=AAPL") + + # Bad bytes pass through verbatim; status code preserved. + assert response.status_code == 200 + assert response.content == bad_bytes diff --git a/openbb_platform/extensions/platform_api/tests/conftest.py b/openbb_platform/extensions/platform_api/tests/conftest.py new file mode 100644 index 00000000000..08c88f01e78 --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/conftest.py @@ -0,0 +1,21 @@ +"""Test bootstrap for ``openbb-platform-api``.""" + +import sys + +import openbb_core.api +import openbb_core.api.app_loader # noqa: F401 +import openbb_core.api.exception_handlers # noqa: F401 +import openbb_core.app.model +import openbb_core.app.model.credentials # noqa: F401 +import openbb_core.app.service +import openbb_core.app.service.system_service # noqa: F401 +import openbb_core.app.service.user_service # noqa: F401 + +for _parent, _children in ( + ("openbb_core.api", ("app_loader", "exception_handlers")), + ("openbb_core.app.model", ("credentials",)), + ("openbb_core.app.service", ("system_service", "user_service")), +): + _pkg = sys.modules[_parent] + for _child in _children: + setattr(_pkg, _child, sys.modules[f"{_parent}.{_child}"]) diff --git a/openbb_platform/extensions/platform_api/tests/models/test_query.py b/openbb_platform/extensions/platform_api/tests/models/test_query.py new file mode 100644 index 00000000000..9804b0a543e --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/models/test_query.py @@ -0,0 +1,65 @@ +"""Tests for openbb_platform_api.models.query.""" + +from openbb_platform_api.models.query import OmniWidgetInput + + +def test_omni_widget_input_accepts_none_prompt(): + """``prompt`` defaults to ``None`` and stays ``None`` when omitted.""" + obj = OmniWidgetInput() + assert obj.prompt is None + + +def test_omni_widget_input_passes_through_non_string_prompt(): + """A non-string scalar (int / dict / list) should be left intact — + the JSON-loads branch only kicks in for strings. + """ + obj = OmniWidgetInput(prompt=42) + assert obj.prompt == 42 + + obj = OmniWidgetInput(prompt={"key": "value"}) + assert obj.prompt == {"key": "value"} + + +def test_omni_widget_input_parses_valid_json_string(): + """A clean JSON string gets parsed into the corresponding object.""" + obj = OmniWidgetInput(prompt='{"k": 1}') + assert obj.prompt == {"k": 1} + + +def test_omni_widget_input_repairs_trailing_commas_in_json(): + """Workspace sometimes sends JSON with trailing commas — the regex + cleanup branch repairs ``{"k": 1,}`` style payloads. + """ + # Raw json.loads can't parse this — exercise the json.JSONDecodeError + # ``re.sub`` repair branch. + obj = OmniWidgetInput(prompt='{"k": 1,}') + # The cleaned prompt parses; OR it falls through to the raw string + # if the regex doesn't fix it. Either way the field accepts. + assert obj.prompt is not None + + +def test_omni_widget_input_falls_back_to_raw_string_on_unrepairable(): + """A garbage string that's neither JSON nor a recoverable typo + falls through to the raw value — exercises the second + ``except JSONDecodeError`` arm. + """ + obj = OmniWidgetInput(prompt="this is just text") + assert obj.prompt == "this is just text" + + +def test_omni_widget_input_empty_string_collapses_to_none(): + """An empty string is treated as no prompt — the validator's first + ``if not v or v == ""`` branch returns ``None``. + """ + obj = OmniWidgetInput(prompt="") + assert obj.prompt is None + + +def test_omni_widget_input_extra_fields_allowed(): + """``model_config.extra='allow'`` lets unrecognized fields ride + along — Workspace passes additional context that the widget + handler may want to consume. + """ + obj = OmniWidgetInput(prompt=None, custom_field="x") + # The custom field should be accessible via __pydantic_extra__. + assert getattr(obj, "custom_field", None) == "x" diff --git a/openbb_platform/extensions/platform_api/tests/models/test_response.py b/openbb_platform/extensions/platform_api/tests/models/test_response.py new file mode 100644 index 00000000000..e490b5a1c33 --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/models/test_response.py @@ -0,0 +1,245 @@ +"""Tests for openbb_platform_api.models.response.""" + +import base64 +import json + +import pytest + +from openbb_platform_api.models.response import ( + MetricResponseModel, + OmniWidgetResponseModel, + PdfResponseModel, +) + +# --------------------------------------------------------------------------- +# MetricResponseModel +# --------------------------------------------------------------------------- + + +def test_metric_response_model_basic_construction(): + """Required ``label`` + ``value`` build cleanly with no delta.""" + m = MetricResponseModel(label="Revenue", value=1234) + assert m.label == "Revenue" + assert m.value == 1234 + assert m.delta is None + + +def test_metric_response_model_accepts_string_and_float_values(): + """``value`` is ``int | float | str`` so all three flow through.""" + m = MetricResponseModel(label="Pct", value=3.14, delta="+2.0%") + assert m.value == 3.14 + assert m.delta == "+2.0%" + + +# --------------------------------------------------------------------------- +# PdfResponseModel +# --------------------------------------------------------------------------- + + +def test_pdf_response_requires_content_or_url(): + """Both empty → validator raises with the canonical message.""" + with pytest.raises(ValueError, match="Either 'content' or 'url'"): + PdfResponseModel() + + +def test_pdf_response_url_must_have_scheme(): + """A bare ``url`` like ``"file.pdf"`` (no ``://``) is rejected.""" + with pytest.raises(ValueError, match="Invalid URL reference"): + PdfResponseModel(url="not-a-url") + + +def test_pdf_response_url_only_passes_through(): + """``url`` with a real scheme is accepted; ``content`` slot stays + untouched and ``data_format`` is stamped. + """ + m = PdfResponseModel(url="https://example.com/doc.pdf", filename="doc.pdf") + assert m.url == "https://example.com/doc.pdf" + assert m.data_format == {"data_type": "pdf", "filename": "doc.pdf"} + + +def test_pdf_response_bytes_content_gets_base64_encoded(): + """Raw bytes get re-encoded as a base64 string so the JSON wire + payload stays text-only. + """ + raw = b"%PDF-1.4 fake content" + m = PdfResponseModel(content=raw, filename="x.pdf") + decoded = base64.b64decode(m.content) + assert decoded == raw + assert m.data_format == {"data_type": "pdf", "filename": "x.pdf"} + + +def test_pdf_response_string_content_passes_through_unchanged(): + """A string ``content`` (already base64-encoded) doesn't get + re-encoded — the validator only encodes ``bytes``. + """ + encoded = base64.b64encode(b"hello").decode("utf-8") + m = PdfResponseModel(content=encoded) + assert m.content == encoded + + +# --------------------------------------------------------------------------- +# OmniWidgetResponseModel +# --------------------------------------------------------------------------- + + +def test_omni_response_rejects_empty_content(): + """Workspace requires *some* content — an explicit ``None`` is a bug.""" + with pytest.raises(ValueError, match="Content cannot be empty"): + OmniWidgetResponseModel(content=None) + + +def test_omni_response_validates_parse_as_choices(): + """Only ``table`` / ``chart`` / ``text`` are valid hints.""" + with pytest.raises(ValueError, match="Invalid parse_as"): + OmniWidgetResponseModel(content="hi", parse_as="bogus") + + +def test_omni_response_explicit_parse_as_skips_inference(): + """When the caller passes ``parse_as`` explicitly, the validator + trusts it and stamps ``data_format`` without re-running inference. + """ + m = OmniWidgetResponseModel(content="some text", parse_as="text") + assert m.data_format == {"data_type": "object", "parse_as": "text"} + # ``parse_as`` is consumed during validation and removed from the + # model body so it doesn't leak into the wire response. + assert getattr(m, "parse_as", None) is None + + +def test_omni_response_infers_chart_for_plotly_dict(): + """``{layout, data}`` → ``parse_as="chart"`` automatically.""" + fig = {"layout": {"title": "x"}, "data": [{"type": "scatter"}]} + m = OmniWidgetResponseModel(content=fig) + assert m.data_format["parse_as"] == "chart" + assert m.content == fig + + +def test_omni_response_infers_table_for_list_of_dicts(): + """A list of records gets tagged as a table.""" + rows = [{"a": 1}, {"a": 2}] + m = OmniWidgetResponseModel(content=rows) + assert m.data_format["parse_as"] == "table" + assert m.content == rows + + +def test_omni_response_infers_table_for_dict_of_lists(): + """``{"col": [...]}`` shape gets converted into row-records via + ``pandas.to_json(orient="records")`` before tagging as a table. + """ + cols = {"a": [1, 2], "b": [3, 4]} + m = OmniWidgetResponseModel(content=cols) + assert m.data_format["parse_as"] == "table" + assert m.content == [{"a": 1, "b": 3}, {"a": 2, "b": 4}] + + +def test_omni_response_infers_table_from_dataframe(): + """A pandas DataFrame becomes a list of records.""" + pd = pytest.importorskip("pandas") + df = pd.DataFrame({"x": [1, 2], "y": [3, 4]}) + m = OmniWidgetResponseModel(content=df) + assert m.data_format["parse_as"] == "table" + assert m.content == [{"x": 1, "y": 3}, {"x": 2, "y": 4}] + + +def test_omni_response_parses_json_string(): + """A JSON-encoded string of a list/dict gets parsed and tagged + according to its shape. + """ + payload = json.dumps([{"a": 1}, {"a": 2}]) + m = OmniWidgetResponseModel(content=payload) + assert m.data_format["parse_as"] == "table" + assert m.content == [{"a": 1}, {"a": 2}] + + +def test_omni_response_repairs_trailing_commas_in_json_string(): + """The validator falls back to a regex cleanup pass when the raw + JSON parse fails — covers Workspace's lenient payloads. + """ + m = OmniWidgetResponseModel(content='[{"a": 1,},]') + # Either parses (the repair worked) or stays as the raw string with + # ``parse_as="text"`` — both are valid outcomes for the lenient path. + assert m.data_format["parse_as"] in {"table", "text"} + + +def test_omni_response_string_falls_back_to_text_for_garbage(): + """A non-JSON string can't be repaired — falls through to text.""" + m = OmniWidgetResponseModel(content="just plain text") + assert m.data_format["parse_as"] == "text" + + +def test_omni_response_text_for_unrecognized_shape(): + """A scalar that isn't string/list/dict/DataFrame → text.""" + m = OmniWidgetResponseModel(content=42) + assert m.data_format["parse_as"] == "text" + + +def test_omni_response_chart_from_plotly_figure_object(): + """A real Plotly ``Figure`` instance gets serialized to JSON and + tagged as a chart — exercises the ``content.__class__.__name__ == + 'Figure'`` branch. + """ + pytest.importorskip("plotly") + import plotly.graph_objects as go + + fig = go.Figure(data=[go.Scatter(x=[1, 2], y=[3, 4])]) + m = OmniWidgetResponseModel(content=fig) + assert m.data_format["parse_as"] == "chart" + # ``content`` is the JSON serialization, not the Figure object. + assert isinstance(m.content, str) + parsed = json.loads(m.content) + assert "data" in parsed and "layout" in parsed + + +def test_omni_response_dataframe_serialization_failure_raises_value_error(): + """If pandas can't serialize the DataFrame (corrupt state), the + validator surfaces a clear ``ValueError`` instead of leaking the + raw exception. + """ + pd = pytest.importorskip("pandas") + + # Patch ``to_json`` to raise so we exercise the except-arm. + class _BadDF(pd.DataFrame): + @property + def _constructor(self): + return _BadDF + + def to_json(self, *args, **kwargs): # noqa: ARG002 + raise RuntimeError("simulated to_json failure") + + df = _BadDF({"x": [1, 2]}) + with pytest.raises(ValueError, match="Failed to convert DataFrame"): + OmniWidgetResponseModel(content=df) + + +def test_omni_response_figure_to_json_failure_raises_value_error(): + """Plotly Figure ``to_json`` failure → clear ValueError. Exercises + the ``except Exception`` arm of the Figure-class branch. + """ + + class _BrokenFigure: + """Custom class whose ``__class__.__name__`` is 'Figure' but + whose ``to_json`` always raises — covers the failure arm + without needing a real plotly install. + """ + + def to_json(self): + raise RuntimeError("simulated to_json failure") + + _BrokenFigure.__name__ = "Figure" + + with pytest.raises(ValueError, match="Failed to convert chart to JSON"): + OmniWidgetResponseModel(content=_BrokenFigure()) + + +def test_omni_response_dict_of_lists_serialization_failure_raises_value_error(): + """The dict-of-lists branch transposes via stdlib ``zip(strict=True)`` + rather than pandas, so any input where the per-key lists have + different lengths surfaces as a ``ValueError`` from the transpose. + Mirrors the DataFrame-branch defensive arm but without an + optional-pandas dependency. + """ + with pytest.raises(ValueError, match="dictionary of lists"): + # ``zip(*..., strict=True)`` raises when the iterables have + # mismatched lengths — the transpose can't produce a + # meaningful list-of-records, so the validator surfaces the + # failure with the per-branch error message. + OmniWidgetResponseModel(content={"a": [1, 2, 3], "b": [3, 4]}) diff --git a/openbb_platform/extensions/platform_api/tests/service/test_agents_service.py b/openbb_platform/extensions/platform_api/tests/service/test_agents_service.py new file mode 100644 index 00000000000..914e18ed655 --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/service/test_agents_service.py @@ -0,0 +1,171 @@ +"""Test agents_service module.""" + +import pytest +from fastapi import APIRouter, FastAPI + +from openbb_platform_api.utils.merge_agents import ( + get_additional_agents, + has_additional_agents, +) + + +def _build_app(include_extra: bool = False, extra_returns_dict: bool = True) -> FastAPI: + app = FastAPI() + + @app.get("/agents.json") + async def root_agents(): + return {"root": {"id": "root-agent"}} + + @app.get("/health") + async def health(): + return {"status": "ok"} + + if include_extra: + + @app.get("/module/agents.json") + async def module_agents(): + if extra_returns_dict: + return {"module": {"id": "module-agent"}} + return ["not-a-dict"] + + return app + + +def _build_included_app() -> FastAPI: + """Attach an agents.json route via ``include_router``. + + FastAPI 0.137+ wraps included routes in ``_IncludedRouter`` instead + of flattening them into ``app.routes``, so the merge must walk the + routes through ``iter_api_routes`` to find them. + """ + app = FastAPI() + + @app.get("/agents.json") + async def root_agents(): + return {"root": {"id": "root-agent"}} + + sub = APIRouter() + + @sub.get("/agents.json") + async def sub_agents(): + return {"module": {"id": "module-agent"}} + + app.include_router(sub, prefix="/sub") + return app + + +def test_has_additional_agents_false_without_extra_routes(): + app = _build_app(include_extra=False) + assert not has_additional_agents(app) + + +def test_has_additional_agents_true_with_extra_routes(): + app = _build_app(include_extra=True) + assert has_additional_agents(app) + + +def test_has_additional_agents_true_with_included_router(): + """An agents.json route attached via include_router is detected (0.137+).""" + app = _build_included_app() + assert has_additional_agents(app) + + +@pytest.mark.asyncio +async def test_get_additional_agents_returns_empty_when_none(): + app = _build_app(include_extra=False) + assert await get_additional_agents(app) == {} + + +@pytest.mark.asyncio +async def test_get_additional_agents_collects_valid_routes(): + app = _build_app(include_extra=True) + additional = await get_additional_agents(app) + assert additional == {"/module/": {"module": {"id": "module-agent"}}} + + +@pytest.mark.asyncio +async def test_get_additional_agents_collects_from_included_router(): + """An agents.json route added via include_router is collected (0.137+ guard).""" + app = _build_included_app() + additional = await get_additional_agents(app) + assert additional == {"/sub/": {"module": {"id": "module-agent"}}} + + +@pytest.mark.asyncio +async def test_get_additional_agents_skips_non_dict_responses(): + app = _build_app(include_extra=True, extra_returns_dict=False) + assert await get_additional_agents(app) == {} + + +@pytest.mark.asyncio +async def test_get_additional_agents_skips_routes_with_no_endpoint_or_root_path(): + """Routes without an ``endpoint`` attr (defensive) and routes whose + path is the root ``/agents.json`` get skipped during the merge — + we only iterate over additional, non-root routes. + """ + from fastapi.routing import APIRoute + + app = FastAPI() + + @app.get("/module/agents.json") + async def module_agents(): + return {"module": {"id": "module-agent"}} + + # Stub a route with endpoint=None to exercise the endpoint skip arm. + fake_route = APIRoute( + path="/garbage/agents.json", + endpoint=lambda: None, + methods=["GET"], + ) + fake_route.endpoint = None # ty: ignore[invalid-assignment] + app.routes.append(fake_route) + + result = await get_additional_agents(app) + # Only the well-formed module route survived. + assert "/module/" in result + assert "/garbage/" not in result + + +@pytest.mark.asyncio +async def test_get_additional_agents_rewrites_relative_endpoint_paths(): + """Each agent's ``endpoints`` get prefixed with the route's path + (when they're absolute strings and not already prefixed) so Workspace + can call them directly; non-string values are left untouched. + """ + app = FastAPI() + + @app.get("/myrouter/agents.json") + async def router_agents(): + return { + "agent_a": { + "endpoints": { + "tool1": "/tool1", # relative, will get prefixed + "tool2": "/myrouter/tool2", # already prefixed, untouched + "tool3": "https://other-host.example/x", # not "/", untouched + "tool4": 123, # non-string, untouched + } + } + } + + result = await get_additional_agents(app) + rewritten = result["/myrouter/"]["agent_a"]["endpoints"] + assert rewritten["tool1"] == "/myrouter/tool1" + assert rewritten["tool2"] == "/myrouter/tool2" + assert rewritten["tool3"] == "https://other-host.example/x" + assert rewritten["tool4"] == 123 + + +@pytest.mark.asyncio +async def test_get_additional_agents_handles_non_dict_agent_value(): + """Defensive: an agent whose value isn't a dict (e.g. a string) is + handled without crashing the prefix-rewrite loop. + """ + app = FastAPI() + + @app.get("/x/agents.json") + async def router_agents(): + return {"agent": "not a dict"} + + result = await get_additional_agents(app) + # Survived without raising; the non-dict agent is preserved as-is. + assert result["/x/"]["agent"] == "not a dict" diff --git a/openbb_platform/extensions/platform_api/tests/service/test_apps_service.py b/openbb_platform/extensions/platform_api/tests/service/test_apps_service.py new file mode 100644 index 00000000000..12af0ebd65f --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/service/test_apps_service.py @@ -0,0 +1,123 @@ +"""Test apps_service module.""" + +import pytest +from fastapi import APIRouter, FastAPI + +from openbb_platform_api.utils.merge_apps import ( + get_additional_apps, + has_additional_apps, +) + + +def _build_app(include_extra: bool = False, extra_returns_list: bool = True) -> FastAPI: + app = FastAPI() + + @app.get("/apps.json") + async def root_apps(): + return [{"appId": "root", "endpoint": "/root"}] + + @app.get("/health") + async def health(): + return {"status": "ok"} + + if include_extra: + + @app.get("/module/apps.json") + async def module_apps(): + if extra_returns_list: + return [{"appId": "module", "endpoint": "/module/data"}] + return "not-a-list" + + return app + + +def _build_included_app() -> FastAPI: + """Attach an apps.json route via ``include_router``. + + FastAPI 0.137+ wraps included routes in ``_IncludedRouter`` instead + of flattening them into ``app.routes``, so the merge must walk the + routes through ``iter_api_routes`` to find them. + """ + app = FastAPI() + + @app.get("/apps.json") + async def root_apps(): + return [{"appId": "root"}] + + sub = APIRouter() + + @sub.get("/apps.json") + async def sub_apps(): + return [{"appId": "module", "endpoint": "/sub/data"}] + + app.include_router(sub, prefix="/sub") + return app + + +def test_has_additional_apps_false_without_extra_routes(): + app = _build_app(include_extra=False) + assert not has_additional_apps(app) + + +def test_has_additional_apps_true_with_extra_routes(): + app = _build_app(include_extra=True) + assert has_additional_apps(app) + + +def test_has_additional_apps_true_with_included_router(): + """An apps.json route attached via include_router is detected (0.137+).""" + app = _build_included_app() + assert has_additional_apps(app) + + +@pytest.mark.asyncio +async def test_get_additional_apps_returns_empty_when_none(): + app = _build_app(include_extra=False) + assert await get_additional_apps(app) == {} + + +@pytest.mark.asyncio +async def test_get_additional_apps_skips_non_list_responses(): + app = _build_app(include_extra=True, extra_returns_list=False) + assert await get_additional_apps(app) == {} + + +@pytest.mark.asyncio +async def test_get_additional_apps_collects_valid_routes(): + app = _build_app(include_extra=True) + apps = await get_additional_apps(app) + assert apps == {"/module/": [{"appId": "module", "endpoint": "/module/data"}]} + + +@pytest.mark.asyncio +async def test_get_additional_apps_collects_from_included_router(): + """An apps.json route added via include_router is collected (0.137+ guard).""" + app = _build_included_app() + apps = await get_additional_apps(app) + assert apps == {"/sub/": [{"appId": "module", "endpoint": "/sub/data"}]} + + +@pytest.mark.asyncio +async def test_get_additional_apps_skips_routes_with_no_endpoint_or_root_path(): + """Routes whose endpoint is None or whose path is the root + ``/apps.json`` are explicitly skipped during the merge. + """ + from fastapi.routing import APIRoute + + app = FastAPI() + + @app.get("/module/apps.json") + async def module_apps(): + return [{"appId": "module"}] + + fake_route = APIRoute( + path="/garbage/apps.json", + endpoint=lambda: None, + methods=["GET"], + ) + fake_route.endpoint = None # ty: ignore[invalid-assignment] + app.routes.append(fake_route) + + result = await get_additional_apps(app) + assert "/module/" in result + assert "/garbage/" not in result diff --git a/openbb_platform/extensions/platform_api/tests/service/test_widgets_service.py b/openbb_platform/extensions/platform_api/tests/service/test_widgets_service.py new file mode 100644 index 00000000000..3df4e25cb44 --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/service/test_widgets_service.py @@ -0,0 +1,588 @@ +"""Tests for ``openbb_platform_api.service.widgets_service``. + +Covers the editable-on-disk flow (build → diff → prompt → write) and +the ephemeral in-memory build path. Each test isolates the module's +mutable state (``FIRST_RUN`` / ``PATH_WIDGETS``) so test order doesn't +matter. +""" + +import json +from unittest.mock import MagicMock, patch + +import pytest + +from openbb_platform_api.service import widgets_service + + +@pytest.fixture +def reset_widgets_service_state(): + """Snapshot + restore module-level state around each test.""" + original_first_run = widgets_service.FIRST_RUN + original_path_widgets = widgets_service.PATH_WIDGETS.copy() + yield + widgets_service.FIRST_RUN = original_first_run + widgets_service.PATH_WIDGETS = original_path_widgets + + +def test_get_widgets_json_ephemeral_build_non_router_path_widgets( + reset_widgets_service_state, monkeypatch +): + """Non-editable mode → ``build_json`` is the source of truth, no + disk I/O. Router-attached widgets whose key *doesn't* end in ``/`` + are merged in (the trailing-``/`` keys auto-add a ``*`` exclude + that then prevents their own merge — that's an intentional + deduplication for trailing-slash router prefixes). + """ + widgets_service.FIRST_RUN = False + widgets_service.PATH_WIDGETS = { + # Key without trailing ``/`` — survives the dedup logic and + # merges into the final dict. + "router_namespace": {"router_widget": {"name": "Router"}}, + } + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value={"auto_widget": {"name": "Auto"}}), + ) + + result = widgets_service.get_widgets_json( + _build=False, _openapi={}, widget_exclude_filter=[] + ) + # Both auto and router-attached widgets in the merged set. + assert result["auto_widget"] == {"name": "Auto"} + assert result["router_widget"] == {"name": "Router"} + + +def test_get_widgets_json_excludes_widget_id_from_path_widgets( + reset_widgets_service_state, monkeypatch +): + """A widget id explicitly in the exclude filter is dropped from + the router-attached merge step — the inner ``if widget_id not in + widget_exclude_filter`` guard. + """ + widgets_service.FIRST_RUN = False + widgets_service.PATH_WIDGETS = { + "router": { + "kept": {"name": "Kept"}, + "dropped_id": {"name": "Dropped"}, + }, + } + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value={}), + ) + + result = widgets_service.get_widgets_json( + _build=False, + _openapi={}, + widget_exclude_filter=["dropped_id"], + ) + assert result == {"kept": {"name": "Kept"}} + + +def test_get_widgets_json_trailing_slash_path_widgets_self_dedup( + reset_widgets_service_state, monkeypatch +): + """Path-widget keys ending in ``/`` get auto-added to the exclude + filter as ``*``-globs so the auto-builder skips them. The merge + step then also skips them — net effect: trailing-slash router + prefixes are EXPECTED to be defined entirely by the router (not + auto-merged here). Lock this contract so a future refactor can't + silently flip it. + """ + widgets_service.FIRST_RUN = False + widgets_service.PATH_WIDGETS = { + "/router_prefix/": {"router_widget": {"name": "Router"}}, + } + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value={"auto": {"name": "Auto"}}), + ) + + result = widgets_service.get_widgets_json( + _build=False, _openapi={}, widget_exclude_filter=[] + ) + # Auto present; router-attached path with trailing-/ is self-excluded. + assert "auto" in result + assert "router_widget" not in result + + +def test_get_widgets_json_editable_writes_when_diff_and_overwrite( + tmp_path, reset_widgets_service_state, monkeypatch +): + """Editable + diff present + user answers ``y`` → file is rewritten + with the fresh build. + """ + widgets_service.FIRST_RUN = False + widgets_path = tmp_path / "widgets.json" + widgets_path.write_text(json.dumps({"old": {"name": "Stale"}})) + + fresh = {"new": {"name": "Fresh"}} + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value=fresh), + ) + + with patch("builtins.input", return_value="y"): + result = widgets_service.get_widgets_json( + _build=True, + _openapi={}, + widget_exclude_filter=[], + editable=True, + widgets_path=str(widgets_path), + ) + + assert result == fresh + # File on disk now reflects the fresh build. + assert json.loads(widgets_path.read_text()) == fresh + + +def test_get_widgets_json_editable_appends_when_user_answers_n( + tmp_path, reset_widgets_service_state, monkeypatch +): + """User answers ``n`` → fresh build is appended to existing rather + than overwriting (existing entries win). + """ + widgets_service.FIRST_RUN = False + widgets_path = tmp_path / "widgets.json" + existing = {"old": {"name": "Stale"}} + widgets_path.write_text(json.dumps(existing)) + + fresh = {"new": {"name": "Fresh"}, "old": {"name": "Overwritten"}} + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value=fresh), + ) + + with patch("builtins.input", return_value="n"): + result = widgets_service.get_widgets_json( + _build=True, + _openapi={}, + widget_exclude_filter=[], + editable=True, + widgets_path=str(widgets_path), + ) + + # Existing took precedence on the conflicting ``old`` key. + assert result["old"]["name"] == "Stale" + # New entries from the build still merged in. + assert result["new"]["name"] == "Fresh" + + +def test_get_widgets_json_editable_ignores_when_user_answers_i( + tmp_path, reset_widgets_service_state, monkeypatch +): + """User answers ``i`` → file untouched; existing widgets returned.""" + widgets_service.FIRST_RUN = False + widgets_path = tmp_path / "widgets.json" + existing = {"keep_me": {"name": "Stable"}} + widgets_path.write_text(json.dumps(existing)) + + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value={"new": {"name": "Fresh"}}), + ) + + with patch("builtins.input", return_value="i"): + result = widgets_service.get_widgets_json( + _build=True, + _openapi={}, + widget_exclude_filter=[], + editable=True, + widgets_path=str(widgets_path), + ) + # File on disk unchanged. + assert json.loads(widgets_path.read_text()) == existing + assert result == existing + + +def test_get_widgets_json_editable_creates_missing_file( + tmp_path, reset_widgets_service_state, monkeypatch +): + """When the widgets file doesn't exist, the editable path forces + a build, creates the parent directory, and writes the result. + """ + widgets_service.FIRST_RUN = False + widgets_path = tmp_path / "deep" / "nested" / "widgets.json" + fresh = {"x": {"name": "Fresh"}} + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value=fresh), + ) + + # No diff because the file didn't exist → no prompt. + result = widgets_service.get_widgets_json( + _build=False, # forced to True internally because file didn't exist + _openapi={}, + widget_exclude_filter=[], + editable=True, + widgets_path=str(widgets_path), + ) + assert result == fresh + assert widgets_path.exists() + assert json.loads(widgets_path.read_text()) == fresh + + +def test_get_widgets_json_first_run_populates_path_widgets_from_app( + reset_widgets_service_state, monkeypatch +): + """First-run + a FastAPI app with extra widget routes → + ``PATH_WIDGETS`` gets populated via ``run_async``. Exercises the + ``FIRST_RUN`` mutation branch. + """ + from fastapi import FastAPI + + widgets_service.FIRST_RUN = True + widgets_service.PATH_WIDGETS = {} + + fake_app = FastAPI() + + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value={}), + ) + monkeypatch.setattr( + "openbb_platform_api.utils.merge_widgets.has_additional_widgets", + lambda app: True, + ) + + captured: dict = {} + + def fake_run_async(callable_or_coroutine, *args, **kwargs): + captured["called"] = True + return {"/extra/": {"router_widget": {"name": "Router"}}} + + monkeypatch.setattr("openbb_core.provider.utils.helpers.run_async", fake_run_async) + + widgets_service.get_widgets_json( + _build=False, + _openapi={}, + widget_exclude_filter=[], + editable=False, + widgets_path=None, + app=fake_app, + ) + + assert captured.get("called") is True + assert "/extra/" in widgets_service.PATH_WIDGETS + + +def test_get_widgets_json_editable_default_path_when_widgets_path_omitted( + reset_widgets_service_state, monkeypatch, tmp_path +): + """Editable + no ``widgets_path`` → derive path from + ``sys.executable``'s parent. Exercises the env-default branch. + """ + widgets_service.FIRST_RUN = False + + fresh = {"new": {"name": "Fresh"}} + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value=fresh), + ) + # Point sys.executable at a real path under tmp_path so the derived + # ``parent.parents[1] / "assets" / "widgets.json"`` location is + # writable. + fake_python = tmp_path / "envs" / "myenv" / "bin" / "python" + fake_python.parent.mkdir(parents=True) + fake_python.write_text("") + monkeypatch.setattr("sys.executable", str(fake_python)) + + result = widgets_service.get_widgets_json( + _build=True, + _openapi={}, + widget_exclude_filter=[], + editable=True, + widgets_path=None, # ← exercises the default-path branch + ) + assert result == fresh + + +def test_get_widgets_json_editable_falls_back_to_existing_when_write_fails( + tmp_path, reset_widgets_service_state, monkeypatch +): + """A disk-write failure during the editable build is logged but + doesn't kill the response. When the existing file had content, + that's what callers get back — favouring the user's stored config + over an unsaved fresh build keeps the loaded session stable. + """ + widgets_service.FIRST_RUN = False + widgets_path = tmp_path / "widgets.json" + existing = {"old": {"name": "Stale"}} + widgets_path.write_text(json.dumps(existing)) + + fresh = {"new": {"name": "Fresh"}} + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + MagicMock(return_value=fresh), + ) + + real_open = open + + def failing_open(path, mode="r", *args, **kwargs): + if "w" in mode: + raise OSError("simulated write failure") + return real_open(path, mode, *args, **kwargs) + + with ( + patch("builtins.open", side_effect=failing_open), + patch("builtins.input", return_value="y"), + ): + result = widgets_service.get_widgets_json( + _build=True, + _openapi={}, + widget_exclude_filter=[], + editable=True, + widgets_path=str(widgets_path), + ) + # Existing on-disk widgets returned because they were non-empty — + # the function picks ``existing_widgets_json`` over the fresh + # build when both are available and the write failed. + assert result == existing + + +# --------------------------------------------------------------------------- +# Spec-driven source override — ``[\"Custom\"]`` → ``[spec_name]`` +# --------------------------------------------------------------------------- + + +def test_get_widgets_json_overrides_custom_source_with_spec_source( + reset_widgets_service_state, monkeypatch +): + """Spec-driven launches stash the spec file's full name (extension + included) on ``app.state.openbb_spec_source``. Every widget whose + builder emitted the default ``["Custom"]`` source citation gets + that label replaced with the spec filename so dashboards display + the actual file the data came from + (e.g. ``["fertilizer.spec"]`` for + ``/etc/openbb/fertilizer.spec``). + """ + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + lambda _o, _f: { + "w1": {"source": ["Custom"], "name": "Widget 1"}, + "w2": {"source": ["Custom"], "name": "Widget 2"}, + }, + ) + app = MagicMock() + app.routes = [] + app.state.openbb_spec_source = "fertilizer.spec" + + result = widgets_service.get_widgets_json( + False, {}, [], editable=False, widgets_path=None, app=app + ) + assert result["w1"]["source"] == ["fertilizer.spec"] + assert result["w2"]["source"] == ["fertilizer.spec"] + + +def test_get_widgets_json_preserves_explicit_source_overrides( + reset_widgets_service_state, monkeypatch +): + """A widget whose ``source`` was explicitly set via + ``widget_config`` (i.e. anything other than the default + ``["Custom"]``) is left untouched — the spec-name override only + replaces the default citation, never an author-supplied one. + """ + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + lambda _o, _f: { + "default_widget": {"source": ["Custom"], "name": "Auto"}, + "explicit_widget": {"source": ["FRED"], "name": "Hand-Set"}, + }, + ) + app = MagicMock() + app.routes = [] + app.state.openbb_spec_source = "fertilizer.spec" + + result = widgets_service.get_widgets_json( + False, {}, [], editable=False, widgets_path=None, app=app + ) + # Default → overridden with spec filename. + assert result["default_widget"]["source"] == ["fertilizer.spec"] + # Explicit → untouched. + assert result["explicit_widget"]["source"] == ["FRED"] + + +def test_get_widgets_json_no_override_when_spec_source_unset( + reset_widgets_service_state, monkeypatch +): + """Non-spec-driven launches (e.g. ``--app`` or default openbb-core) + don't have ``openbb_spec_source`` on ``app.state``; the override + is a no-op and the auto-generated ``["Custom"]`` source flows + through. + """ + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + lambda _o, _f: {"w1": {"source": ["Custom"], "name": "Widget 1"}}, + ) + # Plain MagicMock state — no ``openbb_spec_source`` attr; the + # ``getattr(..., None)`` lookup in the helper returns None. + app = MagicMock() + app.routes = [] + app.state = MagicMock(spec=[]) # truly empty state namespace + + result = widgets_service.get_widgets_json( + False, {}, [], editable=False, widgets_path=None, app=app + ) + assert result["w1"]["source"] == ["Custom"] + + +def test_get_widgets_json_overrides_default_mcp_server_with_spec_source( + reset_widgets_service_state, monkeypatch +): + """``build_json`` hardcodes ``mcp_tool.mcp_server: "Open Data + Platform"`` (v4 platform default). Spec-driven launches replace + that with the spec file's name so the synthesized backend + advertises itself under its OWN MCP namespace, not the + platform's. The ``tool_id`` is also re-scoped with the spec + stem prefix so MCP tool IDs stay globally unique across servers. + """ + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + lambda _o, _f: { + "w1": { + "source": ["Custom"], + "mcp_tool": { + "mcp_server": "Open Data Platform", + "tool_id": "resource_8bgf-5mdv.json", + }, + }, + "w2": { + "source": ["Custom"], + "mcp_tool": { + "mcp_server": "Open Data Platform", + "tool_id": "equity_price_historical", + }, + }, + }, + ) + app = MagicMock() + app.routes = [] + app.state.openbb_spec_source = "fertilizer.spec" + + result = widgets_service.get_widgets_json( + False, {}, [], editable=False, widgets_path=None, app=app + ) + assert result["w1"]["mcp_tool"]["mcp_server"] == "fertilizer.spec" + assert result["w2"]["mcp_tool"]["mcp_server"] == "fertilizer.spec" + # ``tool_id`` gets the stem-prefixed scope so multiple spec-driven + # launchers wired to the same MCP client don't collide. + assert result["w1"]["mcp_tool"]["tool_id"] == "fertilizer__resource_8bgf-5mdv.json" + assert result["w2"]["mcp_tool"]["tool_id"] == "fertilizer__equity_price_historical" + + +def test_get_widgets_json_tool_id_uses_path_stem_for_dotted_filenames( + reset_widgets_service_state, monkeypatch +): + """``Path().stem`` strips only the LAST extension, so a spec + named ``my.app.spec`` becomes the namespace ``my.app``. Matches + what most operators would call the namespace. + """ + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + lambda _o, _f: { + "w": { + "source": ["Custom"], + "mcp_tool": { + "mcp_server": "Open Data Platform", + "tool_id": "my_widget", + }, + } + }, + ) + app = MagicMock() + app.routes = [] + app.state.openbb_spec_source = "my.app.spec" + + result = widgets_service.get_widgets_json( + False, {}, [], editable=False, widgets_path=None, app=app + ) + assert result["w"]["mcp_tool"]["tool_id"] == "my.app__my_widget" + + +def test_get_widgets_json_preserves_explicit_mcp_server_overrides( + reset_widgets_service_state, monkeypatch +): + """A widget whose ``mcp_tool.mcp_server`` was explicitly set via + ``widget_config`` (anything other than the v4 platform default + ``"Open Data Platform"``) is left untouched, including its + ``tool_id``. The author owns the whole ``mcp_tool`` block when + they override the namespace — the launcher doesn't second-guess + the ID format. + """ + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + lambda _o, _f: { + "default_mcp": { + "source": ["Custom"], + "mcp_tool": { + "mcp_server": "Open Data Platform", + "tool_id": "default_mcp", + }, + }, + "custom_mcp": { + "source": ["Custom"], + "mcp_tool": { + "mcp_server": "Hand-Set MCP", + "tool_id": "custom_mcp", + }, + }, + }, + ) + app = MagicMock() + app.routes = [] + app.state.openbb_spec_source = "fertilizer.spec" + + result = widgets_service.get_widgets_json( + False, {}, [], editable=False, widgets_path=None, app=app + ) + # Default → overridden with spec filename + stem-scoped tool_id. + assert result["default_mcp"]["mcp_tool"]["mcp_server"] == "fertilizer.spec" + assert result["default_mcp"]["mcp_tool"]["tool_id"] == "fertilizer__default_mcp" + # Explicit → both fields untouched (author owns the namespace). + assert result["custom_mcp"]["mcp_tool"]["mcp_server"] == "Hand-Set MCP" + assert result["custom_mcp"]["mcp_tool"]["tool_id"] == "custom_mcp" + + +def test_get_widgets_json_skips_mcp_override_when_mcp_tool_missing_or_malformed( + reset_widgets_service_state, monkeypatch +): + """A widget without ``mcp_tool``, or with a non-dict + ``mcp_tool``, is silently skipped — defensive against partial + widget configs and forward-compatible if the schema changes. + """ + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + lambda _o, _f: { + "no_mcp": {"source": ["Custom"]}, + "malformed_mcp": {"source": ["Custom"], "mcp_tool": "not a dict"}, + }, + ) + app = MagicMock() + app.routes = [] + app.state.openbb_spec_source = "fertilizer.spec" + + # No crash; both widgets still get the source override. + result = widgets_service.get_widgets_json( + False, {}, [], editable=False, widgets_path=None, app=app + ) + assert result["no_mcp"]["source"] == ["fertilizer.spec"] + assert "mcp_tool" not in result["no_mcp"] + assert result["malformed_mcp"]["source"] == ["fertilizer.spec"] + assert result["malformed_mcp"]["mcp_tool"] == "not a dict" + + +def test_get_widgets_json_no_override_when_app_is_none( + reset_widgets_service_state, monkeypatch +): + """Defensive: ``app`` may be ``None`` in code paths that don't + pass it through (legacy callers). The override helper returns + silently rather than raising on ``None.state``. + """ + monkeypatch.setattr( + "openbb_platform_api.utils.widgets.build_json", + lambda _o, _f: {"w1": {"source": ["Custom"], "name": "Widget 1"}}, + ) + result = widgets_service.get_widgets_json( + False, {}, [], editable=False, widgets_path=None, app=None + ) + assert result["w1"]["source"] == ["Custom"] diff --git a/openbb_platform/extensions/platform_api/tests/test_compat_shims.py b/openbb_platform/extensions/platform_api/tests/test_compat_shims.py new file mode 100644 index 00000000000..7e6fc66395e --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/test_compat_shims.py @@ -0,0 +1,152 @@ +"""Backwards-compatibility shim coverage. + +The V5 reorganization moved code into ``models/`` / ``app/`` / +``service/`` subpackages. The legacy import paths are preserved as +shim modules; these tests assert each one re-exports the public +surface so external callers keep working without code changes. +""" + + +def test_query_models_shim_re_exports_omni_widget_input(): + """``openbb_platform_api.query_models.OmniWidgetInput`` is the + same class as ``openbb_platform_api.models.query.OmniWidgetInput``. + """ + from openbb_platform_api import query_models + from openbb_platform_api.models.query import OmniWidgetInput + + assert query_models.OmniWidgetInput is OmniWidgetInput + assert "OmniWidgetInput" in query_models.__all__ + + +def test_response_models_shim_re_exports_response_classes(): + """``openbb_platform_api.response_models`` exposes the V5 + response-model classes verbatim. + """ + from openbb_platform_api import response_models + from openbb_platform_api.models.response import ( + MetricResponseModel, + OmniWidgetResponseModel, + PdfResponseModel, + ) + + assert response_models.MetricResponseModel is MetricResponseModel + assert response_models.OmniWidgetResponseModel is OmniWidgetResponseModel + assert response_models.PdfResponseModel is PdfResponseModel + + +def test_utils_merge_apps_shim_re_exports_apps_service(): + """``utils.merge_apps`` shim points at the V5 service module.""" + from openbb_platform_api.service.apps_service import ( + get_additional_apps, + has_additional_apps, + ) + from openbb_platform_api.utils import merge_apps + + assert merge_apps.has_additional_apps is has_additional_apps + assert merge_apps.get_additional_apps is get_additional_apps + + +def test_utils_merge_agents_shim_re_exports_agents_service(): + """``utils.merge_agents`` shim points at the V5 service module.""" + from openbb_platform_api.service.agents_service import ( + get_additional_agents, + has_additional_agents, + ) + from openbb_platform_api.utils import merge_agents + + assert merge_agents.has_additional_agents is has_additional_agents + assert merge_agents.get_additional_agents is get_additional_agents + + +def test_utils_api_shim_re_exports_split_helpers(): + """``utils.api`` shim covers all the split-out helpers — args, + bootstrap, network, widgets_service — under the legacy name. + """ + from openbb_platform_api.app.args import ( + LAUNCH_SCRIPT_DESCRIPTION, + parse_args, + ) + from openbb_platform_api.app.bootstrap import import_app + from openbb_platform_api.service.widgets_service import get_widgets_json + from openbb_platform_api.utils import api as api_shim + from openbb_platform_api.utils.network import check_port, get_user_settings + + assert api_shim.parse_args is parse_args + assert api_shim.LAUNCH_SCRIPT_DESCRIPTION is LAUNCH_SCRIPT_DESCRIPTION + assert api_shim.import_app is import_app + assert api_shim.check_port is check_port + assert api_shim.get_user_settings is get_user_settings + assert api_shim.get_widgets_json is get_widgets_json + + +def test_utils_api_shim_first_run_and_path_widgets_proxy_to_service(): + """``FIRST_RUN`` and ``PATH_WIDGETS`` go through ``__getattr__`` so + reads always reflect the live service state — not a stale snapshot. + """ + from openbb_platform_api.service import widgets_service + from openbb_platform_api.utils import api as api_shim + + # Mutate the canonical state and confirm the shim reflects it. + original_first_run = widgets_service.FIRST_RUN + original_path_widgets = widgets_service.PATH_WIDGETS + try: + widgets_service.FIRST_RUN = False + widgets_service.PATH_WIDGETS = {"x": {"some": "widget"}} + assert api_shim.FIRST_RUN is False + assert api_shim.PATH_WIDGETS == {"x": {"some": "widget"}} + finally: + widgets_service.FIRST_RUN = original_first_run + widgets_service.PATH_WIDGETS = original_path_widgets + + +def test_main_module_re_exports_app_app_namespace(): + """``openbb_platform_api.main`` is the canonical entry point uvicorn + consumes via ``main:app``. The shim must surface ``app``, + ``main``, ``launch_api``, and the route handlers from the V5 + ``app.app`` module. + """ + from openbb_platform_api import main as main_shim + from openbb_platform_api.app import app as v5_app_module + + for name in ("app", "main", "launch_api", "logger", "openapi"): + assert getattr(main_shim, name) is getattr(v5_app_module, name) + + +def test_utils_api_shim_unknown_attr_raises(): + """Unknown attribute access on the shim raises a clean ``AttributeError`` + (not the catch-all module-not-found ``__getattr__`` returns ``None`` of + a sloppy implementation). + """ + import pytest + + from openbb_platform_api.utils import api as api_shim + + with pytest.raises(AttributeError): + _ = api_shim.no_such_attribute + + +def test_utils_api_shim_dir_lists_lazy_targets(): + """``dir(api_shim)`` surfaces the lazy re-exported names so IDE + autocomplete / ``inspect.getmembers`` users see the same surface + they'd get from a non-shim module. The set must include each + legacy name we're proxying. + """ + from openbb_platform_api.utils import api as api_shim + + listed = dir(api_shim) + # Spot-check the whole legacy surface — if any of these names + # disappears the shim has silently regressed. + for name in ( + "LAUNCH_SCRIPT_DESCRIPTION", + "parse_args", + "import_app", + "get_widgets_json", + "logger", + "FIRST_RUN", + "PATH_WIDGETS", + "check_port", + "get_user_settings", + ): + assert name in listed + # Output is stable / sorted so callers can rely on ordering. + assert listed == sorted(listed) diff --git a/openbb_platform/extensions/platform_api/tests/test_merge_agents.py b/openbb_platform/extensions/platform_api/tests/test_merge_agents.py deleted file mode 100644 index 91bccd431eb..00000000000 --- a/openbb_platform/extensions/platform_api/tests/test_merge_agents.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Test merge_agents module.""" - -import pytest -from fastapi import FastAPI -from openbb_platform_api.utils.merge_agents import ( - get_additional_agents, - has_additional_agents, -) - - -def _build_app(include_extra: bool = False, extra_returns_dict: bool = True) -> FastAPI: - app = FastAPI() - - @app.get("/agents.json") - async def root_agents(): - return {"root": {"id": "root-agent"}} - - if include_extra: - - @app.get("/module/agents.json") - async def module_agents(): - if extra_returns_dict: - return {"module": {"id": "module-agent"}} - return ["not-a-dict"] - - return app - - -def test_has_additional_agents_false_without_extra_routes(): - app = _build_app(include_extra=False) - assert not has_additional_agents(app) - - -def test_has_additional_agents_true_with_extra_routes(): - app = _build_app(include_extra=True) - assert has_additional_agents(app) - - -@pytest.mark.asyncio -async def test_get_additional_agents_returns_empty_when_none(): - app = _build_app(include_extra=False) - assert await get_additional_agents(app) == {} - - -@pytest.mark.asyncio -async def test_get_additional_agents_collects_valid_routes(): - app = _build_app(include_extra=True) - additional = await get_additional_agents(app) - assert additional == {"/module/": {"module": {"id": "module-agent"}}} - - -@pytest.mark.asyncio -async def test_get_additional_agents_skips_non_dict_responses(): - app = _build_app(include_extra=True, extra_returns_dict=False) - assert await get_additional_agents(app) == {} diff --git a/openbb_platform/extensions/platform_api/tests/test_merge_apps.py b/openbb_platform/extensions/platform_api/tests/test_merge_apps.py deleted file mode 100644 index 9cc3f8051ae..00000000000 --- a/openbb_platform/extensions/platform_api/tests/test_merge_apps.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Test merge_apps module.""" - -import pytest -from fastapi import FastAPI -from openbb_platform_api.utils.merge_apps import ( - get_additional_apps, - has_additional_apps, -) - - -def _build_app(include_extra: bool = False, extra_returns_list: bool = True) -> FastAPI: - app = FastAPI() - - @app.get("/apps.json") - async def root_apps(): - return [{"appId": "root", "endpoint": "/root"}] - - if include_extra: - - @app.get("/module/apps.json") - async def module_apps(): - if extra_returns_list: - return [{"appId": "module", "endpoint": "/module/data"}] - return "not-a-list" - - return app - - -def test_has_additional_apps_false_without_extra_routes(): - app = _build_app(include_extra=False) - assert not has_additional_apps(app) - - -def test_has_additional_apps_true_with_extra_routes(): - app = _build_app(include_extra=True) - assert has_additional_apps(app) - - -@pytest.mark.asyncio -async def test_get_additional_apps_returns_empty_when_none(): - app = _build_app(include_extra=False) - assert await get_additional_apps(app) == {} - - -@pytest.mark.asyncio -async def test_get_additional_apps_skips_non_list_responses(): - app = _build_app(include_extra=True, extra_returns_list=False) - assert await get_additional_apps(app) == {} - - -@pytest.mark.asyncio -async def test_get_additional_apps_collects_valid_routes(): - app = _build_app(include_extra=True) - apps = await get_additional_apps(app) - assert apps == {"/module/": [{"appId": "module", "endpoint": "/module/data"}]} diff --git a/openbb_platform/extensions/platform_api/tests/test_merge_widgets.py b/openbb_platform/extensions/platform_api/tests/test_merge_widgets.py deleted file mode 100644 index 910fe5b15ee..00000000000 --- a/openbb_platform/extensions/platform_api/tests/test_merge_widgets.py +++ /dev/null @@ -1,136 +0,0 @@ -"""Test merge_widgets module.""" - -import copy - -import pytest -from fastapi import FastAPI -from openbb_platform_api.utils.merge_widgets import ( - fix_router_widgets, - get_additional_widgets, - get_and_fix_widget_paths, - has_additional_widgets, -) - - -def _build_app(include_extra: bool = False, extra_returns_dict: bool = True) -> FastAPI: - app = FastAPI() - - @app.get("/widgets.json") - async def root_widgets(): - return {"root": {"widgetId": "root", "endpoint": "/root"}} - - if include_extra: - - @app.get("/module/widgets.json") - async def module_widgets(): - if extra_returns_dict: - return { - "module": { - "widgetId": "module", - "endpoint": "/module/data", - "params": [], - } - } - return "not-a-dict" - - return app - - -def test_has_additional_widgets_false_without_extra_routes(): - app = _build_app(include_extra=False) - assert not has_additional_widgets(app) - - -def test_has_additional_widgets_true_with_extra_routes(): - app = _build_app(include_extra=True) - assert has_additional_widgets(app) - - -@pytest.mark.asyncio -async def test_get_additional_widgets_returns_empty_when_none(): - app = _build_app(include_extra=False) - assert await get_additional_widgets(app) == {} - - -@pytest.mark.asyncio -async def test_get_additional_widgets_skips_non_dict_responses(): - app = _build_app(include_extra=True, extra_returns_dict=False) - assert await get_additional_widgets(app) == {} - - -@pytest.mark.asyncio -async def test_get_additional_widgets_collects_valid_routes(): - app = _build_app(include_extra=True) - widgets = await get_additional_widgets(app) - assert widgets == { - "/module/": { - "module": { - "widgetId": "module", - "endpoint": "/module/data", - "params": [], - } - } - } - - -def test_fix_router_widgets_updates_nested_paths_without_mutating_source(): - original = { - "widgetA": { - "widgetId": "widgetA", - "endpoint": "/data", - "wsEndpoint": "/stream", - "imgUrl": "/images/icon.png", - "params": [ - { - "name": "param1", - "endpoint": "/param", - "optionsEndpoint": "/param/options", - }, - {"name": "param2", "endpoint": "http://external/api"}, - ], - }, - "widgetB/widgets.json": {"endpoint": "/should/skip"}, - "not_a_dict": "skip_me", - } - snapshot = copy.deepcopy(original) - updated = fix_router_widgets("/api/", original) - - assert snapshot == original - assert list(updated.keys()) == ["widgetA"] - widget = updated["widgetA"] - assert widget["endpoint"] == "/api/data" - assert widget["wsEndpoint"] == "/api/stream" - assert widget["imgUrl"] == "/api/images/icon.png" - assert widget["params"][0]["endpoint"] == "/api/param" - assert widget["params"][0]["optionsEndpoint"] == "/api/param/options" - assert widget["params"][1]["endpoint"] == "http://external/api" - - -@pytest.mark.asyncio -async def test_get_and_fix_widget_paths_integrates_collection_and_fixing(): - app = FastAPI() - - @app.get("/widgets.json") - async def root_widgets(): - return {} - - @app.get("/api/widgets.json") - async def api_widgets(): - return { - "first": { - "widgetId": "first", - "endpoint": "/data", - "wsEndpoint": "/stream", - "imgUrl": "/assets/icon.png", - "params": [{"endpoint": "/param"}], - }, - "ignore": "non-dict", - } - - result = await get_and_fix_widget_paths(app) - assert "/api/" in result - fixed = result["/api/"]["first"] - assert fixed["endpoint"] == "/api/data" - assert fixed["wsEndpoint"] == "/api/stream" - assert fixed["imgUrl"] == "/api/assets/icon.png" - assert fixed["params"][0]["endpoint"] == "/api/param" diff --git a/openbb_platform/extensions/platform_api/tests/test_openapi_utils.py b/openbb_platform/extensions/platform_api/tests/test_openapi_utils.py deleted file mode 100644 index 86a43ce1103..00000000000 --- a/openbb_platform/extensions/platform_api/tests/test_openapi_utils.py +++ /dev/null @@ -1,585 +0,0 @@ -"""Test OpenAPI Utils.""" - -# pylint: disable=redefined-outer-name,line-too-long -# flake8: noqa: E501 - -import json -from pathlib import Path - -import pytest -from openbb_platform_api.utils.openapi import ( - _extract_provider_description, - data_schema_to_columns_defs, - get_data_schema_for_widget, - get_query_schema_for_widget, - post_query_schema_for_widget, - process_parameter, -) - - -# Load the mock OpenAPI JSON -@pytest.fixture(scope="module") -def mock_openapi_json(): - mock_openapi_path = Path(__file__).parent / "mock_openapi.json" - with open(mock_openapi_path, encoding="utf-8") as file: - return json.load(file) - - -@pytest.mark.parametrize( - "path, params_number, query_schema, expected_has_chart", - [ - ( - "/api/v1/economy/balance_of_payments", - 4, - [ - { - "parameter_name": "provider", - "label": "Provider", - "description": "Source of the data.", - "optional": True, - "type": "text", - "value": None, - "show": False, - "available_providers": ["fred"], - }, - { - "parameter_name": "country", - "label": "Country", - "description": "The country to get data. Enter as a 3-letter ISO country code, default is USA.", - "optional": True, - "type": "text", - "value": "united_states", - "multiple_items_allowed": {}, - "options": { - "fred": [ - {"label": "argentina", "value": "argentina"}, - {"label": "australia", "value": "australia"}, - {"label": "austria", "value": "austria"}, - {"label": "belgium", "value": "belgium"}, - {"label": "brazil", "value": "brazil"}, - {"label": "canada", "value": "canada"}, - {"label": "chile", "value": "chile"}, - {"label": "china", "value": "china"}, - {"label": "colombia", "value": "colombia"}, - {"label": "costa_rica", "value": "costa_rica"}, - {"label": "czechia", "value": "czechia"}, - {"label": "denmark", "value": "denmark"}, - {"label": "estonia", "value": "estonia"}, - {"label": "finland", "value": "finland"}, - {"label": "france", "value": "france"}, - {"label": "germany", "value": "germany"}, - {"label": "greece", "value": "greece"}, - {"label": "hungary", "value": "hungary"}, - {"label": "iceland", "value": "iceland"}, - {"label": "india", "value": "india"}, - {"label": "indonesia", "value": "indonesia"}, - {"label": "ireland", "value": "ireland"}, - {"label": "israel", "value": "israel"}, - {"label": "italy", "value": "italy"}, - {"label": "japan", "value": "japan"}, - {"label": "korea", "value": "korea"}, - {"label": "latvia", "value": "latvia"}, - {"label": "lithuania", "value": "lithuania"}, - {"label": "luxembourg", "value": "luxembourg"}, - {"label": "mexico", "value": "mexico"}, - {"label": "netherlands", "value": "netherlands"}, - {"label": "new_zealand", "value": "new_zealand"}, - {"label": "norway", "value": "norway"}, - {"label": "poland", "value": "poland"}, - {"label": "portugal", "value": "portugal"}, - {"label": "russia", "value": "russia"}, - {"label": "saudi_arabia", "value": "saudi_arabia"}, - {"label": "slovak_republic", "value": "slovak_republic"}, - {"label": "slovenia", "value": "slovenia"}, - {"label": "south_africa", "value": "south_africa"}, - {"label": "spain", "value": "spain"}, - {"label": "sweden", "value": "sweden"}, - {"label": "switzerland", "value": "switzerland"}, - {"label": "turkey", "value": "turkey"}, - {"label": "united_kingdom", "value": "united_kingdom"}, - {"label": "united_states", "value": "united_states"}, - {"label": "g7", "value": "g7"}, - {"label": "g20", "value": "g20"}, - ] - }, - "x-widget_config": {}, - "available_providers": ["fred"], - "show": True, - }, - { - "parameter_name": "start_date", - "label": "Start Date", - "description": "Start date of the data, in YYYY-MM-DD format.", - "optional": True, - "type": "date", - "value": None, - "multiple_items_allowed": {}, - "options": {"fred": []}, - "x-widget_config": {}, - "available_providers": ["fred"], - "show": True, - }, - { - "parameter_name": "end_date", - "label": "End Date", - "description": "End date of the data, in YYYY-MM-DD format.", - "optional": True, - "type": "date", - "value": None, - "multiple_items_allowed": {}, - "options": {"fred": []}, - "x-widget_config": {}, - "available_providers": ["fred"], - "show": True, - }, - ], - False, - ), - ( - "/api/v1/economy/fred_series", - 8, - [ - { - "parameter_name": "provider", - "label": "Provider", - "description": "Source of the data.", - "optional": True, - "type": "text", - "value": None, - "show": False, - "available_providers": ["fred"], - }, - { - "parameter_name": "symbol", - "label": "Symbol", - "description": "Symbol to get data for.", - "optional": False, - "type": "text", - "value": None, - "multiple_items_allowed": {"fred": True}, - "options": {"fred": []}, - "x-widget_config": {}, - "show": True, - }, - { - "parameter_name": "start_date", - "label": "Start Date", - "description": "Start date of the data, in YYYY-MM-DD format.", - "optional": True, - "type": "date", - "value": None, - "multiple_items_allowed": {}, - "options": {"fred": []}, - "x-widget_config": {}, - "show": True, - }, - { - "parameter_name": "end_date", - "label": "End Date", - "description": "End date of the data, in YYYY-MM-DD format.", - "optional": True, - "type": "date", - "value": None, - "multiple_items_allowed": {}, - "options": {"fred": []}, - "x-widget_config": {}, - "show": True, - }, - { - "parameter_name": "limit", - "label": "Limit", - "description": "The number of data entries to return.", - "optional": True, - "type": "number", - "value": 100000, - "multiple_items_allowed": {}, - "options": {"fred": []}, - "x-widget_config": {}, - "show": True, - }, - { - "parameter_name": "frequency", - "label": "Frequency", - "description": "Frequency aggregation to convert high frequency data to lower frequency.\n \n None = No change\n \n a = Annual\n \n q = Quarterly\n \n m = Monthly\n \n w = Weekly\n \n d = Daily\n \n wef = Weekly, Ending Friday\n \n weth = Weekly, Ending Thursday\n \n wew = Weekly, Ending Wednesday\n \n wetu = Weekly, Ending Tuesday\n \n wem = Weekly, Ending Monday\n \n wesu = Weekly, Ending Sunday\n \n wesa = Weekly, Ending Saturday\n \n bwew = Biweekly, Ending Wednesday\n \n bwem = Biweekly, Ending Monday", - "optional": True, - "type": "text", - "value": None, - "multiple_items_allowed": {}, - "options": { - "fred": [ - {"label": "a", "value": "a"}, - {"label": "q", "value": "q"}, - {"label": "m", "value": "m"}, - {"label": "w", "value": "w"}, - {"label": "d", "value": "d"}, - {"label": "wef", "value": "wef"}, - {"label": "weth", "value": "weth"}, - {"label": "wew", "value": "wew"}, - {"label": "wetu", "value": "wetu"}, - {"label": "wem", "value": "wem"}, - {"label": "wesu", "value": "wesu"}, - {"label": "wesa", "value": "wesa"}, - {"label": "bwew", "value": "bwew"}, - {"label": "bwem", "value": "bwem"}, - ] - }, - "x-widget_config": {}, - "available_providers": ["fred"], - "show": True, - }, - { - "parameter_name": "aggregation_method", - "label": "Aggregation Method", - "description": "A key that indicates the aggregation method used for frequency aggregation.\n" - + " This parameter has no affect if the frequency parameter is not set.\n" - + " \n avg = Average\n \n sum = Sum\n \n eop = End of Period", - "optional": True, - "type": "text", - "value": "eop", - "multiple_items_allowed": {}, - "options": { - "fred": [ - {"label": "avg", "value": "avg"}, - {"label": "sum", "value": "sum"}, - {"label": "eop", "value": "eop"}, - ] - }, - "x-widget_config": {}, - "available_providers": ["fred"], - "show": True, - }, - { - "parameter_name": "transform", - "label": "Transform", - "description": "Transformation type\n \n None = No transformation\n \n chg = Change\n \n ch1 = Change from Year Ago\n \n pch = Percent Change\n \n pc1 = Percent Change from Year Ago\n \n pca = Compounded Annual Rate of Change\n \n cch = Continuously Compounded Rate of Change\n \n cca = Continuously Compounded Annual Rate of Change\n \n log = Natural Log", - "optional": True, - "type": "text", - "value": None, - "multiple_items_allowed": {}, - "options": { - "fred": [ - {"label": "chg", "value": "chg"}, - {"label": "ch1", "value": "ch1"}, - {"label": "pch", "value": "pch"}, - {"label": "pc1", "value": "pc1"}, - {"label": "pca", "value": "pca"}, - {"label": "cch", "value": "cch"}, - {"label": "cca", "value": "cca"}, - {"label": "log", "value": "log"}, - ] - }, - "x-widget_config": {}, - "available_providers": ["fred"], - "show": True, - }, - ], - True, - ), - ( - "/api/v1/regulators/sec/schema_files", - 5, - [ - { - "parameter_name": "provider", - "label": "Provider", - "description": "Source of the data.", - "optional": True, - "type": "text", - "value": None, - "show": False, - "available_providers": ["sec"], - }, - { - "parameter_name": "taxonomy", - "label": "Taxonomy", - "description": "Taxonomy family to explore. Omit to list all available taxonomies and their descriptions.", - "optional": True, - "type": "text", - "value": None, - "multiple_items_allowed": {}, - "options": { - "sec": [ - {"label": "us-gaap", "value": "us-gaap"}, - {"label": "srt", "value": "srt"}, - {"label": "dei", "value": "dei"}, - {"label": "ecd", "value": "ecd"}, - {"label": "cyd", "value": "cyd"}, - {"label": "ffd", "value": "ffd"}, - {"label": "ifrs", "value": "ifrs"}, - {"label": "hmrc-dpl", "value": "hmrc-dpl"}, - {"label": "rxp", "value": "rxp"}, - {"label": "spac", "value": "spac"}, - {"label": "cef", "value": "cef"}, - {"label": "oef", "value": "oef"}, - {"label": "vip", "value": "vip"}, - {"label": "fnd", "value": "fnd"}, - {"label": "sro", "value": "sro"}, - {"label": "sbs", "value": "sbs"}, - {"label": "rocr", "value": "rocr"}, - {"label": "country", "value": "country"}, - {"label": "currency", "value": "currency"}, - {"label": "exch", "value": "exch"}, - {"label": "naics", "value": "naics"}, - {"label": "sic", "value": "sic"}, - {"label": "stpr", "value": "stpr"}, - {"label": "snj", "value": "snj"}, - ] - }, - "x-widget_config": {}, - "available_providers": ["sec"], - "show": True, - }, - { - "parameter_name": "year", - "label": "Year", - "description": "Taxonomy year (2009-2026 for us-gaap, varies by taxonomy). Defaults to the most recent year when omitted.", - "optional": True, - "type": "text", - "value": None, - "multiple_items_allowed": {}, - "options": {"sec": []}, - "x-widget_config": {}, - "available_providers": ["sec"], - "show": True, - }, - { - "parameter_name": "component", - "label": "Component", - "description": "Presentation component to retrieve. Values are taxonomy-specific. Omit to return all components for the taxonomy.", - "optional": True, - "type": "text", - "value": None, - "multiple_items_allowed": {}, - "options": {"sec": []}, - "x-widget_config": {}, - "available_providers": ["sec"], - "show": True, - }, - { - "parameter_name": "category", - "label": "Category", - "description": "Filter taxonomies by SEC filer category.", - "optional": True, - "type": "text", - "value": None, - "multiple_items_allowed": {}, - "options": { - "sec": [ - { - "label": "operating_company", - "value": "operating_company", - }, - { - "label": "investment_company", - "value": "investment_company", - }, - { - "label": "self_regulatory_org", - "value": "self_regulatory_org", - }, - {"label": "sbs_repository", "value": "sbs_repository"}, - {"label": "nrsro", "value": "nrsro"}, - {"label": "common_reference", "value": "common_reference"}, - ] - }, - "x-widget_config": {}, - "available_providers": ["sec"], - "show": True, - }, - ], - False, - ), - ], -) -def test_get_query_schema_for_widget( - mock_openapi_json, path, params_number, query_schema, expected_has_chart -): - route_params, has_chart = get_query_schema_for_widget(mock_openapi_json, path) - assert len(route_params) == params_number - assert route_params == query_schema - assert has_chart == expected_has_chart - - -@pytest.mark.parametrize( - "openapi_operation_id", - [ - "economy_survey_sloos", - "economy_survey_university_of_michigan", - "economy_balance_of_payments", - ], -) -def test_get_data_schema_for_widget(mock_openapi_json, openapi_operation_id): - schema = get_data_schema_for_widget(mock_openapi_json, openapi_operation_id) - assert schema is not None - - -@pytest.mark.parametrize( - "openapi_operation_id", - [ - "form_submit_form_submit_post", - ], -) -def test_post_query_schema_for_widget(mock_openapi_json, openapi_operation_id): - """Test post_query_schema_for_widget function.""" - schema = post_query_schema_for_widget(mock_openapi_json, openapi_operation_id) - assert schema - - -@pytest.mark.parametrize( - "openapi_operation_id", - [ - "economy_survey_sloos", - "economy_survey_university_of_michigan", - "economy_balance_of_payments", - ], -) -def test_data_schema_to_columns_defs(mock_openapi_json, openapi_operation_id): - """Test data_schema_to_columns_defs function.""" - column_defs = data_schema_to_columns_defs( - mock_openapi_json, openapi_operation_id, provider="fred" - ) - assert len(column_defs) > 1 # There should be at least two columns - - -class TestExtractProviderDescription: - """Tests for _extract_provider_description function.""" - - def test_simple_description_no_provider(self): - """Test extraction from simple description without provider markers.""" - desc = "This is a simple description." - result = _extract_provider_description(desc, "fred") - assert result == "This is a simple description." - - def test_description_with_multiple_items_suffix(self): - """Test that 'Multiple comma separated items allowed' is stripped.""" - desc = "Description text. Multiple comma separated items allowed." - result = _extract_provider_description(desc, "fred") - assert result == "Description text." - - def test_provider_specific_description_extraction(self): - """Test extraction of provider-specific description.""" - desc = "Fred specific description (provider: fred);\n Other description (provider: other)" - result = _extract_provider_description(desc, "fred") - assert result == "Fred specific description" - - def test_embedded_semicolon_preserved(self): - """Test that semicolons in description text are preserved.""" - desc = "A semicolon delimited list of tags; Example: 'japan;imports' (provider: fred)" - result = _extract_provider_description(desc, "fred") - assert "Example: 'japan;imports'" in result - - def test_multiple_providers_in_marker(self): - """Test handling of multiple providers in one marker.""" - desc = "Shared description (provider: fred, yfinance)" - result = _extract_provider_description(desc, "fred") - assert result == "Shared description" - - def test_provider_at_end_of_marker(self): - """Test provider listed last in comma-separated list.""" - desc = "Description text (provider: other, fred)" - result = _extract_provider_description(desc, "fred") - assert result == "Description text" - - def test_empty_description(self): - """Test handling of empty description.""" - result = _extract_provider_description("", "fred") - assert result == "" - - def test_fallback_to_general_description(self): - """Test fallback when provider not found in markers.""" - desc = "General description (provider: other_provider)" - result = _extract_provider_description(desc, "fred") - assert result == "General description" - - -class TestProcessParameterWithSingleProvider: - """Tests for process_parameter function with single_provider parameter.""" - - def test_provider_specific_default_extracted(self): - """Test that provider-specific default is extracted when single_provider is set.""" - param = { - "name": "country", - "description": "Country for fred (provider: fred); Country for other (provider: other)", - "schema": { - "fred": {"default": "united_states"}, - "other": {"default": "germany"}, - }, - } - result = process_parameter(param, ["fred", "other"], single_provider="fred") - assert result["value"] == "united_states" - assert ( - "fred" in result["description"].lower() - or "country" in result["description"].lower() - ) - - def test_global_default_used_when_no_provider_specific(self): - """Test that global default is used when provider has no specific default.""" - param = { - "name": "limit", - "description": "Limit results", - "default": 100, - "schema": {}, - } - result = process_parameter(param, ["fred", "other"], single_provider="fred") - assert result["value"] == 100 - - def test_global_default_skipped_when_other_providers_have_defaults(self): - """Test that global default is skipped when other providers have specific defaults.""" - param = { - "name": "topic", - "description": "Topic param", - "default": "general_default", - "schema": { - "other_provider": {"default": "specific_default"}, - }, - } - result = process_parameter( - param, ["fred", "other_provider"], single_provider="fred" - ) - # fred should not inherit global default because other_provider has a specific one - assert result["value"] is None - - def test_provider_specific_description_extracted(self): - """Test that provider-specific description is extracted.""" - param = { - "name": "indicator", - "description": "Fred indicator description (provider: fred); Other indicator (provider: other)", - "schema": {}, - } - result = process_parameter(param, ["fred", "other"], single_provider="fred") - assert "Fred indicator" in result["description"] - assert "Other" not in result["description"] - - def test_no_single_provider_uses_general_description(self): - """Test that without single_provider, general description is used.""" - param = { - "name": "symbol", - "description": "General symbol description (provider: fred); Other desc (provider: other)", - "schema": {}, - } - result = process_parameter(param, ["fred", "other"], single_provider=None) - assert result["description"] == "General symbol description" - - -class TestGetQuerySchemaWithSingleProvider: - """Tests for get_query_schema_for_widget with single_provider parameter.""" - - def test_single_provider_passed_to_process_parameter(self, mock_openapi_json): - """Test that single_provider is passed through to process_parameter.""" - # Use a route that has provider-specific params - route = "/api/v1/economy/cpi" - query_schema, _ = get_query_schema_for_widget( - mock_openapi_json, route, single_provider="fred" - ) - country_param = next( - (p for p in query_schema if p["parameter_name"] == "country"), None - ) - if country_param: - # Should have the default value for fred provider - assert country_param.get("value") is not None or country_param.get( - "optional" - ) - - -if __name__ == "__main__": - pytest.main() diff --git a/openbb_platform/extensions/platform_api/tests/test_widgets_utils.py b/openbb_platform/extensions/platform_api/tests/test_widgets_utils.py deleted file mode 100644 index f37671065a1..00000000000 --- a/openbb_platform/extensions/platform_api/tests/test_widgets_utils.py +++ /dev/null @@ -1,508 +0,0 @@ -import json -from pathlib import Path - -import pytest -from openbb_platform_api.response_models import OmniWidgetResponseModel -from openbb_platform_api.utils.widgets import build_json, modify_query_schema - -# pylint: disable=redefined-outer-name - - -# Load the mock OpenAPI JSON -@pytest.fixture(scope="module") -def mock_openapi_json(): - mock_openapi_path = Path(__file__).parent / "mock_openapi.json" - with open(mock_openapi_path) as file: - return json.load(file) - - -# Load the mock widgets JSON -@pytest.fixture(scope="module") -def mock_widgets_json(): - mock_widgets_path = Path(__file__).parent / "mock_widgets.json" - with open(mock_widgets_path) as file: - return json.load(file) - - -def test_omni_widget_response_model(): - # Create a sample OmniWidgetResponseModel instance - - test_table = [ - {"symbol": "AAPL", "price": 150.0, "volume": 1000000}, - {"symbol": "GOOGL", "price": 2800.0, "volume": 500000}, - ] - widget = OmniWidgetResponseModel( - content=test_table, - ) - # Assert the content and data_format fields - assert widget.content == test_table - assert widget.data_format == { - "data_type": "object", - "parse_as": "table", - } - assert not hasattr(widget, "parse_as") - - widget = OmniWidgetResponseModel( - content=test_table, - parse_as="text", - ) - # Assert the parse_as field takes precedence - assert widget.content == test_table - assert widget.data_format == { - "data_type": "object", - "parse_as": "text", - } - # Assert that the parse_as field is not sent at the root of the object. - assert not hasattr(widget, "parse_as") - - # Test with JSON string content - widget = OmniWidgetResponseModel( - content=json.dumps(test_table), - ) - - # Assert the content and data_format fields - assert isinstance(widget.content, list) - assert widget.data_format == { - "data_type": "object", - "parse_as": "table", - } - assert not hasattr(widget, "parse_as") - - assert "x-widget_config" in widget.schema_json() - - -@pytest.mark.parametrize( - "query_schema, provider_value, expected_result", - [ - ( - [ - { - "parameter_name": "provider", - "label": "Provider", - "description": "Source of the data.", - "optional": True, - "type": "text", - "available_providers": ["sec"], - "show": True, - }, - { - "parameter_name": "query", - "label": "Query", - "description": "Search query.", - "optional": True, - "value": "", - "options": {"sec": []}, - "multiple_items_allowed": {}, - "available_providers": ["sec"], - "type": "text", - "show": True, - }, - { - "parameter_name": "use_cache", - "label": "Use Cache", - "description": "Whether or not to use cache.", - "optional": True, - "value": True, - "options": {"sec": []}, - "multiple_items_allowed": {}, - "available_providers": ["sec"], - "type": "boolean", - "show": True, - }, - { - "parameter_name": "url", - "label": "Url", - "description": "Enter an optional URL path to fetch the next level.", - "optional": True, - "value": None, - "options": {"sec": []}, - "multiple_items_allowed": {}, - "available_providers": ["sec"], - "show": True, - }, - ], - "sec", - [ - { - "label": "Query", - "description": "Search query.", - "optional": True, - "value": "", - "type": "text", - "show": True, - "paramName": "query", - }, - { - "label": "Use Cache", - "description": "Whether or not to use cache.", - "optional": True, - "value": True, - "type": "boolean", - "show": True, - "paramName": "use_cache", - }, - { - "label": "URL", - "description": "Enter an optional URL path to fetch the next level.", - "optional": True, - "value": None, - "show": True, - "paramName": "url", - }, - {"paramName": "provider", "value": "sec", "show": False}, - ], - ), - ( - [ - { - "parameter_name": "provider", - "label": "Provider", - "description": "Source of the data.", - "optional": True, - "type": "text", - "available_providers": ["fred"], - "show": True, - }, - { - "parameter_name": "symbol", - "label": "Symbol", - "description": "Symbol to get data for.", - "optional": False, - "type": "text", - "multiple_items_allowed": {"fred": True}, - "show": True, - }, - { - "parameter_name": "start_date", - "label": "Start Date", - "description": "Start date of the data, in YYYY-MM-DD format.", - "optional": True, - "value": None, - "options": {"fred": []}, - "multiple_items_allowed": {}, - "available_providers": ["fred"], - "type": "date", - "show": True, - }, - { - "parameter_name": "end_date", - "label": "End Date", - "description": "End date of the data, in YYYY-MM-DD format.", - "optional": True, - "value": None, - "options": {"fred": []}, - "multiple_items_allowed": {}, - "available_providers": ["fred"], - "type": "date", - "show": True, - }, - { - "parameter_name": "limit", - "label": "Limit", - "description": "The number of data entries to return.", - "optional": True, - "value": 100000, - "options": {"fred": []}, - "multiple_items_allowed": {}, - "available_providers": ["fred"], - "type": "number", - "show": True, - }, - { - "parameter_name": "frequency", - "label": "Frequency", - "description": "Frequency aggregation to convert high frequency data to lower frequency." - + "\n " - + "\n None = No change" - + "\n " - + "\n a = Annual" - + "\n " - + "\n q = Quarterly" - + "\n " - + "\n m = Monthly" - + "\n " - + "\n w = Weekly" - + "\n " - + "\n d = Daily" - + "\n " - + "\n wef = Weekly, Ending Friday" - + "\n " - + "\n weth = Weekly, Ending Thursday" - + "\n " - + "\n wew = Weekly, Ending Wednesday" - + "\n " - + "\n wetu = Weekly, Ending Tuesday" - + "\n " - + "\n wem = Weekly, Ending Monday" - + "\n " - + "\n wesu = Weekly, Ending Sunday" - + "\n " - + "\n wesa = Weekly, Ending Saturday" - + "\n " - + "\n bwew = Biweekly, Ending Wednesday" - + "\n " - + "\n bwem = Biweekly, Ending Monday", - "optional": True, - "value": None, - "options": { - "fred": [ - {"label": "a", "value": "a"}, - {"label": "q", "value": "q"}, - {"label": "m", "value": "m"}, - {"label": "w", "value": "w"}, - {"label": "d", "value": "d"}, - {"label": "wef", "value": "wef"}, - {"label": "weth", "value": "weth"}, - {"label": "wew", "value": "wew"}, - {"label": "wetu", "value": "wetu"}, - {"label": "wem", "value": "wem"}, - {"label": "wesu", "value": "wesu"}, - {"label": "wesa", "value": "wesa"}, - {"label": "bwew", "value": "bwew"}, - {"label": "bwem", "value": "bwem"}, - ] - }, - "multiple_items_allowed": {}, - "available_providers": ["fred"], - "show": True, - }, - { - "parameter_name": "aggregation_method", - "label": "Aggregation Method", - "description": "A key that indicates the aggregation method used for frequency aggregation." - + "\n This parameter has no affect if the frequency parameter is not set." - + "\n " - + "\n avg = Average" - + "\n " - + "\n sum = Sum" - + "\n " - + "\n eop = End of Period", - "optional": True, - "value": "eop", - "options": { - "fred": [ - {"label": "avg", "value": "avg"}, - {"label": "sum", "value": "sum"}, - {"label": "eop", "value": "eop"}, - ] - }, - "multiple_items_allowed": {}, - "available_providers": ["fred"], - "show": True, - }, - { - "parameter_name": "transform", - "label": "Transform", - "description": "Transformation type" - + "\n " - + "\n None = No transformation" - + "\n " - + "\n chg = Change" - + "\n " - + "\n ch1 = Change from Year Ago" - + "\n " - + "\n pch = Percent Change" - + "\n " - + "\n pc1 = Percent Change from Year Ago" - + "\n " - + "\n pca = Compounded Annual Rate of Change" - + "\n " - + "\n cch = Continuously Compounded Rate of Change" - + "\n " - + "\n cca = Continuously Compounded Annual Rate of Change" - + "\n " - + "\n log = Natural Log", - "optional": True, - "value": None, - "options": { - "fred": [ - {"label": "chg", "value": "chg"}, - {"label": "ch1", "value": "ch1"}, - {"label": "pch", "value": "pch"}, - {"label": "pc1", "value": "pc1"}, - {"label": "pca", "value": "pca"}, - {"label": "cch", "value": "cch"}, - {"label": "cca", "value": "cca"}, - {"label": "log", "value": "log"}, - ] - }, - "multiple_items_allowed": {}, - "available_providers": ["fred"], - "show": True, - }, - ], - "fred", - [ - { - "label": "Symbol", - "description": "Symbol to get data for. Multiple comma separated items allowed.", - "optional": False, - "type": "text", - "show": True, - "multiSelect": True, - "paramName": "symbol", - "multiple": True, - "style": { - "popupWidth": 400, - }, - }, - { - "label": "Start Date", - "description": "Start date of the data, in YYYY-MM-DD format.", - "optional": True, - "value": None, - "type": "date", - "show": True, - "paramName": "start_date", - }, - { - "label": "End Date", - "description": "End date of the data, in YYYY-MM-DD format.", - "optional": True, - "value": None, - "type": "date", - "show": True, - "paramName": "end_date", - }, - { - "label": "Limit", - "description": "The number of data entries to return.", - "optional": True, - "value": 100000, - "type": "number", - "show": True, - "paramName": "limit", - }, - { - "label": "Frequency", - "description": "Frequency aggregation to convert high frequency data to lower frequency." - + "\n " - + "\n None = No change" - + "\n " - + "\n a = Annual" - + "\n " - + "\n q = Quarterly" - + "\n " - + "\n m = Monthly" - + "\n " - + "\n w = Weekly" - + "\n " - + "\n d = Daily" - + "\n " - + "\n wef = Weekly, Ending Friday" - + "\n " - + "\n weth = Weekly, Ending Thursday" - + "\n " - + "\n wew = Weekly, Ending Wednesday" - + "\n " - + "\n wetu = Weekly, Ending Tuesday" - + "\n " - + "\n wem = Weekly, Ending Monday" - + "\n " - + "\n wesu = Weekly, Ending Sunday" - + "\n " - + "\n wesa = Weekly, Ending Saturday" - + "\n " - + "\n bwew = Biweekly, Ending Wednesday" - + "\n " - + "\n bwem = Biweekly, Ending Monday", - "optional": True, - "value": None, - "show": True, - "options": [ - {"label": "a", "value": "a"}, - {"label": "q", "value": "q"}, - {"label": "m", "value": "m"}, - {"label": "w", "value": "w"}, - {"label": "d", "value": "d"}, - {"label": "wef", "value": "wef"}, - {"label": "weth", "value": "weth"}, - {"label": "wew", "value": "wew"}, - {"label": "wetu", "value": "wetu"}, - {"label": "wem", "value": "wem"}, - {"label": "wesu", "value": "wesu"}, - {"label": "wesa", "value": "wesa"}, - {"label": "bwew", "value": "bwew"}, - {"label": "bwem", "value": "bwem"}, - ], - "type": "text", - "paramName": "frequency", - }, - { - "label": "Aggregation Method", - "description": "A key that indicates the aggregation method used for frequency aggregation." - + "\n This parameter has no affect if the frequency parameter is not set." - + "\n " - + "\n avg = Average" - + "\n " - + "\n sum = Sum" - + "\n " - + "\n eop = End of Period", - "optional": True, - "value": "eop", - "show": True, - "options": [ - {"label": "avg", "value": "avg"}, - {"label": "sum", "value": "sum"}, - {"label": "eop", "value": "eop"}, - ], - "type": "text", - "paramName": "aggregation_method", - }, - { - "label": "Transform", - "description": "Transformation type" - + "\n " - + "\n None = No transformation" - + "\n " - + "\n chg = Change" - + "\n " - + "\n ch1 = Change from Year Ago" - + "\n " - + "\n pch = Percent Change" - + "\n " - + "\n pc1 = Percent Change from Year Ago" - + "\n " - + "\n pca = Compounded Annual Rate of Change" - + "\n " - + "\n cch = Continuously Compounded Rate of Change" - + "\n " - + "\n cca = Continuously Compounded Annual Rate of Change" - + "\n " - + "\n log = Natural Log", - "optional": True, - "value": None, - "show": True, - "options": [ - {"label": "chg", "value": "chg"}, - {"label": "ch1", "value": "ch1"}, - {"label": "pch", "value": "pch"}, - {"label": "pc1", "value": "pc1"}, - {"label": "pca", "value": "pca"}, - {"label": "cch", "value": "cch"}, - {"label": "cca", "value": "cca"}, - {"label": "log", "value": "log"}, - ], - "type": "text", - "paramName": "transform", - }, - {"paramName": "provider", "value": "fred", "show": False}, - ], - ), - ], -) -def test_modify_query_schema(query_schema, provider_value, expected_result): - result = modify_query_schema(query_schema, provider_value) - assert result == expected_result - - -def test_build_json(mock_openapi_json, mock_widgets_json): - result = build_json(openapi=mock_openapi_json, widget_exclude_filter=[]) - assert len(result) == len( - mock_openapi_json["paths"] - ) # +1 for the duplicate path with a chart. -1 for the form widget over 2 paths. - assert result == mock_widgets_json - - -if __name__ == "__main__": - pytest.main() diff --git a/openbb_platform/extensions/platform_api/tests/mock_openapi.json b/openbb_platform/extensions/platform_api/tests/utils/mock_openapi.json similarity index 100% rename from openbb_platform/extensions/platform_api/tests/mock_openapi.json rename to openbb_platform/extensions/platform_api/tests/utils/mock_openapi.json diff --git a/openbb_platform/extensions/platform_api/tests/mock_widgets.json b/openbb_platform/extensions/platform_api/tests/utils/mock_widgets.json similarity index 99% rename from openbb_platform/extensions/platform_api/tests/mock_widgets.json rename to openbb_platform/extensions/platform_api/tests/utils/mock_widgets.json index a4c89fefb84..9b0fcdbf7f7 100644 --- a/openbb_platform/extensions/platform_api/tests/mock_widgets.json +++ b/openbb_platform/extensions/platform_api/tests/utils/mock_widgets.json @@ -145,7 +145,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "symbol", @@ -316,7 +317,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "consumer_sentiment", @@ -479,7 +481,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "activity_index", @@ -758,7 +761,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "topic", @@ -934,7 +938,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "symbol", @@ -1314,7 +1319,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "country", @@ -1593,7 +1599,7 @@ "formatterFn": null, "headerName": "Period", "headerTooltip": "The date representing the beginning of the reporting period.", - "cellDataType": "date" + "cellDataType": "dateString" }, { "field": "balance_percent_of_gdp", @@ -1916,14 +1922,14 @@ "formatterFn": null, "headerName": "Observation Start", "headerTooltip": "The date of the first observation in the series.", - "cellDataType": "date" + "cellDataType": "dateString" }, { "field": "observation_end", "formatterFn": null, "headerName": "Observation End", "headerTooltip": "The date of the last observation in the series.", - "cellDataType": "date" + "cellDataType": "dateString" }, { "field": "frequency", @@ -2240,7 +2246,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" } ], "enableAdvanced": true @@ -2468,7 +2475,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" } ], "enableAdvanced": true @@ -2546,7 +2554,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "level", @@ -2912,7 +2921,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "region", @@ -3118,7 +3128,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "symbol", @@ -3250,7 +3261,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of the data.", - "cellDataType": "date" + "cellDataType": "dateString", + "sort": "desc" }, { "field": "symbol", @@ -3869,7 +3881,8 @@ "formatterFn": null, "headerName": "Date", "headerTooltip": "The date of publication.", - "cellDataType": "date" + "cellDataType": "date", + "sort": "desc" }, { "field": "title", diff --git a/openbb_platform/extensions/platform_api/tests/utils/test_merge_widgets.py b/openbb_platform/extensions/platform_api/tests/utils/test_merge_widgets.py new file mode 100644 index 00000000000..e911c1ad35a --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/utils/test_merge_widgets.py @@ -0,0 +1,290 @@ +"""Test merge_widgets module.""" + +import copy + +import pytest +from fastapi import APIRouter, FastAPI + +from openbb_platform_api.utils.merge_widgets import ( + fix_router_widgets, + get_additional_widgets, + get_and_fix_widget_paths, + has_additional_widgets, +) + + +def _build_app(include_extra: bool = False, extra_returns_dict: bool = True) -> FastAPI: + app = FastAPI() + + @app.get("/widgets.json") + async def root_widgets(): + return {"root": {"widgetId": "root", "endpoint": "/root"}} + + @app.get("/health") + async def health(): + return {"status": "ok"} + + if include_extra: + + @app.get("/module/widgets.json") + async def module_widgets(): + if extra_returns_dict: + return { + "module": { + "widgetId": "module", + "endpoint": "/module/data", + "params": [], + } + } + return "not-a-dict" + + return app + + +def _build_included_app() -> FastAPI: + """Attach a widgets.json route via ``include_router``. + + FastAPI 0.137+ wraps included routes in ``_IncludedRouter`` instead + of flattening them into ``app.routes``, so the merge must walk the + routes through ``iter_api_routes`` to find them. + """ + app = FastAPI() + + @app.get("/widgets.json") + async def root_widgets(): + return {"root": {"widgetId": "root", "endpoint": "/root"}} + + sub = APIRouter() + + @sub.get("/widgets.json") + async def sub_widgets(): + return { + "module": { + "widgetId": "module", + "endpoint": "/sub/data", + "params": [], + } + } + + app.include_router(sub, prefix="/sub") + return app + + +def test_has_additional_widgets_false_without_extra_routes(): + app = _build_app(include_extra=False) + assert not has_additional_widgets(app) + + +def test_has_additional_widgets_true_with_extra_routes(): + app = _build_app(include_extra=True) + assert has_additional_widgets(app) + + +def test_has_additional_widgets_true_with_included_router(): + """A widgets.json route attached via include_router is detected (0.137+).""" + app = _build_included_app() + assert has_additional_widgets(app) + + +@pytest.mark.asyncio +async def test_get_additional_widgets_returns_empty_when_none(): + app = _build_app(include_extra=False) + assert await get_additional_widgets(app) == {} + + +@pytest.mark.asyncio +async def test_get_additional_widgets_skips_non_dict_responses(): + app = _build_app(include_extra=True, extra_returns_dict=False) + assert await get_additional_widgets(app) == {} + + +@pytest.mark.asyncio +async def test_get_additional_widgets_collects_valid_routes(): + app = _build_app(include_extra=True) + widgets = await get_additional_widgets(app) + assert widgets == { + "/module/": { + "module": { + "widgetId": "module", + "endpoint": "/module/data", + "params": [], + } + } + } + + +@pytest.mark.asyncio +async def test_get_additional_widgets_collects_from_included_router(): + """A widgets.json route added via include_router is collected (0.137+ guard).""" + app = _build_included_app() + widgets = await get_additional_widgets(app) + assert widgets == { + "/sub/": { + "module": { + "widgetId": "module", + "endpoint": "/sub/data", + "params": [], + } + } + } + + +@pytest.mark.asyncio +async def test_get_additional_widgets_skips_routes_with_no_endpoint(): + """A route entry whose ``endpoint`` is missing/None gets skipped.""" + from fastapi.routing import APIRoute + + app = FastAPI() + + @app.get("/module/widgets.json") + async def module_widgets(): + return {"module": {"widgetId": "module"}} + + fake_route = APIRoute( + path="/garbage/widgets.json", + endpoint=lambda: None, + methods=["GET"], + ) + fake_route.endpoint = None # ty: ignore[invalid-assignment] + app.routes.append(fake_route) + + result = await get_additional_widgets(app) + assert "/module/" in result + assert "/garbage/" not in result + + +def test_fix_router_widgets_updates_nested_paths_without_mutating_source(): + original = { + "widgetA": { + "widgetId": "widgetA", + "endpoint": "/data", + "wsEndpoint": "/stream", + "imgUrl": "/images/icon.png", + "params": [ + { + "name": "param1", + "endpoint": "/param", + "optionsEndpoint": "/param/options", + }, + {"name": "param2", "endpoint": "http://external/api"}, + ], + }, + "widgetB/widgets.json": {"endpoint": "/should/skip"}, + "not_a_dict": "skip_me", + } + snapshot = copy.deepcopy(original) + updated = fix_router_widgets("/api/", original) + + assert snapshot == original + assert list(updated.keys()) == ["widgetA"] + widget = updated["widgetA"] + assert widget["endpoint"] == "/api/data" + assert widget["wsEndpoint"] == "/api/stream" + assert widget["imgUrl"] == "/api/images/icon.png" + assert widget["params"][0]["endpoint"] == "/api/param" + assert widget["params"][0]["optionsEndpoint"] == "/api/param/options" + assert widget["params"][1]["endpoint"] == "http://external/api" + + +def test_fix_router_widgets_leaves_prefixed_and_missing_fields_untouched(): + """Already-prefixed or absent endpoint/wsEndpoint/imgUrl are left as-is.""" + widgets = { + "widgetA": { + "widgetId": "widgetA", + "endpoint": "/api/data", + } + } + updated = fix_router_widgets("/api/", widgets) + assert updated["widgetA"]["endpoint"] == "/api/data" + assert "wsEndpoint" not in updated["widgetA"] + assert "imgUrl" not in updated["widgetA"] + assert updated["widgetA"]["params"] == [] + + +def test_fix_router_widgets_skips_external_and_missing_optional_fields(): + """External (scheme) wsEndpoint/imgUrl and missing param fields are skipped.""" + widgets = { + "widgetA": { + "widgetId": "widgetA", + "endpoint": "/data", + "wsEndpoint": "wss://external/stream", + "imgUrl": "https://external/icon.png", + "params": [ + { + "name": "param1", + "endpoint": "https://x/y", + "optionsEndpoint": "ws://z", + } + ], + } + } + updated = fix_router_widgets("/api/", widgets) + assert updated["widgetA"]["endpoint"] == "/api/data" + assert updated["widgetA"]["wsEndpoint"] == "wss://external/stream" + assert updated["widgetA"]["imgUrl"] == "https://external/icon.png" + assert updated["widgetA"]["params"][0]["endpoint"] == "https://x/y" + assert updated["widgetA"]["params"][0]["optionsEndpoint"] == "ws://z" + + +@pytest.mark.asyncio +async def test_get_and_fix_widget_paths_integrates_collection_and_fixing(): + app = FastAPI() + + @app.get("/widgets.json") + async def root_widgets(): + return {} + + @app.get("/api/widgets.json") + async def api_widgets(): + return { + "first": { + "widgetId": "first", + "endpoint": "/data", + "wsEndpoint": "/stream", + "imgUrl": "/assets/icon.png", + "params": [{"endpoint": "/param"}], + }, + "ignore": "non-dict", + } + + result = await get_and_fix_widget_paths(app) + assert "/api/" in result + fixed = result["/api/"]["first"] + assert fixed["endpoint"] == "/api/data" + assert fixed["wsEndpoint"] == "/api/stream" + assert fixed["imgUrl"] == "/api/assets/icon.png" + assert fixed["params"][0]["endpoint"] == "/api/param" + + +@pytest.mark.asyncio +async def test_get_and_fix_widget_paths_returns_empty_when_no_additional_widgets(): + """When no router-attached widget routes are present, the wrapped + path-fix pass short-circuits to ``{}``. + """ + app = FastAPI() + + @app.get("/widgets.json") + async def root_widgets(): + return {} + + result = await get_and_fix_widget_paths(app) + assert result == {} + + +@pytest.mark.asyncio +async def test_get_and_fix_widget_paths_keeps_paths_when_no_widgets_are_fixable(): + """When every widget on a path is skipped, the path is left unchanged.""" + app = FastAPI() + + @app.get("/widgets.json") + async def root_widgets(): + return {} + + @app.get("/api/widgets.json") + async def api_widgets(): + return {"skip/widgets.json": {"endpoint": "/x"}, "bad": "non-dict"} + + result = await get_and_fix_widget_paths(app) + assert result == { + "/api/": {"skip/widgets.json": {"endpoint": "/x"}, "bad": "non-dict"} + } diff --git a/openbb_platform/extensions/platform_api/tests/utils/test_openapi.py b/openbb_platform/extensions/platform_api/tests/utils/test_openapi.py new file mode 100644 index 00000000000..4021de95df4 --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/utils/test_openapi.py @@ -0,0 +1,2946 @@ +"""Test OpenAPI Utils.""" + +# ruff: noqa: E501 + +import json +from pathlib import Path + +import pytest + +from openbb_platform_api.utils.openapi import ( + _extract_provider_description, + data_schema_to_columns_defs, + get_data_schema_for_widget, + get_query_schema_for_widget, + post_query_schema_for_widget, + process_parameter, +) + + +# Load the mock OpenAPI JSON +@pytest.fixture(scope="module") +def mock_openapi_json(): + mock_openapi_path = Path(__file__).parent / "mock_openapi.json" + with open(mock_openapi_path, encoding="utf-8") as file: + return json.load(file) + + +@pytest.mark.parametrize( + "path, params_number, query_schema, expected_has_chart", + [ + ( + "/api/v1/economy/balance_of_payments", + 4, + [ + { + "parameter_name": "provider", + "label": "Provider", + "description": "Source of the data.", + "optional": True, + "type": "text", + "value": None, + "show": False, + "available_providers": ["fred"], + }, + { + "parameter_name": "country", + "label": "Country", + "description": "The country to get data. Enter as a 3-letter ISO country code, default is USA.", + "optional": True, + "type": "text", + "value": "united_states", + "multiple_items_allowed": {}, + "options": { + "fred": [ + {"label": "argentina", "value": "argentina"}, + {"label": "australia", "value": "australia"}, + {"label": "austria", "value": "austria"}, + {"label": "belgium", "value": "belgium"}, + {"label": "brazil", "value": "brazil"}, + {"label": "canada", "value": "canada"}, + {"label": "chile", "value": "chile"}, + {"label": "china", "value": "china"}, + {"label": "colombia", "value": "colombia"}, + {"label": "costa_rica", "value": "costa_rica"}, + {"label": "czechia", "value": "czechia"}, + {"label": "denmark", "value": "denmark"}, + {"label": "estonia", "value": "estonia"}, + {"label": "finland", "value": "finland"}, + {"label": "france", "value": "france"}, + {"label": "germany", "value": "germany"}, + {"label": "greece", "value": "greece"}, + {"label": "hungary", "value": "hungary"}, + {"label": "iceland", "value": "iceland"}, + {"label": "india", "value": "india"}, + {"label": "indonesia", "value": "indonesia"}, + {"label": "ireland", "value": "ireland"}, + {"label": "israel", "value": "israel"}, + {"label": "italy", "value": "italy"}, + {"label": "japan", "value": "japan"}, + {"label": "korea", "value": "korea"}, + {"label": "latvia", "value": "latvia"}, + {"label": "lithuania", "value": "lithuania"}, + {"label": "luxembourg", "value": "luxembourg"}, + {"label": "mexico", "value": "mexico"}, + {"label": "netherlands", "value": "netherlands"}, + {"label": "new_zealand", "value": "new_zealand"}, + {"label": "norway", "value": "norway"}, + {"label": "poland", "value": "poland"}, + {"label": "portugal", "value": "portugal"}, + {"label": "russia", "value": "russia"}, + {"label": "saudi_arabia", "value": "saudi_arabia"}, + {"label": "slovak_republic", "value": "slovak_republic"}, + {"label": "slovenia", "value": "slovenia"}, + {"label": "south_africa", "value": "south_africa"}, + {"label": "spain", "value": "spain"}, + {"label": "sweden", "value": "sweden"}, + {"label": "switzerland", "value": "switzerland"}, + {"label": "turkey", "value": "turkey"}, + {"label": "united_kingdom", "value": "united_kingdom"}, + {"label": "united_states", "value": "united_states"}, + {"label": "g7", "value": "g7"}, + {"label": "g20", "value": "g20"}, + ] + }, + "x-widget_config": {}, + "available_providers": ["fred"], + "show": True, + }, + { + "parameter_name": "start_date", + "label": "Start Date", + "description": "Start date of the data, in YYYY-MM-DD format.", + "optional": True, + "type": "date", + "value": None, + "multiple_items_allowed": {}, + "options": {"fred": []}, + "x-widget_config": {}, + "available_providers": ["fred"], + "show": True, + }, + { + "parameter_name": "end_date", + "label": "End Date", + "description": "End date of the data, in YYYY-MM-DD format.", + "optional": True, + "type": "date", + "value": None, + "multiple_items_allowed": {}, + "options": {"fred": []}, + "x-widget_config": {}, + "available_providers": ["fred"], + "show": True, + }, + ], + False, + ), + ( + "/api/v1/economy/fred_series", + 8, + [ + { + "parameter_name": "provider", + "label": "Provider", + "description": "Source of the data.", + "optional": True, + "type": "text", + "value": None, + "show": False, + "available_providers": ["fred"], + }, + { + "parameter_name": "symbol", + "label": "Symbol", + "description": "Symbol to get data for.", + "optional": False, + "type": "text", + "value": None, + "multiple_items_allowed": {"fred": True}, + "options": {"fred": []}, + "x-widget_config": {}, + "show": True, + }, + { + "parameter_name": "start_date", + "label": "Start Date", + "description": "Start date of the data, in YYYY-MM-DD format.", + "optional": True, + "type": "date", + "value": None, + "multiple_items_allowed": {}, + "options": {"fred": []}, + "x-widget_config": {}, + "show": True, + }, + { + "parameter_name": "end_date", + "label": "End Date", + "description": "End date of the data, in YYYY-MM-DD format.", + "optional": True, + "type": "date", + "value": None, + "multiple_items_allowed": {}, + "options": {"fred": []}, + "x-widget_config": {}, + "show": True, + }, + { + "parameter_name": "limit", + "label": "Limit", + "description": "The number of data entries to return.", + "optional": True, + "type": "number", + "value": 100000, + "multiple_items_allowed": {}, + "options": {"fred": []}, + "x-widget_config": {}, + "show": True, + }, + { + "parameter_name": "frequency", + "label": "Frequency", + "description": "Frequency aggregation to convert high frequency data to lower frequency.\n \n None = No change\n \n a = Annual\n \n q = Quarterly\n \n m = Monthly\n \n w = Weekly\n \n d = Daily\n \n wef = Weekly, Ending Friday\n \n weth = Weekly, Ending Thursday\n \n wew = Weekly, Ending Wednesday\n \n wetu = Weekly, Ending Tuesday\n \n wem = Weekly, Ending Monday\n \n wesu = Weekly, Ending Sunday\n \n wesa = Weekly, Ending Saturday\n \n bwew = Biweekly, Ending Wednesday\n \n bwem = Biweekly, Ending Monday", + "optional": True, + "type": "text", + "value": None, + "multiple_items_allowed": {}, + "options": { + "fred": [ + {"label": "a", "value": "a"}, + {"label": "q", "value": "q"}, + {"label": "m", "value": "m"}, + {"label": "w", "value": "w"}, + {"label": "d", "value": "d"}, + {"label": "wef", "value": "wef"}, + {"label": "weth", "value": "weth"}, + {"label": "wew", "value": "wew"}, + {"label": "wetu", "value": "wetu"}, + {"label": "wem", "value": "wem"}, + {"label": "wesu", "value": "wesu"}, + {"label": "wesa", "value": "wesa"}, + {"label": "bwew", "value": "bwew"}, + {"label": "bwem", "value": "bwem"}, + ] + }, + "x-widget_config": {}, + "available_providers": ["fred"], + "show": True, + }, + { + "parameter_name": "aggregation_method", + "label": "Aggregation Method", + "description": "A key that indicates the aggregation method used for frequency aggregation.\n" + + " This parameter has no affect if the frequency parameter is not set.\n" + + " \n avg = Average\n \n sum = Sum\n \n eop = End of Period", + "optional": True, + "type": "text", + "value": "eop", + "multiple_items_allowed": {}, + "options": { + "fred": [ + {"label": "avg", "value": "avg"}, + {"label": "sum", "value": "sum"}, + {"label": "eop", "value": "eop"}, + ] + }, + "x-widget_config": {}, + "available_providers": ["fred"], + "show": True, + }, + { + "parameter_name": "transform", + "label": "Transform", + "description": "Transformation type\n \n None = No transformation\n \n chg = Change\n \n ch1 = Change from Year Ago\n \n pch = Percent Change\n \n pc1 = Percent Change from Year Ago\n \n pca = Compounded Annual Rate of Change\n \n cch = Continuously Compounded Rate of Change\n \n cca = Continuously Compounded Annual Rate of Change\n \n log = Natural Log", + "optional": True, + "type": "text", + "value": None, + "multiple_items_allowed": {}, + "options": { + "fred": [ + {"label": "chg", "value": "chg"}, + {"label": "ch1", "value": "ch1"}, + {"label": "pch", "value": "pch"}, + {"label": "pc1", "value": "pc1"}, + {"label": "pca", "value": "pca"}, + {"label": "cch", "value": "cch"}, + {"label": "cca", "value": "cca"}, + {"label": "log", "value": "log"}, + ] + }, + "x-widget_config": {}, + "available_providers": ["fred"], + "show": True, + }, + ], + True, + ), + ( + "/api/v1/regulators/sec/schema_files", + 5, + [ + { + "parameter_name": "provider", + "label": "Provider", + "description": "Source of the data.", + "optional": True, + "type": "text", + "value": None, + "show": False, + "available_providers": ["sec"], + }, + { + "parameter_name": "taxonomy", + "label": "Taxonomy", + "description": "Taxonomy family to explore. Omit to list all available taxonomies and their descriptions.", + "optional": True, + "type": "text", + "value": None, + "multiple_items_allowed": {}, + "options": { + "sec": [ + {"label": "us-gaap", "value": "us-gaap"}, + {"label": "srt", "value": "srt"}, + {"label": "dei", "value": "dei"}, + {"label": "ecd", "value": "ecd"}, + {"label": "cyd", "value": "cyd"}, + {"label": "ffd", "value": "ffd"}, + {"label": "ifrs", "value": "ifrs"}, + {"label": "hmrc-dpl", "value": "hmrc-dpl"}, + {"label": "rxp", "value": "rxp"}, + {"label": "spac", "value": "spac"}, + {"label": "cef", "value": "cef"}, + {"label": "oef", "value": "oef"}, + {"label": "vip", "value": "vip"}, + {"label": "fnd", "value": "fnd"}, + {"label": "sro", "value": "sro"}, + {"label": "sbs", "value": "sbs"}, + {"label": "rocr", "value": "rocr"}, + {"label": "country", "value": "country"}, + {"label": "currency", "value": "currency"}, + {"label": "exch", "value": "exch"}, + {"label": "naics", "value": "naics"}, + {"label": "sic", "value": "sic"}, + {"label": "stpr", "value": "stpr"}, + {"label": "snj", "value": "snj"}, + ] + }, + "x-widget_config": {}, + "available_providers": ["sec"], + "show": True, + }, + { + "parameter_name": "year", + "label": "Year", + "description": "Taxonomy year (2009-2026 for us-gaap, varies by taxonomy). Defaults to the most recent year when omitted.", + "optional": True, + "type": "text", + "value": None, + "multiple_items_allowed": {}, + "options": {"sec": []}, + "x-widget_config": {}, + "available_providers": ["sec"], + "show": True, + }, + { + "parameter_name": "component", + "label": "Component", + "description": "Presentation component to retrieve. Values are taxonomy-specific. Omit to return all components for the taxonomy.", + "optional": True, + "type": "text", + "value": None, + "multiple_items_allowed": {}, + "options": {"sec": []}, + "x-widget_config": {}, + "available_providers": ["sec"], + "show": True, + }, + { + "parameter_name": "category", + "label": "Category", + "description": "Filter taxonomies by SEC filer category.", + "optional": True, + "type": "text", + "value": None, + "multiple_items_allowed": {}, + "options": { + "sec": [ + { + "label": "operating_company", + "value": "operating_company", + }, + { + "label": "investment_company", + "value": "investment_company", + }, + { + "label": "self_regulatory_org", + "value": "self_regulatory_org", + }, + {"label": "sbs_repository", "value": "sbs_repository"}, + {"label": "nrsro", "value": "nrsro"}, + {"label": "common_reference", "value": "common_reference"}, + ] + }, + "x-widget_config": {}, + "available_providers": ["sec"], + "show": True, + }, + ], + False, + ), + ], +) +def test_get_query_schema_for_widget( + mock_openapi_json, path, params_number, query_schema, expected_has_chart +): + route_params, has_chart = get_query_schema_for_widget(mock_openapi_json, path) + assert len(route_params) == params_number + assert route_params == query_schema + assert has_chart == expected_has_chart + + +# --------------------------------------------------------------------------- +# data_schema_to_columns_defs branches (focused unit-style coverage) +# --------------------------------------------------------------------------- + + +def _columns_openapi_for_widget( + properties: dict, *, title="MyData", schema_extras: dict | None = None +) -> dict: + """Build a minimal openapi document so ``data_schema_to_columns_defs`` + has something to walk. + + Layout: ``/some/route`` → response references an ``OBBject_`` + schema whose ``properties.results`` is an array of items pointing + at the data schema (named ``<title>``). The data schema's + ``properties`` are what column-defs get built from. + """ + data_schema = {"title": title, "properties": properties} + if schema_extras: + data_schema.update(schema_extras) + obbject_name = f"OBBject_{title}_" + return { + "components": { + "schemas": { + title: data_schema, + obbject_name: { + "title": obbject_name, + "properties": { + "results": { + "anyOf": [ + { + "items": {"$ref": f"#/components/schemas/{title}"}, + "type": "array", + }, + {"type": "null"}, + ] + } + }, + }, + } + }, + "paths": { + "/some/route": { + "get": { + "operationId": "some_op", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": f"#/components/schemas/{obbject_name}" + } + } + } + } + }, + } + } + }, + } + + +def test_data_schema_to_columns_handles_anyof_branches(): + """``anyOf`` mixing types selects the right ``cellDataType``: + number wins over text/date-string; date-string wins over text; + text is fallback. ``format: "date"`` (no time component) maps to + ``cellDataType: "dateString"`` so the value renders as YYYY-MM-DD + verbatim instead of being parsed as a timestamp. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "amount": {"anyOf": [{"type": "number"}, {"type": "null"}]}, + "asof": {"anyOf": [{"type": "string", "format": "date"}, {"type": "null"}]}, + "ts": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}]}, + "label": {"anyOf": [{"type": "string"}, {"type": "null"}]}, + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + by_field = {c["field"]: c for c in cols} + assert by_field["amount"]["cellDataType"] == "number" + assert by_field["asof"]["cellDataType"] == "dateString" + assert by_field["ts"]["cellDataType"] == "date" + assert by_field["label"]["cellDataType"] == "text" + + +def test_data_schema_to_columns_handles_items_array_subschema(): + """``items``-shaped property routes through the same anyOf-style + detection. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "scores": { + "type": "array", + "items": {"anyOf": [{"type": "number"}, {"type": "null"}]}, + } + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0]["cellDataType"] == "number" + + +def test_data_schema_to_columns_handles_items_with_string_date(): + """``items`` with date-formatted string → cellDataType=dateString + (verbatim YYYY-MM-DD render, no implicit timestamp parsing). + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "dates": { + "type": "array", + "items": { + "anyOf": [ + {"type": "string", "format": "date"}, + {"type": "null"}, + ] + }, + } + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0]["cellDataType"] == "dateString" + + +def test_data_schema_to_columns_date_column_sorts_descending_by_default(): + """A column literally named ``date`` is the temporal axis of the + dataset; pre-sort descending so the most recent observation lands + at the top. Authors can override via + ``widget_config.data.table.columnsDefs[].sort``. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "date": {"type": "string", "format": "date", "title": "Date"}, + "value": {"type": "number"}, + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + by_field = {c["field"]: c for c in cols} + assert by_field["date"]["sort"] == "desc" + # The default doesn't leak onto non-date columns. + assert "sort" not in by_field["value"] + + +def test_data_schema_to_columns_other_date_columns_dont_get_sort(): + """Only the column literally named ``date`` gets the descending + sort default — fields like ``start_date`` / ``as_of_date`` are + parameters or annotations, not the time-series axis. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "as_of_date": {"type": "string", "format": "date"}, + "start_date": {"type": "string", "format": "date"}, + "value": {"type": "number"}, + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + for c in cols: + assert "sort" not in c + + +def test_data_schema_to_columns_date_time_format_uses_date_celltype(): + """``format: "date-time"`` (full timestamp with time-of-day) maps + to ``cellDataType: "date"`` so ag-grid parses it as a Date object + and locale-renders. Distinct from the no-time ``format: "date"`` + case which uses ``"dateString"`` for verbatim YYYY-MM-DD render. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "ts": {"type": "string", "format": "date-time"}, + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0]["cellDataType"] == "date" + + +def test_data_schema_to_columns_handles_items_text_fallback(): + """Items with no recognized type → text fallback.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "tags": { + "type": "array", + "items": {"anyOf": [{"type": "object"}]}, + } + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0]["cellDataType"] == "text" + + +def test_data_schema_to_columns_anyof_text_fallback(): + """AnyOf with no number / date types → text fallback.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "kind": {"anyOf": [{"type": "object"}, {"type": "null"}]}, + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0]["cellDataType"] == "text" + + +def test_data_schema_to_columns_integer_uses_int_formatter(): + """Integer-typed props get ``formatterFn="int"``.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = {"count": {"type": "integer"}} + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0]["cellDataType"] == "number" + assert cols[0]["formatterFn"] == "int" + + +def test_data_schema_to_columns_pins_date_and_symbol_columns(): + """``date`` / ``symbol`` columns get pinned to the left.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "date": {"type": "string", "format": "date"}, + "symbol": {"type": "string"}, + "value": {"type": "number"}, + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + by_field = {c["field"]: c for c in cols} + assert by_field["date"]["pinned"] == "left" + assert by_field["symbol"]["pinned"] == "left" + assert "pinned" not in by_field["value"] + + +def test_data_schema_to_columns_uppercases_acronym_field_names(): + """``cik`` / ``isin`` / etc. get header uppercased + text type.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "cik": {"type": "string"}, + "isin": {"type": "string"}, + "symbol": {"type": "string"}, + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + by_field = {c["field"]: c for c in cols} + assert by_field["cik"]["headerName"] == "CIK" + assert by_field["isin"]["headerName"] == "ISIN" + assert by_field["symbol"]["cellDataType"] == "text" + + +def test_data_schema_to_columns_year_fields_render_as_text_to_skip_comma(): + """Year fields use ``cellDataType: "text"`` — that's the only way + to suppress ag-grid's locale-based thousands separator + (``"2,023"`` → ``"2023"``). ``formatterFn: "none"`` alone doesn't + override the type-driven number formatting. 4-digit years sort + identically as text vs. number, so no semantic loss. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = {"fiscal_year": {"type": "string"}} + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0]["cellDataType"] == "text" + assert cols[0]["formatterFn"] == "none" + + +def _columns_openapi_for_chains(properties: dict) -> dict: + """Variant of ``_columns_openapi_for_widget`` that registers the + route under ``/x/chains`` so the chains-specific branches fire. + """ + base = _columns_openapi_for_widget(properties) + base["paths"]["/x/chains"] = base["paths"].pop("/some/route") + return base + + +def test_data_schema_to_columns_chains_route_hides_redundant_fields(): + """``/chains`` routes hide certain redundant fields.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "underlying_symbol": {"type": "string"}, + "contract_symbol": {"type": "string"}, + } + openapi = _columns_openapi_for_chains(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/x/chains") + for col in cols: + if col["field"] in ("underlying_symbol", "contract_symbol"): + assert col.get("hide") is True + + +def test_data_schema_to_columns_greeks_get_no_formatter(): + """Greeks (``delta`` / ``gamma`` / ``theta`` / ``rho``) get + ``formatterFn="none"``; some get a ``greenRed`` render. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "delta": {"type": "number"}, + "gamma": {"type": "number"}, + "theta": {"type": "number"}, + } + openapi = _columns_openapi_for_chains(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/x/chains") + by_field = {c["field"]: c for c in cols} + assert by_field["delta"]["formatterFn"] == "none" + assert by_field["delta"]["renderFn"] == "greenRed" + assert by_field["theta"]["renderFn"] == "greenRed" + + +def test_data_schema_to_columns_implied_volatility_normalized_percent(): + """``/chains`` routes format ``implied_volatility`` as a normalized + percent. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = {"implied_volatility": {"type": "number"}} + openapi = _columns_openapi_for_chains(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/x/chains") + assert cols[0]["formatterFn"] == "normalizedPercent" + + +def test_data_schema_to_columns_change_field_renders_green_red(): + """``change`` field renders with a green/red color treatment.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = {"change": {"type": "number"}} + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0]["renderFn"] == "greenRed" + + +def test_data_schema_to_columns_percent_unit_measurement(): + """``x-unit_measurement="percent"`` → percent formatter + greenRed.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "rate": {"type": "number", "x-unit_measurement": "percent"}, + "scaled_rate": { + "type": "number", + "x-unit_measurement": "percent", + "x-frontend_multiply": 100, + }, + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + by_field = {c["field"]: c for c in cols} + assert by_field["rate"]["formatterFn"] == "percent" + assert by_field["scaled_rate"]["formatterFn"] == "normalizedPercent" + assert by_field["rate"]["renderFn"] == "greenRed" + + +def test_data_schema_to_columns_x_widget_config_exclude_drops_column(): + """``x-widget_config.exclude=True`` on a property drops the column.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "kept": {"type": "string"}, + "dropped": {"type": "string", "x-widget_config": {"exclude": True}}, + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + fields = [c["field"] for c in cols] + assert "kept" in fields + assert "dropped" not in fields + + +def test_data_schema_to_columns_x_widget_config_merges_overrides(): + """Non-exclude ``x-widget_config`` overrides update the column def.""" + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + properties = { + "amount": { + "type": "number", + "x-widget_config": {"headerName": "AMT"}, + } + } + openapi = _columns_openapi_for_widget(properties) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0]["headerName"] == "AMT" + + +def test_data_schema_to_columns_get_widget_config_short_circuits(): + """``get_widget_config=True`` returns the schema-level + ``x-widget_config`` block instead of a list of column defs. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + openapi = _columns_openapi_for_widget( + {"amount": {"type": "number"}}, + schema_extras={"x-widget_config": {"$.runButton": True}}, + ) + out = data_schema_to_columns_defs( + openapi, "w_obb", "test", "/some/route", get_widget_config=True + ) + assert out == {"$.runButton": True} + + +def test_data_schema_to_columns_oneof_provider_match_in_anyof_outer(): + """When the response schema's outer ``anyOf`` contains an item with + a ``oneOf`` (no ``items.oneOf``), the provider-prefix match picks + the right schema — exercises lines 710-715. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + openapi = { + "components": { + "schemas": { + "FmpData": { + "title": "FmpData", + "properties": {"amount": {"type": "number"}}, + }, + "PolygonData": { + "title": "PolygonData", + "properties": {"value": {"type": "number"}}, + }, + "OBBject_oneof_outer": { + "title": "OBBject_oneof_outer", + "properties": { + "results": { + "anyOf": [ + { + "oneOf": [ + {"$ref": "#/components/schemas/FmpData"}, + {"$ref": "#/components/schemas/PolygonData"}, + ] + }, + {"type": "null"}, + ] + } + }, + }, + } + }, + "paths": { + "/some/route": { + "get": { + "operationId": "outer_op", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OBBject_oneof_outer" + } + } + } + } + }, + } + } + }, + } + cols = data_schema_to_columns_defs(openapi, "w_obb", "fmp", "/some/route") + fields = [c["field"] for c in cols] + assert "amount" in fields # FmpData picked + + +def test_data_schema_to_columns_falls_back_to_first_schema_on_no_match(): + """When neither description nor title matches the provider, the + first schema in the list is used as a final fallback — exercises + line 758-759. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + openapi = { + "components": { + "schemas": { + "AlphaData": { + "title": "alpha_data", + "description": "fmp data", + "properties": {"a": {"type": "number"}}, + }, + "BetaData": { + "title": "beta_data", + "description": "polygon data", + "properties": {"b": {"type": "number"}}, + }, + "OBBject_fallback": { + "title": "OBBject_fallback", + "properties": { + "results": { + "anyOf": [ + { + "items": { + "oneOf": [ + {"$ref": "#/components/schemas/AlphaData"}, + {"$ref": "#/components/schemas/BetaData"}, + ] + }, + "type": "array", + }, + {"type": "null"}, + ] + } + }, + }, + } + }, + "paths": { + "/some/route": { + "get": { + "operationId": "fb_op", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OBBject_fallback" + } + } + } + } + }, + } + } + }, + } + # Provider "yfinance" doesn't match either alpha / beta description + # or title — falls back to the first schema (AlphaData with field "a"). + cols = data_schema_to_columns_defs(openapi, "w_obb", "yfinance", "/some/route") + fields = [c["field"] for c in cols] + assert "a" in fields + + +def test_post_query_schema_for_widget_walks_request_body_ref_properties(): + """A POST whose ``requestBody.schema`` is a ``$ref`` pointing at a + schema with ``properties`` walks each property and emits per-param + schemas. + """ + from openbb_platform_api.utils.openapi import post_query_schema_for_widget + + openapi = { + "components": { + "schemas": { + "PostBody": { + "title": "PostBody", + "properties": { + "name": {"type": "string", "title": "Name"}, + "age": {"type": "integer"}, + }, + } + } + }, + "paths": { + "/api/v1/submit": { + "post": { + "operationId": "submit_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/PostBody"} + } + } + }, + } + } + }, + } + out = post_query_schema_for_widget(openapi, "submit_op", "/api/v1/submit") + names = [p.get("parameter_name") for p in out or []] + # ``provider`` sentinel is auto-appended at the end. + assert "name" in names + assert "age" in names + + +def test_post_query_schema_for_widget_with_title_in_schema_picks_provider(): + """``schema.title`` matching a key inside ``schema`` itself uses + that title as the sole provider — exercises line 1074. + """ + from openbb_platform_api.utils.openapi import post_query_schema_for_widget + + openapi = { + "components": { + "schemas": { + "PostBody": { + "title": "PostBody", + "properties": {"x": {"type": "number"}}, + } + } + }, + "paths": { + "/api/v1/submit": { + "post": { + "operationId": "submit_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "fmp", + "fmp": {"choices": ["a", "b"]}, + "$ref": "#/components/schemas/PostBody", + } + } + } + }, + } + } + }, + } + out = post_query_schema_for_widget(openapi, "submit_op", "/api/v1/submit") + assert isinstance(out, list) + + +def test_post_query_schema_for_widget_with_comma_separated_title_providers(): + """``schema.title="fmp,polygon"`` splits into two providers — + exercises lines 1075-1076. + """ + from openbb_platform_api.utils.openapi import post_query_schema_for_widget + + openapi = { + "components": { + "schemas": { + "PostBody": { + "title": "PostBody", + "properties": {"x": {"type": "number"}}, + } + } + }, + "paths": { + "/api/v1/submit": { + "post": { + "operationId": "submit_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "fmp,polygon", + "$ref": "#/components/schemas/PostBody", + } + } + } + }, + } + } + }, + } + out = post_query_schema_for_widget(openapi, "submit_op", "/api/v1/submit") + assert isinstance(out, list) + + +def test_post_query_schema_for_widget_with_route_parameters(): + """A POST route with explicit ``parameters`` (not just requestBody) + walks them via ``set_param`` — exercises lines 1080-1086. + """ + from openbb_platform_api.utils.openapi import post_query_schema_for_widget + + openapi = { + "components": { + "schemas": { + "PostBody": { + "title": "PostBody", + "properties": {"x": {"type": "number"}}, + } + } + }, + "paths": { + "/api/v1/submit": { + "post": { + "operationId": "submit_op", + "parameters": [ + { + "name": "extra_param", + "in": "query", + "schema": {"type": "string"}, + } + ], + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/PostBody"} + } + } + }, + } + } + }, + } + out = post_query_schema_for_widget(openapi, "submit_op", "/api/v1/submit") + names = [p.get("parameter_name") for p in out or []] + # Both the explicit parameter and the body field made it through. + assert "extra_param" in names + assert "x" in names + + +def test_post_query_schema_for_widget_walks_request_body_with_target_schema_filter(): + """``target_schema`` filters the walked properties to just one + field — covers the ``if target_schema and target_schema != k`` + skip arms. + """ + from openbb_platform_api.utils.openapi import post_query_schema_for_widget + + openapi = { + "components": { + "schemas": { + "PostBody": { + "title": "PostBody", + "properties": { + "kept": {"type": "string"}, + "skipped": {"type": "string"}, + }, + } + } + }, + "paths": { + "/api/v1/submit": { + "post": { + "operationId": "submit_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/PostBody"} + } + } + }, + } + } + }, + } + out = post_query_schema_for_widget( + openapi, "submit_op", "/api/v1/submit", target_schema="kept" + ) + names = [p.get("parameter_name") for p in out or []] + # Only ``kept`` survived the filter. + assert "kept" in names + assert "skipped" not in names + + +def test_post_query_schema_anyof_param_with_choices_and_types(): + """A POST body field with ``anyOf`` mixing types + ``enum`` flows + through ``set_param`` and produces a typed param with choices — + exercises lines 1018, 1038, 1048. + """ + from openbb_platform_api.utils.openapi import post_query_schema_for_widget + + openapi = { + "components": { + "schemas": { + "PostBody": { + "title": "PostBody", + "properties": { + "category": { + "anyOf": [ + {"type": "string", "enum": ["a", "b", "c"]}, + {"type": "null"}, + ] + }, + }, + } + } + }, + "paths": { + "/api/v1/submit": { + "post": { + "operationId": "submit_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/PostBody"} + } + } + }, + } + } + }, + } + out = post_query_schema_for_widget(openapi, "submit_op", "/api/v1/submit") + by_name = {p.get("parameter_name"): p for p in out or []} + assert "category" in by_name + + +def test_set_parameter_options_multiple_providers_with_multiple_enums(): + """``title=fmp,polygon`` + ``anyOf`` containing matching number of + enum lists triggers the position-based mapping — exercises + lines 212-219 (the multi-provider/multi-enum branch). + """ + from openbb_platform_api.utils.openapi import set_parameter_options + + p_schema = { + "title": "fmp,polygon", + "anyOf": [ + {"type": "string", "enum": ["fmp_a", "fmp_b"]}, + {"type": "string", "enum": ["pgn_a", "pgn_b"]}, + ], + } + p = {"parameter_name": "x"} + out = set_parameter_options(p, p_schema, ["fmp", "polygon"]) + options = out.get("options", {}) + # Each provider got its enum at the same positional index. + assert {"label": "fmp_a", "value": "fmp_a"} in options.get("fmp", []) + assert {"label": "pgn_a", "value": "pgn_a"} in options.get("polygon", []) + + +def test_set_parameter_options_general_choices_with_multiple_providers(): + """When ``general_choices`` exist and there are multiple providers + (no provider-specific match), the choices land under ``other`` + rather than a specific provider — exercises lines 282-286. + """ + from openbb_platform_api.utils.openapi import set_parameter_options + + p_schema = { + "title": "params", + "enum": ["x", "y", "z"], + "multiple_items_allowed": False, + } + p = {"parameter_name": "x"} + out = set_parameter_options(p, p_schema, ["fmp", "polygon"]) + assert "other" in out.get("options", {}) + + +def test_set_parameter_options_provider_widget_config_extracted(): + """A per-provider ``x-widget_config`` block lands in the param's + ``x-widget_config`` field — exercises line 190. + """ + from openbb_platform_api.utils.openapi import set_parameter_options + + p_schema = { + "fmp": { + "choices": ["a", "b"], + "x-widget_config": {"label": "FMP override"}, + }, + } + p = {"parameter_name": "x"} + out = set_parameter_options(p, p_schema, ["fmp"]) + assert out.get("x-widget_config", {}).get("fmp", {}).get("label") == "FMP override" + + +def test_process_parameter_strips_multiple_items_suffix_from_description(): + """``"Stock symbol Multiple comma separated items allowed"`` → + description trimmed to ``"Stock symbol"``. Exercises lines 479-481. + """ + from openbb_platform_api.utils.openapi import process_parameter + + param = { + "name": "symbol", + "description": "Stock symbol Multiple comma separated items allowed.", + "schema": {"type": "string"}, + "in": "query", + } + out = process_parameter(param, ["fmp"]) + assert "Multiple comma separated items allowed" not in out["description"] + + +def test_process_parameter_picks_up_widget_config_from_schema_per_provider(): + """``x-widget_config`` keyed by provider gets unwrapped — exercises + lines 533-538. + """ + from openbb_platform_api.utils.openapi import process_parameter + + param = { + "name": "x", + "description": "X param", + "schema": { + "type": "string", + "x-widget_config": {"fmp": {"label": "FMP X"}}, + }, + "in": "query", + } + out = process_parameter(param, ["fmp"]) + # The provider-specific widget_config block is unwrapped into the + # param, so ``label`` is "FMP X". + assert out.get("label") == "FMP X" + + +def test_process_parameter_filters_provider_specific_via_valid_list(): + """A param whose title says one provider AND that provider is in + the current ``providers`` list, but ``set_parameter_options`` + populated ``available_providers`` with a different one (e.g. via + schema-level provider keys). Exercises lines 553-569. + """ + from openbb_platform_api.utils.openapi import process_parameter + + # Title says ``fmp``; both fmp and polygon are in providers; the + # param survives because fmp is current. + param = { + "name": "tier", + "description": "Tier desc.", + "schema": {"type": "string", "title": "fmp"}, + "in": "query", + } + out = process_parameter(param, ["fmp", "polygon"]) + assert out.get("parameter_name") == "tier" + assert "fmp" in out.get("available_providers", []) + + +def test_process_parameter_extracts_multiple_providers_from_description(): + """A description with ``(provider: fmp, polygon)`` adds both to the + available_providers list — exercises lines 514-519. + """ + from openbb_platform_api.utils.openapi import process_parameter + + param = { + "name": "tier", + "description": "Tier (provider: fmp, polygon).", + "schema": {"type": "string"}, + "in": "query", + } + out = process_parameter(param, ["fmp", "polygon"]) + assert "fmp" in out["available_providers"] + assert "polygon" in out["available_providers"] + + +def test_data_schema_to_columns_schema_level_field_exclude(): + """When a schema-level field (a peer of ``properties`` in + ``target_schema``) carries ``x-widget_config.exclude=True``, the + column for that property is dropped — exercises lines 960-963. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + openapi = _columns_openapi_for_widget( + {"amount": {"type": "number"}, "ignored": {"type": "string"}}, + schema_extras={ + # ``ignored`` peer entry (not under properties) carries the + # exclude config. The function reads ``target_schema.get(key)`` + # to look for this peer-level config. + "ignored": {"x-widget_config": {"exclude": True}}, + }, + ) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + fields = [c["field"] for c in cols] + assert "amount" in fields + assert "ignored" not in fields + + +def test_data_schema_to_columns_schema_level_field_merges_widget_config(): + """A schema-level peer entry's ``x-widget_config`` (without exclude) + merges into the column def — exercises line 963 alone. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + openapi = _columns_openapi_for_widget( + {"amount": {"type": "number"}}, + schema_extras={ + "amount": {"x-widget_config": {"headerName": "Total"}}, + }, + ) + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + assert cols[0].get("headerName") == "Total" + + +def test_set_parameter_options_general_choices_anyof_no_title_providers(): + """``anyOf`` containing enums + no title_providers — populates + general_choices via the second branch, exercises lines 259-268. + """ + from openbb_platform_api.utils.openapi import set_parameter_options + + p_schema = { + "anyOf": [ + {"type": "string", "enum": ["x", "y"]}, + {"type": "null"}, + ], + } + p = {"parameter_name": "x"} + out = set_parameter_options(p, p_schema, ["fmp"]) + # With one provider, general_choices land under that provider's key. + options = out.get("options", {}) + assert {"label": "x", "value": "x"} in options.get("fmp", []) + + +def test_post_query_schema_for_widget_walks_anyof_schema(): + """A POST whose ``requestBody`` schema has ``anyOf`` (rather than a + single ``$ref``) walks each variant — exercises lines 1133-1166. + """ + from openbb_platform_api.utils.openapi import post_query_schema_for_widget + + openapi = { + "components": { + "schemas": { + "Variant1": { + "title": "Variant1", + "properties": {"x": {"type": "number"}}, + }, + } + }, + "paths": { + "/api/v1/submit": { + "post": { + "operationId": "submit_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + {"$ref": "#/components/schemas/Variant1"}, + { + "properties": {"y": {"type": "string"}}, + }, + ], + } + } + } + }, + } + } + }, + } + out = post_query_schema_for_widget(openapi, "submit_op", "/api/v1/submit") + names = [p.get("parameter_name") for p in out or []] + # Both variants' properties surfaced. + assert "x" in names + assert "y" in names + + +def test_data_schema_to_columns_uses_result_schema_ref_directly_when_no_refs(): + """When the response schema's anyOf items don't unpack into refs but + the schema itself has properties, that schema becomes the source. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + openapi = { + "components": { + "schemas": { + "FlatRoute": { + "title": "FlatRoute", + "properties": { + "results": { + # ``anyOf`` with no $ref / oneOf / items.$ref — + # schema_refs stays empty; schema's properties + # are used directly. + "anyOf": [{"type": "object"}], + "properties": {"x": {"type": "number"}}, + } + }, + }, + } + }, + "paths": { + "/some/route": { + "get": { + "operationId": "flat_op", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/FlatRoute"} + } + } + } + }, + } + } + }, + } + cols = data_schema_to_columns_defs(openapi, "w_obb", "test", "/some/route") + # Either covers the column or returns []; either way the lookup didn't crash. + assert isinstance(cols, list) + + +def test_data_schema_to_columns_picks_provider_specific_schema_via_description(): + """When multiple data schemas are available, match by description + that starts with the provider name. + """ + from openbb_platform_api.utils.openapi import data_schema_to_columns_defs + + openapi = { + "components": { + "schemas": { + "FmpData": { + "description": "fmp data class", + "properties": {"amount": {"type": "number"}}, + }, + "PolygonData": { + "description": "polygon data class", + "properties": {"value": {"type": "number"}}, + }, + "OBBject_multi": { + "title": "OBBject_multi", + "properties": { + "results": { + "anyOf": [ + { + "items": { + "oneOf": [ + {"$ref": "#/components/schemas/FmpData"}, + { + "$ref": "#/components/schemas/PolygonData" + }, + ] + }, + "type": "array", + }, + {"type": "null"}, + ] + } + }, + }, + } + }, + "paths": { + "/some/route": { + "get": { + "operationId": "multi_op", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OBBject_multi" + } + } + } + } + }, + } + } + }, + } + cols = data_schema_to_columns_defs(openapi, "w_obb", "fmp", "/some/route") + fields = [c["field"] for c in cols] + # Picked the FMP variant (matches by description), so its ``amount`` + # property is the one that surfaces. + assert "amount" in fields + assert "value" not in fields + + +# --------------------------------------------------------------------------- +# set_parameter_type type-rewrite branches +# --------------------------------------------------------------------------- + + +def test_set_parameter_type_string_becomes_text(): + """``schema.type=string`` → ``p.type=text`` for widget rendering.""" + from openbb_platform_api.utils.openapi import set_parameter_type + + p = {"parameter_name": "x", "value": ""} + out = set_parameter_type(p, {"type": "string"}) + assert out["type"] == "text" + + +def test_set_parameter_type_numeric_value_becomes_number(): + """A numeric ``value`` (without ``type``) trips the number branch.""" + from openbb_platform_api.utils.openapi import set_parameter_type + + p = {"parameter_name": "x", "value": 1.5} + out = set_parameter_type(p, {}) + assert out["type"] == "number" + + +def test_set_parameter_type_boolean_via_anyof(): + """An ``anyOf`` whose first element is ``boolean`` → bool widget.""" + from openbb_platform_api.utils.openapi import set_parameter_type + + p = {"parameter_name": "x", "value": True} + out = set_parameter_type(p, {"anyOf": [{"type": "boolean"}, {"type": "null"}]}) + assert out["type"] == "boolean" + + +def test_set_parameter_type_date_field_becomes_date(): + """``parameter_name="date"`` or any ``*_date`` field gets the date type.""" + from openbb_platform_api.utils.openapi import set_parameter_type + + p1 = set_parameter_type({"parameter_name": "date", "value": ""}, {}) + p2 = set_parameter_type({"parameter_name": "start_date", "value": ""}, {}) + assert p1["type"] == "date" + assert p2["type"] == "date" + + +def test_set_parameter_type_timeframe_becomes_text(): + """``timeframe`` in the parameter name → text (not number).""" + from openbb_platform_api.utils.openapi import set_parameter_type + + p = set_parameter_type({"parameter_name": "timeframe", "value": ""}, {}) + assert p["type"] == "text" + + +def test_set_parameter_type_limit_always_number(): + """``parameter_name="limit"`` is always a number widget.""" + from openbb_platform_api.utils.openapi import set_parameter_type + + p = set_parameter_type({"parameter_name": "limit", "value": ""}, {}) + assert p["type"] == "number" + + +def test_extract_provider_description_picks_section_for_target_provider(): + """A multi-provider description splits sections by ``(provider:..)`` + markers; the matching section returns just the description text. + """ + from openbb_platform_api.utils.openapi import _extract_provider_description + + desc = ( + "General intro for symbol;\n " + "fmp-specific notes (provider: fmp);\n " + "polygon-specific notes (provider: polygon)" + ) + out = _extract_provider_description(desc, "polygon") + assert "polygon-specific notes" in out + + +def test_extract_provider_description_handles_multi_section_text(): + """When a section preceding the provider marker contains multiple + semicolon-newline-separated chunks, the last one is the matching + provider's text — exercises lines 361-363. + """ + from openbb_platform_api.utils.openapi import _extract_provider_description + + desc = ( + "first;\n second;\n fmp section (provider: fmp);\n " + "polygon section (provider: polygon)" + ) + out = _extract_provider_description(desc, "fmp") + # ``fmp section`` is the last section before the (provider: fmp) marker. + assert "fmp section" in out + + +def test_get_query_schema_for_widget_skips_sort_and_order_params(mock_openapi_json): + """``sort`` and ``order`` parameters are skipped during schema + construction; ``chart`` toggles the has_chart flag and is also + skipped — exercises lines 605-609 of get_query_schema_for_widget. + """ + from openbb_platform_api.utils.openapi import get_query_schema_for_widget + + # Inject sort, order, chart parameters into a route's parameter list. + test_openapi = { + "paths": { + "/test_route": { + "get": { + "operationId": "test_op", + "parameters": [ + { + "name": "sort", + "in": "query", + "schema": {"type": "string"}, + }, + { + "name": "order", + "in": "query", + "schema": {"type": "string"}, + }, + { + "name": "chart", + "in": "query", + "schema": {"type": "boolean"}, + }, + { + "name": "symbol", + "in": "query", + "schema": {"type": "string"}, + }, + ], + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + } + } + }, + "components": {"schemas": {}}, + } + params, has_chart = get_query_schema_for_widget(test_openapi, "/test_route") + names = [p.get("parameter_name") for p in params] + # Sort/order skipped; symbol kept. + assert "sort" not in names + assert "order" not in names + assert "symbol" in names + # ``chart`` triggered the flag and was skipped. + assert has_chart is True + assert "chart" not in names + + +def test_set_parameter_type_array_collapses_to_text(): + """An ``array`` or ``list`` typed param renders as text (one widget + can't natively show structured arrays). + """ + from openbb_platform_api.utils.openapi import set_parameter_type + + p = set_parameter_type( + {"parameter_name": "tags", "value": "", "type": "array"}, {"type": "array"} + ) + assert p["type"] == "text" + + +@pytest.mark.parametrize( + "openapi_operation_id", + [ + "economy_survey_sloos", + "economy_survey_university_of_michigan", + "economy_balance_of_payments", + ], +) +def test_get_data_schema_for_widget(mock_openapi_json, openapi_operation_id): + schema = get_data_schema_for_widget(mock_openapi_json, openapi_operation_id) + assert schema is not None + + +@pytest.mark.parametrize( + "openapi_operation_id", + [ + "form_submit_form_submit_post", + ], +) +def test_post_query_schema_for_widget(mock_openapi_json, openapi_operation_id): + """Test post_query_schema_for_widget function.""" + schema = post_query_schema_for_widget(mock_openapi_json, openapi_operation_id) + assert schema + + +@pytest.mark.parametrize( + "openapi_operation_id", + [ + "economy_survey_sloos", + "economy_survey_university_of_michigan", + "economy_balance_of_payments", + ], +) +def test_data_schema_to_columns_defs(mock_openapi_json, openapi_operation_id): + """Test data_schema_to_columns_defs function.""" + column_defs = data_schema_to_columns_defs( + mock_openapi_json, openapi_operation_id, provider="fred" + ) + assert len(column_defs) > 1 # There should be at least two columns + + +# --------------------------------------------------------------------------- +# SSRM auto-detection — _schema_inherits + is_ssrm_route +# --------------------------------------------------------------------------- + + +def _ssrm_openapi( + *, + request_schema: str | None = None, + response_schema: str | None = None, + schemas: dict | None = None, + method: str = "post", +) -> dict: + """Build a minimal OpenAPI dict with a single route whose body and/or + response point at the named component schemas. + """ + body: dict = {} + if request_schema: + body = { + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": f"#/components/schemas/{request_schema}"} + } + } + } + } + response_schema_block: dict = {"type": "object"} + if response_schema: + response_schema_block = {"$ref": f"#/components/schemas/{response_schema}"} + return { + "components": {"schemas": schemas or {}}, + "paths": { + "/api/v1/ssrm": { + method: { + "operationId": "ssrm_op", + "summary": "S", + "description": "D", + "responses": { + "200": { + "content": { + "application/json": {"schema": response_schema_block} + } + } + }, + **body, + } + } + }, + } + + +def test_is_ssrm_route_request_body_direct_match(): + """Body $ref → ``AgGridRowsRequest`` is the canonical SSRM signal.""" + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi( + request_schema="AgGridRowsRequest", + schemas={"AgGridRowsRequest": {"title": "AgGridRowsRequest"}}, + ) + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is True + + +def test_is_ssrm_route_request_body_subclass_via_allof(): + """A Pydantic subclass of ``AgGridRowsRequest`` shows up in OpenAPI as + ``allOf: [{"$ref": ".../AgGridRowsRequest"}]`` — must still match. + """ + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi( + request_schema="ProviderRowsRequest", + schemas={ + "ProviderRowsRequest": { + "title": "ProviderRowsRequest", + "allOf": [{"$ref": "#/components/schemas/AgGridRowsRequest"}], + }, + "AgGridRowsRequest": {"title": "AgGridRowsRequest"}, + }, + ) + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is True + + +def test_is_ssrm_route_request_body_transitive_inheritance(): + """Subclass-of-subclass still matches via the recursive walk.""" + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi( + request_schema="GrandchildRowsRequest", + schemas={ + "GrandchildRowsRequest": { + "title": "GrandchildRowsRequest", + "allOf": [{"$ref": "#/components/schemas/MidRowsRequest"}], + }, + "MidRowsRequest": { + "title": "MidRowsRequest", + "allOf": [{"$ref": "#/components/schemas/AgGridRowsRequest"}], + }, + "AgGridRowsRequest": {"title": "AgGridRowsRequest"}, + }, + ) + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is True + + +def test_is_ssrm_route_skips_inline_allof_entries_without_ref(): + """Pydantic emits inline schemas inside ``allOf`` for field-level + constraints (e.g. ``allOf: [{"$ref": ...}, {"description": "..."}]``). + Entries without a ``$ref`` should be skipped, not crash. + """ + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi( + request_schema="ConstrainedRowsRequest", + schemas={ + "ConstrainedRowsRequest": { + "title": "ConstrainedRowsRequest", + "allOf": [ + {"description": "no ref here"}, + {"$ref": "#/components/schemas/AgGridRowsRequest"}, + ], + }, + "AgGridRowsRequest": {"title": "AgGridRowsRequest"}, + }, + ) + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is True + + +def test_is_ssrm_route_handles_allof_cycle(): + """Cyclic ``allOf`` chains shouldn't recurse forever.""" + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi( + request_schema="A", + schemas={ + "A": {"title": "A", "allOf": [{"$ref": "#/components/schemas/B"}]}, + "B": {"title": "B", "allOf": [{"$ref": "#/components/schemas/A"}]}, + }, + ) + # No match, but more importantly: doesn't blow the stack. + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is False + + +def test_is_ssrm_route_response_named_aggrid_rows_response(): + """Pydantic emits ``AgGridRowsResponse[Foo]`` with the title + ``AgGridRowsResponse_Foo_`` — prefix match wins. + """ + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi( + response_schema="AgGridRowsResponse_FooRow_", + schemas={"AgGridRowsResponse_FooRow_": {"title": "AgGridRowsResponse[FooRow]"}}, + ) + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is True + + +def test_is_ssrm_route_response_structural_rowdata_rowcount(): + """Even without the canonical name, a response whose properties + include both ``rowData`` and ``rowCount`` is treated as SSRM. + """ + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi( + response_schema="CustomRowsResponse", + schemas={ + "CustomRowsResponse": { + "title": "CustomRowsResponse", + "properties": { + "rowData": {"type": "array"}, + "rowCount": {"type": "integer"}, + }, + } + }, + ) + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is True + + +def test_is_ssrm_route_negative_unrelated_post(): + """Plain POST routes with neither signal are not promoted.""" + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi( + request_schema="Plain", + response_schema="PlainOut", + schemas={ + "Plain": {"title": "Plain"}, + "PlainOut": { + "title": "PlainOut", + "properties": {"x": {"type": "number"}}, + }, + }, + ) + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is False + + +def test_is_ssrm_route_negative_response_only_rowdata(): + """``rowData`` alone (no ``rowCount``) does NOT count as SSRM — + structural match requires both keys, on purpose. + """ + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi( + response_schema="HalfMatch", + schemas={ + "HalfMatch": { + "title": "HalfMatch", + "properties": {"rowData": {"type": "array"}}, + } + }, + ) + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is False + + +def test_is_ssrm_route_missing_route_returns_false(): + """Defensive — unknown route can't match.""" + from openbb_platform_api.utils.openapi import is_ssrm_route + + assert is_ssrm_route({"paths": {}, "components": {}}, "/nope", "post") is False + + +def test_is_ssrm_route_body_ref_pointing_at_unknown_schema(): + """A $ref to a missing component shouldn't crash; just doesn't match.""" + from openbb_platform_api.utils.openapi import is_ssrm_route + + openapi = _ssrm_openapi(request_schema="MissingFromComponents") + assert is_ssrm_route(openapi, "/api/v1/ssrm", "post") is False + + +def test_get_data_schema_for_widget_unwraps_ssrm_rowdata_to_row_model(): + """For SSRM responses, ``get_data_schema_for_widget`` descends from + ``AgGridRowsResponse[Row]`` to the row model so column auto-detection + sees the row's fields. + """ + openapi = { + "components": { + "schemas": { + "FooRow": { + "title": "FooRow", + "properties": { + "symbol": {"type": "string"}, + "price": {"type": "number"}, + }, + }, + "AgGridRowsResponse_FooRow_": { + "title": "AgGridRowsResponse[FooRow]", + "properties": { + "rowData": { + "type": "array", + "items": {"$ref": "#/components/schemas/FooRow"}, + }, + "rowCount": {"type": "integer"}, + }, + }, + } + }, + "paths": { + "/api/v1/ssrm": { + "post": { + "operationId": "ssrm_op", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgGridRowsResponse_FooRow_" + } + } + } + } + }, + } + } + }, + } + schema = get_data_schema_for_widget(openapi, "ssrm_op", "/api/v1/ssrm") + assert schema == openapi["components"]["schemas"]["FooRow"] + + +def test_get_data_schema_for_widget_ssrm_inline_row_items_no_ref(): + """When ``rowData.items`` is inlined (no $ref), the inline schema is + returned as-is — column generation still has something to walk. + """ + openapi = { + "components": { + "schemas": { + "InlineSsrm": { + "title": "InlineSsrm", + "properties": { + "rowData": { + "type": "array", + "items": { + "type": "object", + "properties": {"q": {"type": "number"}}, + }, + }, + "rowCount": {"type": "integer"}, + }, + } + } + }, + "paths": { + "/api/v1/ssrm": { + "post": { + "operationId": "ssrm_op", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InlineSsrm" + } + } + } + } + }, + } + } + }, + } + schema = get_data_schema_for_widget(openapi, "ssrm_op", "/api/v1/ssrm") + assert schema == { + "type": "object", + "properties": {"q": {"type": "number"}}, + } + + +class TestExtractProviderDescription: + """Tests for _extract_provider_description function.""" + + def test_simple_description_no_provider(self): + """Test extraction from simple description without provider markers.""" + desc = "This is a simple description." + result = _extract_provider_description(desc, "fred") + assert result == "This is a simple description." + + def test_description_with_multiple_items_suffix(self): + """Test that 'Multiple comma separated items allowed' is stripped.""" + desc = "Description text. Multiple comma separated items allowed." + result = _extract_provider_description(desc, "fred") + assert result == "Description text." + + def test_provider_specific_description_extraction(self): + """Test extraction of provider-specific description.""" + desc = "Fred specific description (provider: fred);\n Other description (provider: other)" + result = _extract_provider_description(desc, "fred") + assert result == "Fred specific description" + + def test_embedded_semicolon_preserved(self): + """Test that semicolons in description text are preserved.""" + desc = "A semicolon delimited list of tags; Example: 'japan;imports' (provider: fred)" + result = _extract_provider_description(desc, "fred") + assert "Example: 'japan;imports'" in result + + def test_multiple_providers_in_marker(self): + """Test handling of multiple providers in one marker.""" + desc = "Shared description (provider: fred, yfinance)" + result = _extract_provider_description(desc, "fred") + assert result == "Shared description" + + def test_provider_at_end_of_marker(self): + """Test provider listed last in comma-separated list.""" + desc = "Description text (provider: other, fred)" + result = _extract_provider_description(desc, "fred") + assert result == "Description text" + + def test_empty_description(self): + """Test handling of empty description.""" + result = _extract_provider_description("", "fred") + assert result == "" + + def test_fallback_to_general_description(self): + """Test fallback when provider not found in markers.""" + desc = "General description (provider: other_provider)" + result = _extract_provider_description(desc, "fred") + assert result == "General description" + + +class TestProcessParameterWithSingleProvider: + """Tests for process_parameter function with single_provider parameter.""" + + def test_provider_specific_default_extracted(self): + """Test that provider-specific default is extracted when single_provider is set.""" + param = { + "name": "country", + "description": "Country for fred (provider: fred); Country for other (provider: other)", + "schema": { + "fred": {"default": "united_states"}, + "other": {"default": "germany"}, + }, + } + result = process_parameter(param, ["fred", "other"], single_provider="fred") + assert result["value"] == "united_states" + assert ( + "fred" in result["description"].lower() + or "country" in result["description"].lower() + ) + + def test_global_default_used_when_no_provider_specific(self): + """Test that global default is used when provider has no specific default.""" + param = { + "name": "limit", + "description": "Limit results", + "default": 100, + "schema": {}, + } + result = process_parameter(param, ["fred", "other"], single_provider="fred") + assert result["value"] == 100 + + def test_global_default_skipped_when_other_providers_have_defaults(self): + """Test that global default is skipped when other providers have specific defaults.""" + param = { + "name": "topic", + "description": "Topic param", + "default": "general_default", + "schema": { + "other_provider": {"default": "specific_default"}, + }, + } + result = process_parameter( + param, ["fred", "other_provider"], single_provider="fred" + ) + # fred should not inherit global default because other_provider has a specific one + assert result["value"] is None + + def test_provider_specific_description_extracted(self): + """Test that provider-specific description is extracted.""" + param = { + "name": "indicator", + "description": "Fred indicator description (provider: fred); Other indicator (provider: other)", + "schema": {}, + } + result = process_parameter(param, ["fred", "other"], single_provider="fred") + assert "Fred indicator" in result["description"] + assert "Other" not in result["description"] + + def test_no_single_provider_uses_general_description(self): + """Test that without single_provider, general description is used.""" + param = { + "name": "symbol", + "description": "General symbol description (provider: fred); Other desc (provider: other)", + "schema": {}, + } + result = process_parameter(param, ["fred", "other"], single_provider=None) + assert result["description"] == "General symbol description" + + +class TestGetQuerySchemaWithSingleProvider: + """Tests for get_query_schema_for_widget with single_provider parameter.""" + + def test_single_provider_passed_to_process_parameter(self, mock_openapi_json): + """Test that single_provider is passed through to process_parameter.""" + # Use a route that has provider-specific params + route = "/api/v1/economy/cpi" + query_schema, _ = get_query_schema_for_widget( + mock_openapi_json, route, single_provider="fred" + ) + country_param = next( + (p for p in query_schema if p["parameter_name"] == "country"), None + ) + if country_param: + # Should have the default value for fred provider + assert country_param.get("value") is not None or country_param.get( + "optional" + ) + + +# --------------------------------------------------------------------------- +# Coverage gaps in process_parameter, data_schema_to_columns_defs, and +# post_query_schema_for_widget +# --------------------------------------------------------------------------- + + +def test_process_parameter_label_falls_back_to_schema_title_when_name_empty(): + """Empty ``param["name"]`` produces an empty ``param_name.title()`` — + triggers the ``schema.title or param.title`` fallback at line 481. + """ + out = process_parameter( + {"name": "", "schema": {"title": "Fallback Title"}}, ["fmp"] + ) + assert out["label"] == "Fallback Title" + + +def test_process_parameter_strips_multiple_items_suffix_from_schema_description(): + """When ``param.description`` is missing but ``schema.description`` + carries the ``Multiple comma separated items allowed`` suffix, the + suffix-strip at lines 551-557 kicks in. The line 489-495 path + that *normally* strips the suffix is gated on ``param.description`` + being truthy — when it isn't, we fall back to schema description + and the second strip pass cleans it up. + """ + out = process_parameter( + { + "name": "tickers", + "schema": { + "description": ( + "List of tickers. Multiple comma separated items allowed." + ), + }, + }, + ["fmp"], + ) + assert "Multiple comma separated items allowed" not in out["description"] + assert out["description"].startswith("List of tickers") + + +def test_process_parameter_extracts_multi_provider_from_comma_title(): + """A schema title of the form ``"fmp,polygon"`` is split into a list + of providers — exercises lines 576-578. After the split, the + parameter is gated to only those providers via the + ``valid_provider_list`` filter further down. + """ + out = process_parameter( + {"name": "x", "schema": {"title": "fmp,polygon"}}, + ["fmp", "polygon", "yfinance"], + ) + # Both providers from the title made it onto available_providers, + # and at least one matches our current providers list, so the + # parameter is kept. + assert out + assert sorted(out.get("available_providers", [])) == ["fmp", "polygon"] + + +def test_data_schema_to_columns_picks_provider_via_title_when_descriptions_dont_match(): + """Multi-schema flow: when none of the candidate schema descriptions + match the provider, fall back to matching by ``title`` — exercises + lines 844-845. + """ + openapi = { + "components": { + "schemas": { + "OBBject_FooData_": { + "title": "OBBject_FooData_", + "properties": { + "results": { + "anyOf": [ + { + "items": { + "oneOf": [ + {"$ref": "#/components/schemas/FmpFooData"}, + {"$ref": "#/components/schemas/YFooData"}, + ] + }, + "type": "array", + } + ] + } + }, + }, + # Descriptions are deliberately generic so the first + # description-loop misses; titles carry the provider + # prefix instead, forcing the title fallback to fire. + "FmpFooData": { + "title": "FmpFooData", + "description": "Generic foo data shape.", + "properties": {"value": {"type": "number"}}, + }, + "YFooData": { + "title": "YFooData", + "description": "Generic foo data shape.", + "properties": {"value": {"type": "number"}}, + }, + } + }, + "paths": { + "/api/v1/foo": { + "get": { + "operationId": "foo_op", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OBBject_FooData_" + } + } + } + } + }, + } + } + }, + } + cols = data_schema_to_columns_defs(openapi, "foo_op", "fmp", "/api/v1/foo") + # Title-fallback selected the FmpFooData schema; only one column + # comes back regardless, but the lookup didn't crash and produced + # a non-empty result driven by the title path. + assert any(c["field"] == "value" for c in cols) + + +def test_post_query_schema_anyof_property_no_type_falls_back_to_text(): + """A POST body property whose ``anyOf`` items have no ``type`` keys + (only ``$ref`` or ``enum``) hits the fallback type assignment at + lines 1127-1135. + """ + openapi = { + "components": { + "schemas": { + "Body": { + "title": "Body", + "type": "object", + "properties": { + # anyOf entries are pure $refs — no ``type`` key, + # so ``param_types`` ends up empty and the else + # branch at line 1127 sets the fallback type. + "obj": { + "anyOf": [ + {"$ref": "#/components/schemas/Sub"}, + {"type": "null"}, + ], + "type": "object", + } + }, + }, + "Sub": {"title": "Sub", "type": "object", "properties": {}}, + } + }, + "paths": { + "/api/v1/post": { + "post": { + "operationId": "post_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Body"} + } + } + }, + } + } + }, + } + params = post_query_schema_for_widget(openapi, "post_op", "/api/v1/post") + obj_param = next((p for p in params if p["parameter_name"] == "obj"), None) + assert obj_param is not None + # ``type: "object"`` falls through to ``"text"`` per the fallback. + assert obj_param["type"] == "text" + + +def test_post_query_schema_top_level_enum_extends_choices(): + """A POST body property with a top-level ``enum`` (no ``anyOf``) + feeds the ``choices.extend`` path at line 1137 — the resulting + options dict carries those values under the ``custom`` key. + """ + openapi = { + "components": { + "schemas": { + "Body": { + "title": "Body", + "type": "object", + "properties": { + "side": {"enum": ["buy", "sell", "hold"]}, + }, + } + } + }, + "paths": { + "/api/v1/post": { + "post": { + "operationId": "post_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Body"} + } + } + }, + } + } + }, + } + params = post_query_schema_for_widget(openapi, "post_op", "/api/v1/post") + side = next(p for p in params if p["parameter_name"] == "side") + # ``set_param`` builds ``options = {"custom": [...]}``; + # ``set_parameter_options`` rewraps it as ``{provider: choices}``. + options = side.get("options", {}) + flat_values = [] + for v in options.values(): + if isinstance(v, list): + flat_values.extend(c.get("value") for c in v) + assert {"buy", "sell", "hold"}.issubset(set(flat_values)) + + +def test_post_query_schema_route_parameters_as_dict(): + """OpenAPI normally emits ``parameters`` as a list, but the function + also handles a dict shape — exercises lines 1173-1175. + """ + openapi = { + "components": { + "schemas": { + "Body": { + "title": "Body", + "type": "object", + "properties": {"x": {"type": "number"}}, + } + } + }, + "paths": { + "/api/v1/post": { + "post": { + "operationId": "post_op", + "parameters": { # dict form, not list + "extra": {"type": "string"} + }, + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Body"} + } + } + }, + } + } + }, + } + params = post_query_schema_for_widget(openapi, "post_op", "/api/v1/post") + assert any(p.get("parameter_name") == "extra" for p in params) + + +def test_post_query_schema_param_ref_falsy_falls_back_to_schema_dict(): + """When ``schema["$ref"]`` is falsy (empty/None), the ``or schema`` + fallback at line 1181 returns the schema dict itself; the dict + branch at line 1184-1185 then strips it down to its ``type`` value + so the rest of the function can keep walking. + """ + openapi = { + "components": {"schemas": {}}, + "paths": { + "/api/v1/post": { + "post": { + "operationId": "post_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + # ``$ref: ""`` is falsy → the function + # falls back to ``schema`` (this dict), + # which has ``type: "object"`` so the + # dict-strip branch at 1184-1185 fires. + "schema": {"$ref": "", "type": "object"} + } + } + }, + } + } + }, + } + # No crash, returns either [] or None (depending on the rest of the + # walk). The important thing for coverage is that we hit lines + # 1184-1185. + out = post_query_schema_for_widget(openapi, "post_op", "/api/v1/post") + assert out in (None, []) + + +def test_post_query_schema_anyof_body_with_target_schema_filters_properties(): + """When the body schema is an ``anyOf`` list and a ``target_schema`` + is supplied, the loop at line 1232-1243 walks each item and skips + properties whose key doesn't match ``target_schema`` — exercises + lines 1236 (ref-based skip) and 1242 (inline-property skip). + """ + openapi = { + "components": { + "schemas": { + "RefBody": { + "title": "RefBody", + "type": "object", + "properties": { + "wanted": {"type": "string"}, + "skipped": {"type": "number"}, + }, + } + } + }, + "paths": { + "/api/v1/post": { + "post": { + "operationId": "post_op", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + # Mix: one $ref item + one inline-properties + # item, each carrying both ``wanted`` and + # ``skipped`` keys so the target_schema + # filter has to skip on both branches. + "anyOf": [ + {"$ref": "#/components/schemas/RefBody"}, + { + "properties": { + "wanted": {"type": "string"}, + "skipped": {"type": "number"}, + } + }, + ], + } + } + } + }, + } + } + }, + } + params = post_query_schema_for_widget( + openapi, "post_op", "/api/v1/post", target_schema="wanted" + ) + names = [p["parameter_name"] for p in params] + assert "wanted" in names + assert "skipped" not in names + + +def test_get_data_schema_descends_into_inline_results_envelope(): + """Spec-driven launches synthesize the response schema inline + under ``content.application/json.schema`` (no ``components.schemas`` + $ref roundtrip). The Socrata generator emits the + ``{"properties": {"results": {"items": {...}}}}`` envelope shape; + column auto-detection has to descend into ``items`` to find the + row fields. Without this, the launcher generated widget configs + with no ``columnsDefs`` for the entire spec-driven proxy path. + """ + from openbb_platform_api.utils.openapi import get_data_schema_for_widget + + inline_schema = { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": {"type": "string", "format": "date"}, + "year": {"type": "integer"}, + "region": {"type": "string"}, + "price": {"type": "number"}, + }, + }, + } + }, + } + openapi = { + "components": {"schemas": {}}, + "paths": { + "/resource/abcd-1234.json": { + "get": { + "operationId": "fertilizer.prices", + "responses": { + "200": { + "content": {"application/json": {"schema": inline_schema}} + } + }, + } + } + }, + } + out = get_data_schema_for_widget( + openapi, "fertilizer.prices", "/resource/abcd-1234.json" + ) + assert isinstance(out, dict) + assert "properties" in out + assert set(out["properties"]) == {"date", "year", "region", "price"} + + +def test_get_data_schema_descends_into_inline_results_with_ref_items(): + """Results envelope where ``items`` is a ``$ref`` to a component + schema — the launcher resolves the $ref and returns the row + schema. Mixed inline/$ref shape that real OpenAPI specs produce + when the row model is reusable. + """ + from openbb_platform_api.utils.openapi import get_data_schema_for_widget + + openapi = { + "components": { + "schemas": { + "FooRow": { + "type": "object", + "properties": {"x": {"type": "integer"}}, + } + } + }, + "paths": { + "/r": { + "get": { + "operationId": "r_op", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FooRow" + }, + } + }, + } + } + } + } + }, + } + } + }, + } + out = get_data_schema_for_widget(openapi, "r_op", "/r") + assert out == openapi["components"]["schemas"]["FooRow"] + + +def test_get_data_schema_returns_results_block_when_items_missing(): + """``results`` with no ``items`` (degenerate / non-array) — return + the ``results`` block itself so column generation can still walk + its properties if it has any. + """ + from openbb_platform_api.utils.openapi import get_data_schema_for_widget + + inline_schema = { + "type": "object", + "properties": { + "results": { + # No ``items`` — properties live directly here. + "type": "object", + "properties": {"x": {"type": "integer"}}, + } + }, + } + openapi = { + "components": {"schemas": {}}, + "paths": { + "/r": { + "get": { + "operationId": "r_op", + "responses": { + "200": { + "content": {"application/json": {"schema": inline_schema}} + } + }, + } + } + }, + } + out = get_data_schema_for_widget(openapi, "r_op", "/r") + # Returns the ``results`` dict so downstream column generation + # can still walk its properties. + assert out == inline_schema["properties"]["results"] + + +def test_get_data_schema_descends_into_inline_rowdata_with_ref_items(): + """SSRM rowData envelope where ``items`` is a ``$ref`` — same as + the results-with-ref case, but for the SSRM payload shape. + """ + from openbb_platform_api.utils.openapi import get_data_schema_for_widget + + openapi = { + "components": { + "schemas": { + "BarRow": { + "type": "object", + "properties": {"y": {"type": "string"}}, + } + } + }, + "paths": { + "/s": { + "post": { + "operationId": "s_op", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rowData": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BarRow" + }, + }, + "rowCount": {"type": "integer"}, + }, + } + } + } + } + }, + } + } + }, + } + out = get_data_schema_for_widget(openapi, "s_op", "/s") + assert out == openapi["components"]["schemas"]["BarRow"] + + +def test_get_data_schema_descends_into_inline_rowdata_ssrm(): + """Inline SSRM-shaped schema (``rowData`` / ``rowCount`` envelope) + descends to the row schema under ``rowData.items`` — same logic + as the components-resolved path, but without going through + ``$ref`` lookup. + """ + from openbb_platform_api.utils.openapi import get_data_schema_for_widget + + inline_schema = { + "type": "object", + "properties": { + "rowData": { + "type": "array", + "items": { + "type": "object", + "properties": { + "symbol": {"type": "string"}, + "price": {"type": "number"}, + }, + }, + }, + "rowCount": {"type": "integer"}, + }, + } + openapi = { + "components": {"schemas": {}}, + "paths": { + "/some/ssrm": { + "post": { + "operationId": "ssrm_op", + "responses": { + "200": { + "content": {"application/json": {"schema": inline_schema}} + } + }, + } + } + }, + } + out = get_data_schema_for_widget(openapi, "ssrm_op", "/some/ssrm") + assert isinstance(out, dict) + assert set(out["properties"]) == {"symbol", "price"} + + +def test_get_data_schema_returns_inline_object_as_row_when_no_envelope(): + """A bare inline object schema (no ``results`` / ``rowData`` + envelope) describes a single row directly. Falls through to + returning the schema itself so column generation has something + to walk. + """ + from openbb_platform_api.utils.openapi import get_data_schema_for_widget + + inline_schema = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "score": {"type": "number"}, + }, + } + openapi = { + "components": {"schemas": {}}, + "paths": { + "/single": { + "get": { + "operationId": "single_op", + "responses": { + "200": { + "content": {"application/json": {"schema": inline_schema}} + } + }, + } + } + }, + } + out = get_data_schema_for_widget(openapi, "single_op", "/single") + assert out is inline_schema + + +def test_spec_proxy_inline_schema_produces_columnsdefs_end_to_end(): + """End-to-end: a spec-driven proxy app with an inline + Socrata-style response_schema produces a widget with + ``columnsDefs`` populated from the row's fields. Regression for + the ``Fertilizer Prices By Region`` empty-columnsDefs report. + """ + from openbb_platform_api.app.spec import build_app_from_spec + from openbb_platform_api.utils.widgets import build_json + + spec = { + "version": 5, + "base_url": "https://example.com", + "api_prefix": "", + "commands": { + "fertilizer.prices": { + "url_path": "/resource/abcd.json", + "method": "get", + "description": "Fertilizer prices.", + "providers": [], + "parameters": [ + { + "name": "region", + "in": "query", + "type": "string", + "is_list": False, + "required": False, + "default": None, + "choices": [], + "providers": [], + }, + ], + "request_body_schema": None, + "response_schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": {"type": "string", "format": "date"}, + "year": {"type": "integer"}, + "region": {"type": "string"}, + "price": {"type": "number"}, + }, + }, + } + }, + }, + } + }, + "routers": {}, + "reference": {}, + } + app = build_app_from_spec(spec) + out = build_json(app.openapi(), []) + assert len(out) == 1 + widget = next(iter(out.values())) + cols = widget["data"]["table"].get("columnsDefs") + assert cols, f"expected columnsDefs to be populated; got {cols}" + by_field = {c["field"]: c for c in cols} + # Date got the dateString cellDataType + descending sort default. + assert by_field["date"]["cellDataType"] == "dateString" + assert by_field["date"]["sort"] == "desc" + # Year is rendered as text to suppress ag-grid's locale comma. + assert by_field["year"]["cellDataType"] == "text" + # Other types preserved. + assert by_field["region"]["cellDataType"] == "text" + assert by_field["price"]["cellDataType"] == "number" + + +if __name__ == "__main__": + pytest.main() diff --git a/openbb_platform/extensions/platform_api/tests/utils/test_widgets.py b/openbb_platform/extensions/platform_api/tests/utils/test_widgets.py new file mode 100644 index 00000000000..4ed8d8c7aca --- /dev/null +++ b/openbb_platform/extensions/platform_api/tests/utils/test_widgets.py @@ -0,0 +1,1961 @@ +import json +from pathlib import Path + +import pytest + +from openbb_platform_api.response_models import OmniWidgetResponseModel +from openbb_platform_api.utils.widgets import build_json, modify_query_schema + + +# Load the mock OpenAPI JSON +@pytest.fixture(scope="module") +def mock_openapi_json(): + mock_openapi_path = Path(__file__).parent / "mock_openapi.json" + with open(mock_openapi_path) as file: + return json.load(file) + + +# Load the mock widgets JSON +@pytest.fixture(scope="module") +def mock_widgets_json(): + mock_widgets_path = Path(__file__).parent / "mock_widgets.json" + with open(mock_widgets_path) as file: + return json.load(file) + + +def test_omni_widget_response_model(): + # Create a sample OmniWidgetResponseModel instance + + test_table = [ + {"symbol": "AAPL", "price": 150.0, "volume": 1000000}, + {"symbol": "GOOGL", "price": 2800.0, "volume": 500000}, + ] + widget = OmniWidgetResponseModel( + content=test_table, + ) + # Assert the content and data_format fields + assert widget.content == test_table + assert widget.data_format == { + "data_type": "object", + "parse_as": "table", + } + assert not hasattr(widget, "parse_as") + + widget = OmniWidgetResponseModel( + content=test_table, + parse_as="text", + ) + # Assert the parse_as field takes precedence + assert widget.content == test_table + assert widget.data_format == { + "data_type": "object", + "parse_as": "text", + } + # Assert that the parse_as field is not sent at the root of the object. + assert not hasattr(widget, "parse_as") + + # Test with JSON string content + widget = OmniWidgetResponseModel( + content=json.dumps(test_table), + ) + + # Assert the content and data_format fields + assert isinstance(widget.content, list) + assert widget.data_format == { + "data_type": "object", + "parse_as": "table", + } + assert not hasattr(widget, "parse_as") + + assert "x-widget_config" in widget.schema_json() + + +@pytest.mark.parametrize( + "query_schema, provider_value, expected_result", + [ + ( + [ + { + "parameter_name": "provider", + "label": "Provider", + "description": "Source of the data.", + "optional": True, + "type": "text", + "available_providers": ["sec"], + "show": True, + }, + { + "parameter_name": "query", + "label": "Query", + "description": "Search query.", + "optional": True, + "value": "", + "options": {"sec": []}, + "multiple_items_allowed": {}, + "available_providers": ["sec"], + "type": "text", + "show": True, + }, + { + "parameter_name": "use_cache", + "label": "Use Cache", + "description": "Whether or not to use cache.", + "optional": True, + "value": True, + "options": {"sec": []}, + "multiple_items_allowed": {}, + "available_providers": ["sec"], + "type": "boolean", + "show": True, + }, + { + "parameter_name": "url", + "label": "Url", + "description": "Enter an optional URL path to fetch the next level.", + "optional": True, + "value": None, + "options": {"sec": []}, + "multiple_items_allowed": {}, + "available_providers": ["sec"], + "show": True, + }, + ], + "sec", + [ + { + "label": "Query", + "description": "Search query.", + "optional": True, + "value": "", + "type": "text", + "show": True, + "paramName": "query", + }, + { + "label": "Use Cache", + "description": "Whether or not to use cache.", + "optional": True, + "value": True, + "type": "boolean", + "show": True, + "paramName": "use_cache", + }, + { + "label": "URL", + "description": "Enter an optional URL path to fetch the next level.", + "optional": True, + "value": None, + "show": True, + "paramName": "url", + }, + {"paramName": "provider", "value": "sec", "show": False}, + ], + ), + ( + [ + { + "parameter_name": "provider", + "label": "Provider", + "description": "Source of the data.", + "optional": True, + "type": "text", + "available_providers": ["fred"], + "show": True, + }, + { + "parameter_name": "symbol", + "label": "Symbol", + "description": "Symbol to get data for.", + "optional": False, + "type": "text", + "multiple_items_allowed": {"fred": True}, + "show": True, + }, + { + "parameter_name": "start_date", + "label": "Start Date", + "description": "Start date of the data, in YYYY-MM-DD format.", + "optional": True, + "value": None, + "options": {"fred": []}, + "multiple_items_allowed": {}, + "available_providers": ["fred"], + "type": "date", + "show": True, + }, + { + "parameter_name": "end_date", + "label": "End Date", + "description": "End date of the data, in YYYY-MM-DD format.", + "optional": True, + "value": None, + "options": {"fred": []}, + "multiple_items_allowed": {}, + "available_providers": ["fred"], + "type": "date", + "show": True, + }, + { + "parameter_name": "limit", + "label": "Limit", + "description": "The number of data entries to return.", + "optional": True, + "value": 100000, + "options": {"fred": []}, + "multiple_items_allowed": {}, + "available_providers": ["fred"], + "type": "number", + "show": True, + }, + { + "parameter_name": "frequency", + "label": "Frequency", + "description": "Frequency aggregation to convert high frequency data to lower frequency." + + "\n " + + "\n None = No change" + + "\n " + + "\n a = Annual" + + "\n " + + "\n q = Quarterly" + + "\n " + + "\n m = Monthly" + + "\n " + + "\n w = Weekly" + + "\n " + + "\n d = Daily" + + "\n " + + "\n wef = Weekly, Ending Friday" + + "\n " + + "\n weth = Weekly, Ending Thursday" + + "\n " + + "\n wew = Weekly, Ending Wednesday" + + "\n " + + "\n wetu = Weekly, Ending Tuesday" + + "\n " + + "\n wem = Weekly, Ending Monday" + + "\n " + + "\n wesu = Weekly, Ending Sunday" + + "\n " + + "\n wesa = Weekly, Ending Saturday" + + "\n " + + "\n bwew = Biweekly, Ending Wednesday" + + "\n " + + "\n bwem = Biweekly, Ending Monday", + "optional": True, + "value": None, + "options": { + "fred": [ + {"label": "a", "value": "a"}, + {"label": "q", "value": "q"}, + {"label": "m", "value": "m"}, + {"label": "w", "value": "w"}, + {"label": "d", "value": "d"}, + {"label": "wef", "value": "wef"}, + {"label": "weth", "value": "weth"}, + {"label": "wew", "value": "wew"}, + {"label": "wetu", "value": "wetu"}, + {"label": "wem", "value": "wem"}, + {"label": "wesu", "value": "wesu"}, + {"label": "wesa", "value": "wesa"}, + {"label": "bwew", "value": "bwew"}, + {"label": "bwem", "value": "bwem"}, + ] + }, + "multiple_items_allowed": {}, + "available_providers": ["fred"], + "show": True, + }, + { + "parameter_name": "aggregation_method", + "label": "Aggregation Method", + "description": "A key that indicates the aggregation method used for frequency aggregation." + + "\n This parameter has no affect if the frequency parameter is not set." + + "\n " + + "\n avg = Average" + + "\n " + + "\n sum = Sum" + + "\n " + + "\n eop = End of Period", + "optional": True, + "value": "eop", + "options": { + "fred": [ + {"label": "avg", "value": "avg"}, + {"label": "sum", "value": "sum"}, + {"label": "eop", "value": "eop"}, + ] + }, + "multiple_items_allowed": {}, + "available_providers": ["fred"], + "show": True, + }, + { + "parameter_name": "transform", + "label": "Transform", + "description": "Transformation type" + + "\n " + + "\n None = No transformation" + + "\n " + + "\n chg = Change" + + "\n " + + "\n ch1 = Change from Year Ago" + + "\n " + + "\n pch = Percent Change" + + "\n " + + "\n pc1 = Percent Change from Year Ago" + + "\n " + + "\n pca = Compounded Annual Rate of Change" + + "\n " + + "\n cch = Continuously Compounded Rate of Change" + + "\n " + + "\n cca = Continuously Compounded Annual Rate of Change" + + "\n " + + "\n log = Natural Log", + "optional": True, + "value": None, + "options": { + "fred": [ + {"label": "chg", "value": "chg"}, + {"label": "ch1", "value": "ch1"}, + {"label": "pch", "value": "pch"}, + {"label": "pc1", "value": "pc1"}, + {"label": "pca", "value": "pca"}, + {"label": "cch", "value": "cch"}, + {"label": "cca", "value": "cca"}, + {"label": "log", "value": "log"}, + ] + }, + "multiple_items_allowed": {}, + "available_providers": ["fred"], + "show": True, + }, + ], + "fred", + [ + { + "label": "Symbol", + "description": "Symbol to get data for. Multiple comma separated items allowed.", + "optional": False, + "type": "text", + "show": True, + "multiSelect": True, + "paramName": "symbol", + "multiple": True, + "style": { + "popupWidth": 400, + }, + }, + { + "label": "Start Date", + "description": "Start date of the data, in YYYY-MM-DD format.", + "optional": True, + "value": None, + "type": "date", + "show": True, + "paramName": "start_date", + }, + { + "label": "End Date", + "description": "End date of the data, in YYYY-MM-DD format.", + "optional": True, + "value": None, + "type": "date", + "show": True, + "paramName": "end_date", + }, + { + "label": "Limit", + "description": "The number of data entries to return.", + "optional": True, + "value": 100000, + "type": "number", + "show": True, + "paramName": "limit", + }, + { + "label": "Frequency", + "description": "Frequency aggregation to convert high frequency data to lower frequency." + + "\n " + + "\n None = No change" + + "\n " + + "\n a = Annual" + + "\n " + + "\n q = Quarterly" + + "\n " + + "\n m = Monthly" + + "\n " + + "\n w = Weekly" + + "\n " + + "\n d = Daily" + + "\n " + + "\n wef = Weekly, Ending Friday" + + "\n " + + "\n weth = Weekly, Ending Thursday" + + "\n " + + "\n wew = Weekly, Ending Wednesday" + + "\n " + + "\n wetu = Weekly, Ending Tuesday" + + "\n " + + "\n wem = Weekly, Ending Monday" + + "\n " + + "\n wesu = Weekly, Ending Sunday" + + "\n " + + "\n wesa = Weekly, Ending Saturday" + + "\n " + + "\n bwew = Biweekly, Ending Wednesday" + + "\n " + + "\n bwem = Biweekly, Ending Monday", + "optional": True, + "value": None, + "show": True, + "options": [ + {"label": "a", "value": "a"}, + {"label": "q", "value": "q"}, + {"label": "m", "value": "m"}, + {"label": "w", "value": "w"}, + {"label": "d", "value": "d"}, + {"label": "wef", "value": "wef"}, + {"label": "weth", "value": "weth"}, + {"label": "wew", "value": "wew"}, + {"label": "wetu", "value": "wetu"}, + {"label": "wem", "value": "wem"}, + {"label": "wesu", "value": "wesu"}, + {"label": "wesa", "value": "wesa"}, + {"label": "bwew", "value": "bwew"}, + {"label": "bwem", "value": "bwem"}, + ], + "type": "text", + "paramName": "frequency", + }, + { + "label": "Aggregation Method", + "description": "A key that indicates the aggregation method used for frequency aggregation." + + "\n This parameter has no affect if the frequency parameter is not set." + + "\n " + + "\n avg = Average" + + "\n " + + "\n sum = Sum" + + "\n " + + "\n eop = End of Period", + "optional": True, + "value": "eop", + "show": True, + "options": [ + {"label": "avg", "value": "avg"}, + {"label": "sum", "value": "sum"}, + {"label": "eop", "value": "eop"}, + ], + "type": "text", + "paramName": "aggregation_method", + }, + { + "label": "Transform", + "description": "Transformation type" + + "\n " + + "\n None = No transformation" + + "\n " + + "\n chg = Change" + + "\n " + + "\n ch1 = Change from Year Ago" + + "\n " + + "\n pch = Percent Change" + + "\n " + + "\n pc1 = Percent Change from Year Ago" + + "\n " + + "\n pca = Compounded Annual Rate of Change" + + "\n " + + "\n cch = Continuously Compounded Rate of Change" + + "\n " + + "\n cca = Continuously Compounded Annual Rate of Change" + + "\n " + + "\n log = Natural Log", + "optional": True, + "value": None, + "show": True, + "options": [ + {"label": "chg", "value": "chg"}, + {"label": "ch1", "value": "ch1"}, + {"label": "pch", "value": "pch"}, + {"label": "pc1", "value": "pc1"}, + {"label": "pca", "value": "pca"}, + {"label": "cch", "value": "cch"}, + {"label": "cca", "value": "cca"}, + {"label": "log", "value": "log"}, + ], + "type": "text", + "paramName": "transform", + }, + {"paramName": "provider", "value": "fred", "show": False}, + ], + ), + ], +) +def test_modify_query_schema(query_schema, provider_value, expected_result): + result = modify_query_schema(query_schema, provider_value) + assert result == expected_result + + +def test_build_json(mock_openapi_json, mock_widgets_json): + result = build_json(openapi=mock_openapi_json, widget_exclude_filter=[]) + assert len(result) == len( + mock_openapi_json["paths"] + ) # +1 for the duplicate path with a chart. -1 for the form widget over 2 paths. + assert result == mock_widgets_json + + +# --------------------------------------------------------------------------- +# deep_merge_configs (covers the inner merge_values + merge_lists helpers) +# --------------------------------------------------------------------------- + + +def test_deep_merge_configs_adds_new_keys_to_base(): + """Keys present only in ``update`` get added to ``base`` verbatim.""" + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"a": 1} + out = deep_merge_configs(base, {"b": 2}) + assert out == {"a": 1, "b": 2} + + +def test_deep_merge_configs_overwrites_with_explicit_empty_values(): + """Update value of ``[]`` / ``{}`` / ``None`` overwrites the base — + explicit "clear this" semantic. + """ + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"a": [1, 2, 3], "b": {"x": 1}, "c": "value"} + out = deep_merge_configs(base, {"a": [], "b": {}, "c": None}) + assert out == {"a": [], "b": {}, "c": None} + + +def test_deep_merge_configs_recurses_into_nested_dicts(): + """Nested dicts get merged, not replaced.""" + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"outer": {"keep": 1, "shared": 2}} + out = deep_merge_configs(base, {"outer": {"shared": 99, "new": 3}}) + assert out == {"outer": {"keep": 1, "shared": 99, "new": 3}} + + +def test_deep_merge_configs_replaces_scalar_when_types_differ(): + """When types don't match, update wins.""" + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"x": "scalar"} + out = deep_merge_configs(base, {"x": [1, 2]}) + assert out == {"x": [1, 2]} + + +def test_deep_merge_configs_string_match_keys_normalized_to_tuple(): + """``match_keys="paramName"`` is normalized to a one-tuple.""" + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"items": [{"paramName": "a", "value": 1}]} + update = {"items": [{"paramName": "a", "value": 99}]} + out = deep_merge_configs(base, update, match_keys="paramName") + assert out["items"] == [{"paramName": "a", "value": 99}] + + +def test_deep_merge_configs_merges_lists_by_match_key(): + """Lists of dicts merge by ``paramName`` / ``field`` match key.""" + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"items": [{"paramName": "a", "v": 1}, {"paramName": "b", "v": 2}]} + update = { + "items": [ + {"paramName": "a", "v": 99}, + {"paramName": "c", "v": 3}, + ] + } + out = deep_merge_configs(base, update) + items = out["items"] + paramnames = [i["paramName"] for i in items] + assert paramnames == ["a", "b", "c"] + assert items[0]["v"] == 99 + + +def test_deep_merge_configs_preserves_unmatched_list_dicts(): + """Base-list dicts whose match-key isn't in update list are preserved.""" + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"items": [{"paramName": "kept", "v": 1}]} + update = {"items": [{"paramName": "new", "v": 2}]} + out = deep_merge_configs(base, update) + paramnames = [i["paramName"] for i in out["items"]] + assert "kept" in paramnames + assert "new" in paramnames + + +def test_deep_merge_configs_preserves_base_list_with_no_match_key(): + """Base-list dict with no recognized match key → preserved unchanged.""" + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"items": [{"unrelated_key": "x"}]} + update = {"items": [{"paramName": "new"}]} + out = deep_merge_configs(base, update) + items = out["items"] + assert {"unrelated_key": "x"} in items + assert {"paramName": "new"} in items + + +def test_deep_merge_configs_recurses_into_nested_lists(): + """Nested list-of-list merges recursively via ``merge_lists``. + + ``deep_merge_configs`` doesn't deduplicate at the outer level — the + inner nested list gets recursively merged with the matching update, + and the unmatched outer-list items from the update are appended. + """ + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"items": [[{"paramName": "a", "v": 1}]]} + update = {"items": [[{"paramName": "a", "v": 99}]]} + out = deep_merge_configs(base, update) + # Inner list gets the merge applied (v: 99 wins). + assert out["items"][0] == [{"paramName": "a", "v": 99}] + + +def test_deep_merge_configs_keeps_nested_list_when_no_update_list(): + """Base list-of-list with no matching update list survives.""" + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"items": [[1, 2, 3]]} + update = {"items": [{"paramName": "added"}]} + out = deep_merge_configs(base, update) + assert [1, 2, 3] in out["items"] + + +def test_deep_merge_configs_keeps_non_dict_non_list_base_items(): + """Scalars in the base list pass through unchanged.""" + from openbb_platform_api.utils.widgets import deep_merge_configs + + base = {"items": ["string-item", 42, None]} + update = {"items": [{"paramName": "added"}]} + out = deep_merge_configs(base, update) + for item in ("string-item", 42, None): + assert item in out["items"] + assert {"paramName": "added"} in out["items"] + + +# --------------------------------------------------------------------------- +# get_form_input_paths +# --------------------------------------------------------------------------- + + +def test_get_form_input_paths_extracts_form_endpoints(): + """``widget_config.form_endpoint`` → mapped from route to endpoint.""" + from openbb_platform_api.utils.widgets import get_form_input_paths + + openapi = { + "paths": { + "/with_form": {"get": {"widget_config": {"form_endpoint": "/submit"}}}, + "/no_form": {"get": {"widget_config": {}}}, + "/no_widget_config": {"get": {}}, + } + } + out = get_form_input_paths(openapi) + assert out == {"/with_form": "/submit"} + + +# --------------------------------------------------------------------------- +# modify_query_schema branches +# --------------------------------------------------------------------------- + + +def test_modify_query_schema_returns_empty_for_empty_input(): + """Empty input → empty list.""" + out = modify_query_schema([], "fmp") + assert out == [] + + +def test_modify_query_schema_skips_provider_param(): + """Items with ``parameter_name="provider"`` are dropped.""" + out = modify_query_schema( + [ + {"parameter_name": "provider", "options": ["fmp", "polygon"]}, + {"parameter_name": "symbol", "type": "text"}, + ], + "fmp", + ) + param_names = [i.get("paramName") for i in out] + # Original ``provider`` skipped; sentinel appended at the end. + assert "provider" in param_names + assert "symbol" in param_names + + +def test_modify_query_schema_skips_unavailable_providers(): + """A param tagged for a different provider is dropped.""" + out = modify_query_schema( + [ + { + "parameter_name": "tier", + "available_providers": ["polygon"], + "type": "text", + }, + {"parameter_name": "symbol", "type": "text"}, + ], + "fmp", + ) + names = [i.get("paramName") for i in out] + assert "tier" not in names + assert "symbol" in names + + +def test_modify_query_schema_appends_multi_items_hint_to_description(): + """Multi-items provider → appended description, multiSelect=True.""" + out = modify_query_schema( + [ + { + "parameter_name": "symbol", + "description": "Stock symbol.", + "type": "text", + "multiple_items_allowed": {"fmp": True}, + } + ], + "fmp", + ) + item = out[0] + assert "Multiple comma separated items allowed" in item["description"] + assert item["multiSelect"] is True + + +def test_modify_query_schema_picks_provider_specific_options(): + """Provider-keyed options → use that provider's list.""" + out = modify_query_schema( + [ + { + "parameter_name": "tier", + "type": "text", + "options": {"fmp": ["a", "b"], "polygon": ["x", "y"]}, + } + ], + "fmp", + ) + assert out[0]["options"] == ["a", "b"] + + +def test_modify_query_schema_falls_back_to_other_options(): + """Single ``other`` key → that's the option set used.""" + out = modify_query_schema( + [ + { + "parameter_name": "tier", + "type": "text", + "options": {"other": ["x", "y"]}, + } + ], + "polygon", + ) + assert out[0]["options"] == ["x", "y"] + + +def test_modify_query_schema_options_as_list_keyed_by_provider(): + """Bare-list ``options`` value → wrapped under provider key.""" + out = modify_query_schema( + [ + { + "parameter_name": "tier", + "type": "text", + "options": ["a", "b"], + } + ], + "fmp", + ) + assert out[0]["options"] == ["a", "b"] + + +def test_modify_query_schema_uppercase_label_for_known_acronyms(): + """``url`` / ``cik`` / etc. get auto-uppercased labels.""" + out = modify_query_schema( + [{"parameter_name": "cik", "type": "text"}], + "fmp", + ) + assert out[0]["label"] == "CIK" + + +def test_modify_query_schema_x_widget_config_exclude_drops_param(): + """``exclude=True`` in ``x-widget_config`` → param dropped.""" + out = modify_query_schema( + [ + { + "parameter_name": "symbol", + "type": "text", + "x-widget_config": {"fmp": {"exclude": True}}, + } + ], + "fmp", + ) + names = [i.get("paramName") for i in out] + assert "symbol" not in names + + +def test_modify_query_schema_x_widget_config_merges_into_param(): + """Provider-specific ``x-widget_config`` is deep-merged.""" + out = modify_query_schema( + [ + { + "parameter_name": "symbol", + "type": "text", + "x-widget_config": { + "fmp": {"label": "Custom Symbol", "description": "FMP override"} + }, + } + ], + "fmp", + ) + item = out[0] + assert item["label"] == "Custom Symbol" + assert item["description"] == "FMP override" + + +def test_modify_query_schema_multi_select_text_no_options_gets_popup_style(): + """MultiSelect + text + no options + no semicolon → popup style.""" + out = modify_query_schema( + [ + { + "parameter_name": "tags", + "type": "text", + "multiSelect": True, + "description": "Choose tags.", + } + ], + "fmp", + ) + item = out[0] + assert item["multiple"] is True + assert "popupWidth" in item.get("style", {}) + + +def test_modify_query_schema_custom_provider_skips_provider_sentinel(): + """``provider="custom"`` → no synthetic provider entry appended.""" + out = modify_query_schema([{"parameter_name": "symbol", "type": "text"}], "custom") + names = [i.get("paramName") for i in out] + assert "provider" not in names + assert "symbol" in names + + +# --------------------------------------------------------------------------- +# build_json edge-case branches +# --------------------------------------------------------------------------- + + +def _minimal_openapi( + paths: dict, + schemas: dict | None = None, +) -> dict: + """Wrap ``paths`` (and optional ``components.schemas``) in a minimum + openapi document the launcher's ``build_json`` accepts. + """ + return { + "paths": paths, + "components": {"schemas": schemas or {}}, + } + + +def _basic_get_route( + widget_config: dict | None = None, response_schema: dict | None = None +) -> dict: + """Build a single GET route definition shaped the way ``build_json`` + walks: parameters, response schema, optional widget_config. + """ + route = { + "operationId": "test_op", + "parameters": [], + "summary": "Summary", + "description": "Description", + "responses": { + "200": { + "content": { + "application/json": { + "schema": response_schema or {"type": "object"} + } + } + } + }, + } + if widget_config: + route["widget_config"] = widget_config + return {"get": route} + + +def test_build_json_empty_openapi_returns_empty_dict(): + """No openapi → no widgets, fast path.""" + assert build_json({}, []) == {} + + +def test_build_json_skips_widget_with_widget_id_in_exclude_filter(): + """When the derived widget id appears in ``widget_exclude_filter``, + the widget is dropped before any further processing. + """ + openapi = _minimal_openapi({"/api/v1/equity_quote": _basic_get_route()}) + out = build_json(openapi, ["equity_quote"]) + assert out == {} + + +def test_build_json_skips_widget_marked_excluded_in_widget_config(): + """A widget whose own ``widget_config.exclude`` is ``True`` is + dropped — exercises line 312. + """ + openapi = _minimal_openapi( + {"/api/v1/foo_bar": _basic_get_route(widget_config={"exclude": True})} + ) + out = build_json(openapi, []) + assert out == {} + + +def test_build_json_skips_widget_when_starred_filter_matches_route(): + """A wildcard exclude (e.g. ``"/api/v1/equity/*"``) drops every + matching route — exercises lines 254-256 (split-out starred list) + and 285-293 (the per-route skip loop). + """ + openapi = _minimal_openapi({"/api/v1/equity/quote": _basic_get_route()}) + out = build_json(openapi, ["/api/v1/equity/*"]) + assert out == {} + + +def test_build_json_skips_event_stream_responses(): + """A route whose 2xx response is ``text/event-stream`` cannot render + as a widget and is skipped — exercises the SSE guard (line 295). + """ + openapi = _minimal_openapi( + { + "/api/v1/stream": { + "get": { + "operationId": "stream_op", + "parameters": [], + "summary": "S", + "description": "D", + "responses": { + "200": { + "content": { + "text/event-stream": {"schema": {"type": "string"}} + } + } + }, + } + } + } + ) + assert build_json(openapi, []) == {} + + +def test_build_json_post_route_appends_widget_id_to_exclude_filter(): + """POST routes whose widget type isn't ssrm/omni/multi-file get + auto-excluded from later rebuilds — exercises line 704. + """ + openapi = _minimal_openapi( + { + "/api/v1/something": { + "post": { + "operationId": "post_op", + "summary": "S", + "description": "D", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"type": "object", "properties": {}} + } + } + }, + } + } + } + ) + exclude_filter: list = [] + out = build_json(openapi, exclude_filter) + # POST builder adds the widget id to the exclude filter, so it + # doesn't appear in the result. + assert out == {} + assert any("something" in item for item in exclude_filter) + + +def test_build_json_chart_widget_emitted_when_chart_param_present(): + """A GET route whose parameters include ``chart`` gets two widgets: + one regular table widget and one chart variant. + """ + openapi = _minimal_openapi( + { + "/api/v1/equity_chart": { + "get": { + "operationId": "equity_chart_op", + "summary": "S", + "description": "D", + "parameters": [ + { + "name": "chart", + "in": "query", + "schema": {"type": "boolean"}, + } + ], + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + } + } + } + ) + out = build_json(openapi, []) + # Both base + chart variant present (widget id ends with ``_chart``). + assert any(k.endswith("_chart") for k in out) + + +def test_build_json_metric_widget_uses_compact_grid(): + """``widget_config.type="metric"`` rewrites ``gridData`` from the + default 40×15 to 4×5 — exercises lines 670-676. + """ + openapi = _minimal_openapi( + {"/api/v1/some_metric": _basic_get_route(widget_config={"type": "metric"})} + ) + out = build_json(openapi, []) + metric_widget = next(iter(out.values())) + assert metric_widget["gridData"]["w"] == 4 + assert metric_widget["gridData"]["h"] == 5 + + +def test_build_json_pdf_widget_uses_pdf_grid_defaults(): + """``widget_config.type="pdf"`` rewrites ``gridData`` to 20×25 — + exercises lines 683-689. + """ + openapi = _minimal_openapi( + {"/api/v1/some_pdf": _basic_get_route(widget_config={"type": "pdf"})} + ) + out = build_json(openapi, []) + pdf_widget = next(iter(out.values())) + assert pdf_widget["gridData"]["w"] == 20 + assert pdf_widget["gridData"]["h"] == 25 + + +def test_build_json_widget_config_source_override(): + """``widget_config.source = [...]`` overrides the auto-derived + provider list — exercises line 697. + """ + openapi = _minimal_openapi( + { + "/api/v1/something": _basic_get_route( + widget_config={"source": ["custom-source"]} + ) + } + ) + out = build_json(openapi, []) + widget = next(iter(out.values())) + assert widget["source"] == ["custom-source"] + + +def test_build_json_post_with_request_body_ref_params(): + """A POST whose ``requestBody`` references a schema with ``$ref`` + properties drives the ``param_names`` extraction path — each ref + becomes a separate ``post_query_schema_for_widget`` call. + """ + openapi = { + "components": { + "schemas": { + "PostInput": { + "title": "PostInput", + "properties": { + "data": {"$ref": "#/components/schemas/InputData"}, + }, + }, + "InputData": { + "title": "InputData", + "type": "object", + "properties": {"x": {"type": "number"}}, + }, + } + }, + "paths": { + "/api/v1/something": { + "post": { + "operationId": "post_op", + "summary": "S", + "description": "D", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/PostInput"} + } + } + }, + } + } + }, + } + # POST routes get added to the exclude filter automatically; we just + # need the call not to crash and to handle the ref-following branch. + exclude_filter: list = [] + build_json(openapi, exclude_filter) + assert any("something" in item for item in exclude_filter) + + +def test_build_json_form_endpoint_emits_form_widget(): + """A GET route whose ``widget_config.form_endpoint`` points at a + POST route emits a ``form`` widget with a ``submit`` button auto- + appended. + """ + openapi = { + "components": { + "schemas": { + "FormInput": { + "title": "FormInput", + "type": "object", + "properties": { + "field_a": {"$ref": "#/components/schemas/FieldA"}, + }, + }, + "FieldA": { + "title": "FieldA", + "type": "object", + "properties": {"value": {"type": "number"}}, + }, + } + }, + "paths": { + "/api/v1/data_widget": { + "get": { + "operationId": "data_widget_op", + "parameters": [], + "summary": "S", + "description": "D", + "widget_config": {"form_endpoint": "/api/v1/data_widget_submit"}, + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + } + }, + "/api/v1/data_widget_submit": { + "post": { + "operationId": "data_widget_submit_op", + "summary": "Submit", + "description": "Submit data", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/FormInput"} + } + } + }, + } + }, + }, + } + out = build_json(openapi, []) + # The data_widget gets a 'form' param added with the submit button. + widget = next(iter(out.values())) + form_param = next( + (p for p in widget.get("params", []) if p.get("type") == "form"), None + ) + assert form_param is not None + + +def test_build_json_form_endpoint_no_params_with_var_schema_widget_config(): + """A form endpoint whose ``requestBody`` schema has NO ``$ref`` + properties (just a plain object) takes the no-params branch. + The model-level ``x-widget_config`` gets merged into the form_params + and ``$.``-prefixed keys are lifted to the widget root. + """ + openapi = { + "components": { + "schemas": { + "FormSchema": { + "title": "Form Schema", + "description": "Form description", + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + "x-widget_config": { + "$.runButton": True, + "label": "Custom Form Label", + }, + } + } + }, + "paths": { + "/api/v1/data_widget": { + "get": { + "operationId": "data_widget_op", + "parameters": [], + "summary": "S", + "description": "D", + "widget_config": {"form_endpoint": "/api/v1/data_widget_submit"}, + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + } + }, + "/api/v1/data_widget_submit": { + "post": { + "operationId": "data_widget_submit_op", + "summary": "Submit", + "description": "Submit data", + "widget_config": { + "$.refetchInterval": 30, + "type": "form", + }, + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/FormSchema"} + } + } + }, + } + }, + }, + } + out = build_json(openapi, []) + widget = next(iter(out.values())) + # ``$.``-prefixed keys lifted to widget root from both var_schema + # and form_route's widget_config. + assert widget.get("runButton") is True + assert widget.get("refetchInterval") == 30 + # Non-prefixed keys merge into form_params. + form_param = next((p for p in widget["params"] if p.get("type") == "form"), None) + assert form_param is not None + assert form_param.get("label") == "Custom Form Label" + + +def test_build_json_post_route_with_var_schema_widget_config_dollar_keys(): + """A POST route (no form_endpoint) whose request-body schema has + ``x-widget_config`` with ``$.``-prefixed keys lifts them to the + widget config — exercises lines 538-557. + """ + openapi = { + "components": { + "schemas": { + "PostSchema": { + "title": "Post Schema", + "type": "object", + "properties": {"x": {"type": "number"}}, + "x-widget_config": { + "$.refetchInterval": 60, + "$.gridData": {"w": 30, "h": 10}, + }, + } + } + }, + "paths": { + "/api/v1/post_widget": { + "post": { + "operationId": "post_widget_op", + "summary": "S", + "description": "D", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/PostSchema"} + } + } + }, + } + } + }, + } + # POST routes get added to exclude_filter; capture the side-effect. + exclude_filter: list = [] + build_json(openapi, exclude_filter) + # Side effect confirmed (POST route excluded) — we're really + # exercising the var_schema processing code path, even though the + # output is empty. + assert any("post_widget" in item for item in exclude_filter) + + +def test_build_json_data_widget_config_extracts_x_widget_keys(): + """When the data schema's ``x-widget_config`` contains ``$.``-prefixed + keys, those land at the widget root (not inside ``data``). + Exercises lines 639-657. + """ + openapi = { + "components": { + "schemas": { + "OBBject_RootedData_": { + "title": "OBBject_RootedData_", + "properties": { + "results": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/RootedData" + }, + "type": "array", + }, + {"type": "null"}, + ] + } + }, + }, + "RootedData": { + "title": "RootedData", + "properties": {"value": {"type": "number"}}, + "x-widget_config": { + "$.runButton": True, + "$.refetchInterval": 60, + }, + }, + } + }, + "paths": { + "/api/v1/rooted": { + "get": { + "operationId": "rooted_op", + "parameters": [], + "summary": "S", + "description": "D", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OBBject_RootedData_" + } + } + } + } + }, + } + } + }, + } + out = build_json(openapi, []) + widget = next(iter(out.values())) + # ``$.runButton`` lifted to root. + assert widget.get("runButton") is True + assert widget.get("refetchInterval") == 60 + + +# --------------------------------------------------------------------------- +# SSRM auto-promotion in build_json +# --------------------------------------------------------------------------- + + +def _ssrm_post_openapi( + *, + request_schema: str = "AgGridRowsRequest", + response_schema: str | None = "AgGridRowsResponse_FooRow_", + schemas: dict | None = None, + widget_config: dict | None = None, +) -> dict: + """Build an OpenAPI doc for a POST route shaped as an ag-grid SSRM + endpoint — body $ref to ``request_schema``, response $ref to + ``response_schema``. + """ + default_schemas = { + "AgGridRowsRequest": {"title": "AgGridRowsRequest"}, + "FooRow": { + "title": "FooRow", + "properties": { + "symbol": {"type": "string"}, + "price": {"type": "number"}, + "as_of_date": {"type": "string", "format": "date"}, + }, + }, + "AgGridRowsResponse_FooRow_": { + "title": "AgGridRowsResponse[FooRow]", + "properties": { + "rowData": { + "type": "array", + "items": {"$ref": "#/components/schemas/FooRow"}, + }, + "rowCount": {"type": "integer"}, + }, + }, + } + schemas = {**default_schemas, **(schemas or {})} + response_block: dict = ( + {"$ref": f"#/components/schemas/{response_schema}"} + if response_schema + else {"type": "object"} + ) + op: dict = { + "operationId": "ssrm_op", + "summary": "SSRM", + "description": "Server-side row model endpoint", + "responses": { + "200": {"content": {"application/json": {"schema": response_block}}} + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": f"#/components/schemas/{request_schema}"} + } + } + }, + } + if widget_config is not None: + op["widget_config"] = widget_config + return { + "components": {"schemas": schemas}, + "paths": {"/api/v1/ssrm/foo": {"post": op}}, + } + + +def test_build_json_ssrm_auto_promotes_type_and_runbutton(): + """A POST whose body is ``AgGridRowsRequest`` and response is + ``AgGridRowsResponse_FooRow_`` is auto-promoted: ``type`` becomes + ``ssrm_table``, ``runButton`` defaults to True, ``dataKey`` stays + empty (no OBBject envelope), and the route survives the POST + exclusion filter that drops every other POST widget. + """ + openapi = _ssrm_post_openapi() + exclude_filter: list = [] + out = build_json(openapi, exclude_filter) + # Exactly one widget surfaces — the SSRM table itself. + assert len(out) == 1 + widget = next(iter(out.values())) + assert widget["type"] == "ssrm_table" + assert widget["runButton"] is True + assert widget["data"]["dataKey"] == "" + # And the POST exclusion (line 699) didn't filter it. + assert widget["widgetId"] not in exclude_filter + + +def test_build_json_ssrm_request_only_signal_is_sufficient(): + """Body alone (``$ref`` to ``AgGridRowsRequest``) is enough — even + when the response schema doesn't match the canonical SSRM shape. + """ + openapi = _ssrm_post_openapi(response_schema=None) + out = build_json(openapi, []) + assert len(out) == 1 + assert next(iter(out.values()))["type"] == "ssrm_table" + + +def test_build_json_ssrm_response_only_signal_is_sufficient(): + """Response alone (``rowData`` + ``rowCount``) is enough — body + can be any other type. + """ + openapi = _ssrm_post_openapi( + request_schema="UnrelatedBody", + schemas={"UnrelatedBody": {"title": "UnrelatedBody"}}, + ) + out = build_json(openapi, []) + assert len(out) == 1 + assert next(iter(out.values()))["type"] == "ssrm_table" + + +def test_build_json_ssrm_subclass_request_body_still_promoted(): + """``AgGridRowsRequest`` subclasses (allOf chain in OpenAPI) are + detected by walking the inheritance chain. + """ + openapi = _ssrm_post_openapi( + request_schema="ProviderRowsRequest", + schemas={ + "ProviderRowsRequest": { + "title": "ProviderRowsRequest", + "allOf": [{"$ref": "#/components/schemas/AgGridRowsRequest"}], + } + }, + ) + out = build_json(openapi, []) + assert next(iter(out.values()))["type"] == "ssrm_table" + + +def test_build_json_ssrm_explicit_runbutton_override_wins(): + """Author can opt out of the SSRM ``runButton: True`` default with + an explicit ``widget_config={"runButton": False}``. The deep-merge + after construction lets the explicit value win. + """ + openapi = _ssrm_post_openapi(widget_config={"runButton": False}) + out = build_json(openapi, []) + widget = next(iter(out.values())) + assert widget["type"] == "ssrm_table" + assert widget["runButton"] is False + + +def test_build_json_ssrm_columns_defs_derived_from_row_model(): + """The ``rowData[].FooRow`` properties drive the auto-generated + ``columnsDefs`` — so the existing per-column inferences (date + detection, pinning) flow through to SSRM tables for free. + """ + openapi = _ssrm_post_openapi() + out = build_json(openapi, []) + widget = next(iter(out.values())) + cols = widget["data"]["table"].get("columnsDefs", []) + assert cols, "expected columnsDefs to be auto-derived from FooRow" + by_field = {c["field"]: c for c in cols} + assert by_field["symbol"]["cellDataType"] == "text" + assert by_field["price"]["cellDataType"] == "number" + # ``format: "date"`` (no time component) → ``dateString`` so the + # value renders verbatim as YYYY-MM-DD instead of being parsed as + # a timestamp. + assert by_field["as_of_date"]["cellDataType"] == "dateString" + # ``symbol`` is in the hardcoded pinned-left set in + # data_schema_to_columns_defs. + assert by_field["symbol"].get("pinned") == "left" + + +def test_build_json_non_ssrm_post_route_unchanged(): + """A POST route that doesn't match either SSRM signal stays a + plain table widget and gets the standard POST-exclusion treatment. + Regression guard against the auto-promotion firing too eagerly. + """ + openapi = _minimal_openapi( + { + "/api/v1/regular_post": { + "post": { + "operationId": "regular_post_op", + "summary": "S", + "description": "D", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"type": "object", "properties": {}} + } + } + }, + } + } + } + ) + exclude_filter: list = [] + out = build_json(openapi, exclude_filter) + # Standard POST exclusion: not promoted, gets filtered out. + assert out == {} + assert any("regular_post" in item for item in exclude_filter) + + +# --------------------------------------------------------------------------- +# Coverage gaps in the ``$.``-prefixed widget_config hierarchy +# --------------------------------------------------------------------------- +# +# These branches drive how widget properties cascade across three levels: +# +# 1. **Param level** — a query param's ``x-widget_config`` can wipe a +# label that the known-acronym fallback (CIK, ISIN, ...) just +# assigned; a second fallback pass restores it. +# 2. **POST/form level** — ``$.``-prefixed keys on the request-body +# model (or on the form route's own ``widget_config``) lift to the +# widget root, with override semantics when the route's explicit +# ``widget_config`` already carries the same unprefixed key. +# 3. **Data-model level** — same lifting from the response model's +# ``x-widget_config``, with the same override behavior. +# +# All three layers feed into ``widget_config_dict`` and finally +# ``deep_merge_configs`` onto the base widget — so they have to be +# tested end-to-end with explicit overrides at each level to confirm +# the priority order. + + +def test_modify_query_schema_known_acronym_label_recovered_after_xwidget_wipe(): + """The second known-acronym pass at line 184-192 only fires when the + first pass set the label and a per-provider ``x-widget_config`` then + explicitly nulls it (``deep_merge_configs`` treats ``None`` as an + explicit empty value). The fallback re-uppercases ``cik`` → ``CIK`` + so the user never sees a blank label. + """ + out = modify_query_schema( + [ + { + "parameter_name": "cik", + "type": "text", + "x-widget_config": {"custom": {"label": None}}, + } + ], + "custom", + ) + item = next(p for p in out if p.get("paramName") == "cik") + assert item["label"] == "CIK" + + +def test_build_json_form_endpoint_with_params_skips_auto_submit_when_button_present(): + """When the form's input model already exposes a param of + ``type: "button"`` (via per-provider ``x-widget_config``), the + auto-submit append should be skipped — exercises the early-break + at lines 421-425. + """ + openapi = { + "components": { + "schemas": { + "FormInput": { + "title": "FormInput", + "type": "object", + "properties": { + "field_a": {"$ref": "#/components/schemas/FieldA"}, + }, + }, + "FieldA": { + "title": "FieldA", + "type": "object", + "properties": { + "name": {"type": "string"}, + # This property gets promoted to ``type: "button"`` + # via ``process_parameter``'s ``p.update(...)`` — + # ``post_query_schema_for_widget`` runs with + # ``providers=["Custom"]`` (capital, hardcoded at + # line 1167 of openapi.py), so the x-widget_config + # key MUST match that casing for the unwrap to fire. + "go": { + "type": "string", + "x-widget_config": {"Custom": {"type": "button"}}, + }, + }, + }, + } + }, + "paths": { + "/api/v1/data_widget": { + "get": { + "operationId": "data_widget_op", + "parameters": [], + "summary": "S", + "description": "D", + "widget_config": {"form_endpoint": "/api/v1/data_widget_submit"}, + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + } + }, + "/api/v1/data_widget_submit": { + "post": { + "operationId": "data_widget_submit_op", + "summary": "Submit", + "description": "Submit data", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/FormInput"} + } + } + }, + } + }, + }, + } + out = build_json(openapi, []) + widget = next(iter(out.values())) + form_param = next(p for p in widget["params"] if p.get("type") == "form") + button_params = [p for p in form_param["inputParams"] if p.get("type") == "button"] + # Exactly one button — the user-defined ``go``, no synthetic + # ``submit`` was appended. + assert len(button_params) == 1 + assert button_params[0]["paramName"] == "go" + + +def test_build_json_form_endpoint_with_params_var_schema_xwidget_merges_into_form_params(): + """When the request-body schema carries a model-level + ``x-widget_config`` (NO ``$.`` prefix), it deep-merges into the + ``form_params`` block — so authors can set form-level UI hints + (label, description, popup width) without touching the route + decorator. Exercises line 448. + """ + openapi = { + "components": { + "schemas": { + "FormInput": { + "title": "FormInput", + "type": "object", + "properties": { + "field_a": {"$ref": "#/components/schemas/FieldA"}, + }, + "x-widget_config": { + "label": "Custom Form Label", + "description": "Custom form description", + }, + }, + "FieldA": { + "title": "FieldA", + "type": "object", + "properties": {"value": {"type": "number"}}, + }, + } + }, + "paths": { + "/api/v1/data_widget": { + "get": { + "operationId": "data_widget_op", + "parameters": [], + "summary": "S", + "description": "D", + "widget_config": {"form_endpoint": "/api/v1/data_widget_submit"}, + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + } + }, + "/api/v1/data_widget_submit": { + "post": { + "operationId": "data_widget_submit_op", + "summary": "Submit", + "description": "Submit data", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/FormInput"} + } + } + }, + } + }, + }, + } + out = build_json(openapi, []) + widget = next(iter(out.values())) + form_param = next(p for p in widget["params"] if p.get("type") == "form") + # Non-prefixed keys on var_schema landed on the form_params block. + assert form_param["label"] == "Custom Form Label" + assert form_param["description"] == "Custom form description" + + +def test_build_json_form_endpoint_dollar_key_overrides_existing_widget_config_key(): + """When the form route's request-body schema lifts a ``$.``-prefixed + key whose unprefixed form is *already* in the GET route's explicit + ``widget_config``, the lifted value wins — exercises line 533. The + cascade order is: GET ``widget_config`` < lifted ``$.`` keys + (because the lifted assignment happens before the final + ``deep_merge_configs`` and the lifted keys go through + ``widget_config_dict.update``). + """ + openapi = { + "components": { + "schemas": { + "FormSchema": { + "title": "Form Schema", + "type": "object", + "properties": {"name": {"type": "string"}}, + "x-widget_config": {"$.runButton": True}, + } + } + }, + "paths": { + "/api/v1/data_widget": { + "get": { + "operationId": "data_widget_op", + "parameters": [], + "summary": "S", + "description": "D", + "widget_config": { + "form_endpoint": "/api/v1/data_widget_submit", + "runButton": False, + }, + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + } + }, + "/api/v1/data_widget_submit": { + "post": { + "operationId": "data_widget_submit_op", + "summary": "Submit", + "description": "Submit data", + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/FormSchema"} + } + } + }, + } + }, + }, + } + out = build_json(openapi, []) + widget = next(iter(out.values())) + # ``$.runButton: True`` lifted from the var_schema overrode the + # GET route's explicit ``runButton: False``. + assert widget["runButton"] is True + + +def test_build_json_post_dollar_key_overrides_existing_widget_config_key(): + """Same override semantics as the form-endpoint case, exercised on + a plain POST (no form_endpoint). The route's explicit + ``widget_config.runButton: False`` is overwritten by the + ``$.runButton: True`` on the request-body schema — exercises line + 554. + """ + openapi = { + "components": { + "schemas": { + "PostSchema": { + "title": "Post Schema", + "type": "object", + "properties": {"x": {"type": "number"}}, + "x-widget_config": {"$.runButton": True}, + } + } + }, + "paths": { + "/api/v1/post_widget": { + "post": { + "operationId": "post_widget_op", + "summary": "S", + "description": "D", + # Explicit ``runButton: False`` plus an SSRM-ish + # type so the widget escapes the POST exclusion + # and we can inspect the final config. + "widget_config": { + "type": "ssrm_table", + "runButton": False, + }, + "responses": { + "200": { + "content": { + "application/json": {"schema": {"type": "object"}} + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/PostSchema"} + } + } + }, + } + } + }, + } + out = build_json(openapi, []) + widget = next(iter(out.values())) + # ``$.runButton: True`` from the request schema overrode the + # explicit ``runButton: False`` on the route. + assert widget["runButton"] is True + + +def test_build_json_data_dollar_key_overrides_existing_widget_config_key(): + """Same override pattern at the data-model level: when the route + already declares ``runButton: False`` in ``widget_config`` and the + response's ``RootedData.x-widget_config`` lifts a ``$.runButton: + True``, the lifted value wins — exercises line 673. + """ + openapi = { + "components": { + "schemas": { + "OBBject_RootedData_": { + "title": "OBBject_RootedData_", + "properties": { + "results": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/RootedData" + }, + "type": "array", + }, + {"type": "null"}, + ] + } + }, + }, + "RootedData": { + "title": "RootedData", + "properties": {"value": {"type": "number"}}, + "x-widget_config": {"$.runButton": True}, + }, + } + }, + "paths": { + "/api/v1/rooted": { + "get": { + "operationId": "rooted_op", + "parameters": [], + "summary": "S", + "description": "D", + "widget_config": {"runButton": False}, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OBBject_RootedData_" + } + } + } + } + }, + } + } + }, + } + out = build_json(openapi, []) + widget = next(iter(out.values())) + # ``$.runButton: True`` from the response model overrode the + # explicit ``runButton: False`` on the route. + assert widget["runButton"] is True + + +if __name__ == "__main__": + pytest.main() diff --git a/openbb_platform/extensions/platform_api/uv.lock b/openbb_platform/extensions/platform_api/uv.lock new file mode 100644 index 00000000000..42aeda8f327 --- /dev/null +++ b/openbb_platform/extensions/platform_api/uv.lock @@ -0,0 +1,3163 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <4" +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", + "python_full_version >= '3.12' and python_full_version < '3.14'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/85/cebc47ee74d8b408749073a1a46c6fcba13d170dc8af7e61996c6c9394ac/aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b", size = 750547, upload-time = "2026-03-31T21:56:30.024Z" }, + { url = "https://files.pythonhosted.org/packages/05/98/afd308e35b9d3d8c9ec54c0918f1d722c86dc17ddfec272fcdbcce5a3124/aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5", size = 503535, upload-time = "2026-03-31T21:56:31.935Z" }, + { url = "https://files.pythonhosted.org/packages/6f/4d/926c183e06b09d5270a309eb50fbde7b09782bfd305dec1e800f329834fb/aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670", size = 497830, upload-time = "2026-03-31T21:56:33.654Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d6/f47d1c690f115a5c2a5e8938cce4a232a5be9aac5c5fb2647efcbbbda333/aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274", size = 1682474, upload-time = "2026-03-31T21:56:35.513Z" }, + { url = "https://files.pythonhosted.org/packages/01/44/056fd37b1bb52eac760303e5196acc74d9d546631b035704ae5927f7b4ac/aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a", size = 1655259, upload-time = "2026-03-31T21:56:37.843Z" }, + { url = "https://files.pythonhosted.org/packages/91/9f/78eb1a20c1c28ae02f6a3c0f4d7b0dcc66abce5290cadd53d78ce3084175/aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d", size = 1736204, upload-time = "2026-03-31T21:56:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/de/6c/d20d7de23f0b52b8c1d9e2033b2db1ac4dacbb470bb74c56de0f5f86bb4f/aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796", size = 1826198, upload-time = "2026-03-31T21:56:41.378Z" }, + { url = "https://files.pythonhosted.org/packages/2f/86/a6f3ff1fd795f49545a7c74b2c92f62729135d73e7e4055bf74da5a26c82/aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95", size = 1681329, upload-time = "2026-03-31T21:56:43.374Z" }, + { url = "https://files.pythonhosted.org/packages/fb/68/84cd3dab6b7b4f3e6fe9459a961acb142aaab846417f6e8905110d7027e5/aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5", size = 1560023, upload-time = "2026-03-31T21:56:45.031Z" }, + { url = "https://files.pythonhosted.org/packages/41/2c/db61b64b0249e30f954a65ab4cb4970ced57544b1de2e3c98ee5dc24165f/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a", size = 1652372, upload-time = "2026-03-31T21:56:47.075Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/e96988a6c982d047810c772e28c43c64c300c943b0ed5c1c0c4ce1e1027c/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73", size = 1662031, upload-time = "2026-03-31T21:56:48.835Z" }, + { url = "https://files.pythonhosted.org/packages/b7/26/a56feace81f3d347b4052403a9d03754a0ab23f7940780dada0849a38c92/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297", size = 1708118, upload-time = "2026-03-31T21:56:50.833Z" }, + { url = "https://files.pythonhosted.org/packages/78/6e/b6173a8ff03d01d5e1a694bc06764b5dad1df2d4ed8f0ceec12bb3277936/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074", size = 1548667, upload-time = "2026-03-31T21:56:52.81Z" }, + { url = "https://files.pythonhosted.org/packages/16/13/13296ffe2c132d888b3fe2c195c8b9c0c24c89c3fa5cc2c44464dc23b22e/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e", size = 1724490, upload-time = "2026-03-31T21:56:54.541Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1f1c287f4a79782ef36e5a6e62954c85343bc30470d862d30bd5f26c9fa2/aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7", size = 1667109, upload-time = "2026-03-31T21:56:56.21Z" }, + { url = "https://files.pythonhosted.org/packages/ef/42/8461a2aaf60a8f4ea4549a4056be36b904b0eb03d97ca9a8a2604681a500/aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9", size = 439478, upload-time = "2026-03-31T21:56:58.292Z" }, + { url = "https://files.pythonhosted.org/packages/e5/71/06956304cb5ee439dfe8d86e1b2e70088bd88ed1ced1f42fb29e5d855f0e/aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76", size = 462047, upload-time = "2026-03-31T21:57:00.257Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/a20c4ac64aeaef1679e25c9983573618ff765d7aa829fa2b84ae7573169e/aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6", size = 757513, upload-time = "2026-03-31T21:57:02.146Z" }, + { url = "https://files.pythonhosted.org/packages/75/0a/39fa6c6b179b53fcb3e4b3d2b6d6cad0180854eda17060c7218540102bef/aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d", size = 506748, upload-time = "2026-03-31T21:57:04.275Z" }, + { url = "https://files.pythonhosted.org/packages/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c", size = 501673, upload-time = "2026-03-31T21:57:06.208Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/3bc7525d7e2beaa11b309a70d48b0d3cfc3c2089ec6a7d0820d59c657053/aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb", size = 1763757, upload-time = "2026-03-31T21:57:07.882Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ab/e87744cf18f1bd78263aba24924d4953b41086bd3a31d22452378e9028a0/aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6", size = 1720152, upload-time = "2026-03-31T21:57:09.946Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f3/ed17a6f2d742af17b50bae2d152315ed1b164b07a5fd5cc1754d99e4dfa5/aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13", size = 1818010, upload-time = "2026-03-31T21:57:12.157Z" }, + { url = "https://files.pythonhosted.org/packages/53/06/ecbc63dc937192e2a5cb46df4d3edb21deb8225535818802f210a6ea5816/aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174", size = 1907251, upload-time = "2026-03-31T21:57:14.023Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc", size = 1759969, upload-time = "2026-03-31T21:57:16.146Z" }, + { url = "https://files.pythonhosted.org/packages/f6/78/a38f8c9105199dd3b9706745865a8a59d0041b6be0ca0cc4b2ccf1bab374/aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6", size = 1616871, upload-time = "2026-03-31T21:57:17.856Z" }, + { url = "https://files.pythonhosted.org/packages/6f/41/27392a61ead8ab38072105c71aa44ff891e71653fe53d576a7067da2b4e8/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49", size = 1739844, upload-time = "2026-03-31T21:57:19.679Z" }, + { url = "https://files.pythonhosted.org/packages/6e/55/5564e7ae26d94f3214250009a0b1c65a0c6af4bf88924ccb6fdab901de28/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8", size = 1731969, upload-time = "2026-03-31T21:57:22.006Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c5/705a3929149865fc941bcbdd1047b238e4a72bcb215a9b16b9d7a2e8d992/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d", size = 1795193, upload-time = "2026-03-31T21:57:24.256Z" }, + { url = "https://files.pythonhosted.org/packages/a6/19/edabed62f718d02cff7231ca0db4ef1c72504235bc467f7b67adb1679f48/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c", size = 1606477, upload-time = "2026-03-31T21:57:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/de/fc/76f80ef008675637d88d0b21584596dc27410a990b0918cb1e5776545b5b/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac", size = 1813198, upload-time = "2026-03-31T21:57:28.316Z" }, + { url = "https://files.pythonhosted.org/packages/e5/67/5b3ac26b80adb20ea541c487f73730dc8fa107d632c998f25bbbab98fcda/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3", size = 1752321, upload-time = "2026-03-31T21:57:30.549Z" }, + { url = "https://files.pythonhosted.org/packages/88/06/e4a2e49255ea23fa4feeb5ab092d90240d927c15e47b5b5c48dff5a9ce29/aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06", size = 439069, upload-time = "2026-03-31T21:57:32.388Z" }, + { url = "https://files.pythonhosted.org/packages/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8", size = 462859, upload-time = "2026-03-31T21:57:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/be/6f/353954c29e7dcce7cf00280a02c75f30e133c00793c7a2ed3776d7b2f426/aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9", size = 748876, upload-time = "2026-03-31T21:57:36.319Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1b/428a7c64687b3b2e9cd293186695affc0e1e54a445d0361743b231f11066/aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416", size = 499557, upload-time = "2026-03-31T21:57:38.236Z" }, + { url = "https://files.pythonhosted.org/packages/29/47/7be41556bfbb6917069d6a6634bb7dd5e163ba445b783a90d40f5ac7e3a7/aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2", size = 500258, upload-time = "2026-03-31T21:57:39.923Z" }, + { url = "https://files.pythonhosted.org/packages/67/84/c9ecc5828cb0b3695856c07c0a6817a99d51e2473400f705275a2b3d9239/aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4", size = 1749199, upload-time = "2026-03-31T21:57:41.938Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/3c6d610e66b495657622edb6ae7c7fd31b2e9086b4ec50b47897ad6042a9/aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9", size = 1721013, upload-time = "2026-03-31T21:57:43.904Z" }, + { url = "https://files.pythonhosted.org/packages/49/a0/24409c12217456df0bae7babe3b014e460b0b38a8e60753d6cb339f6556d/aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5", size = 1781501, upload-time = "2026-03-31T21:57:46.285Z" }, + { url = "https://files.pythonhosted.org/packages/98/9d/b65ec649adc5bccc008b0957a9a9c691070aeac4e41cea18559fef49958b/aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e", size = 1878981, upload-time = "2026-03-31T21:57:48.734Z" }, + { url = "https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1", size = 1767934, upload-time = "2026-03-31T21:57:51.171Z" }, + { url = "https://files.pythonhosted.org/packages/31/04/d3f8211f273356f158e3464e9e45484d3fb8c4ce5eb2f6fe9405c3273983/aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286", size = 1566671, upload-time = "2026-03-31T21:57:53.326Z" }, + { url = "https://files.pythonhosted.org/packages/41/db/073e4ebe00b78e2dfcacff734291651729a62953b48933d765dc513bf798/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9", size = 1705219, upload-time = "2026-03-31T21:57:55.385Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/7dfba71a2f9fd97b15c95c06819de7eb38113d2cdb6319669195a7d64270/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88", size = 1743049, upload-time = "2026-03-31T21:57:57.341Z" }, + { url = "https://files.pythonhosted.org/packages/18/71/901db0061e0f717d226386a7f471bb59b19566f2cae5f0d93874b017271f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3", size = 1749557, upload-time = "2026-03-31T21:57:59.626Z" }, + { url = "https://files.pythonhosted.org/packages/08/d5/41eebd16066e59cd43728fe74bce953d7402f2b4ddfdfef2c0e9f17ca274/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b", size = 1558931, upload-time = "2026-03-31T21:58:01.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/e6/4a799798bf05740e66c3a1161079bda7a3dd8e22ca392481d7a7f9af82a6/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe", size = 1774125, upload-time = "2026-03-31T21:58:04.007Z" }, + { url = "https://files.pythonhosted.org/packages/84/63/7749337c90f92bc2cb18f9560d67aa6258c7060d1397d21529b8004fcf6f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14", size = 1732427, upload-time = "2026-03-31T21:58:06.337Z" }, + { url = "https://files.pythonhosted.org/packages/98/de/cf2f44ff98d307e72fb97d5f5bbae3bfcb442f0ea9790c0bf5c5c2331404/aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3", size = 433534, upload-time = "2026-03-31T21:58:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ca/eadf6f9c8fa5e31d40993e3db153fb5ed0b11008ad5d9de98a95045bed84/aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1", size = 460446, upload-time = "2026-03-31T21:58:10.945Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/d76bf503005709e390122d34e15256b88f7008e246c4bdbe915cd4f1adce/aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61", size = 742930, upload-time = "2026-03-31T21:58:13.155Z" }, + { url = "https://files.pythonhosted.org/packages/57/00/4b7b70223deaebd9bb85984d01a764b0d7bd6526fcdc73cca83bcbe7243e/aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832", size = 496927, upload-time = "2026-03-31T21:58:15.073Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9", size = 497141, upload-time = "2026-03-31T21:58:17.009Z" }, + { url = "https://files.pythonhosted.org/packages/3b/86/b7c870053e36a94e8951b803cb5b909bfbc9b90ca941527f5fcafbf6b0fa/aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090", size = 1732476, upload-time = "2026-03-31T21:58:18.925Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/4e161f84f98d80c03a238671b4136e6530453d65262867d989bbe78244d0/aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b", size = 1706507, upload-time = "2026-03-31T21:58:21.094Z" }, + { url = "https://files.pythonhosted.org/packages/d4/56/ea11a9f01518bd5a2a2fcee869d248c4b8a0cfa0bb13401574fa31adf4d4/aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a", size = 1773465, upload-time = "2026-03-31T21:58:23.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/40/333ca27fb74b0383f17c90570c748f7582501507307350a79d9f9f3c6eb1/aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8", size = 1873523, upload-time = "2026-03-31T21:58:25.59Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665", size = 1754113, upload-time = "2026-03-31T21:58:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/fb/56/3f653d7f53c89669301ec9e42c95233e2a0c0a6dd051269e6e678db4fdb0/aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540", size = 1562351, upload-time = "2026-03-31T21:58:29.918Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a6/9b3e91eb8ae791cce4ee736da02211c85c6f835f1bdfac0594a8a3b7018c/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb", size = 1693205, upload-time = "2026-03-31T21:58:32.214Z" }, + { url = "https://files.pythonhosted.org/packages/98/fc/bfb437a99a2fcebd6b6eaec609571954de2ed424f01c352f4b5504371dd3/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46", size = 1730618, upload-time = "2026-03-31T21:58:34.728Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b6/c8534862126191a034f68153194c389addc285a0f1347d85096d349bbc15/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8", size = 1745185, upload-time = "2026-03-31T21:58:36.909Z" }, + { url = "https://files.pythonhosted.org/packages/0b/93/4ca8ee2ef5236e2707e0fd5fecb10ce214aee1ff4ab307af9c558bda3b37/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d", size = 1557311, upload-time = "2026-03-31T21:58:39.38Z" }, + { url = "https://files.pythonhosted.org/packages/57/ae/76177b15f18c5f5d094f19901d284025db28eccc5ae374d1d254181d33f4/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6", size = 1773147, upload-time = "2026-03-31T21:58:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/01/a4/62f05a0a98d88af59d93b7fcac564e5f18f513cb7471696ac286db970d6a/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c", size = 1730356, upload-time = "2026-03-31T21:58:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/e4/85/fc8601f59dfa8c9523808281f2da571f8b4699685f9809a228adcc90838d/aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc", size = 432637, upload-time = "2026-03-31T21:58:46.167Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83", size = 458896, upload-time = "2026-03-31T21:58:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/46572759afc859e867a5bc8ec3487315869013f59281ce61764f76d879de/aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c", size = 745721, upload-time = "2026-03-31T21:58:50.229Z" }, + { url = "https://files.pythonhosted.org/packages/13/fe/8a2efd7626dbe6049b2ef8ace18ffda8a4dfcbe1bcff3ac30c0c7575c20b/aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be", size = 497663, upload-time = "2026-03-31T21:58:52.232Z" }, + { url = "https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25", size = 499094, upload-time = "2026-03-31T21:58:54.566Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/a8362cb15cf16a3af7e86ed11962d5cd7d59b449202dc576cdc731310bde/aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56", size = 1726701, upload-time = "2026-03-31T21:58:56.864Z" }, + { url = "https://files.pythonhosted.org/packages/45/0c/c091ac5c3a17114bd76cbf85d674650969ddf93387876cf67f754204bd77/aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2", size = 1683360, upload-time = "2026-03-31T21:58:59.072Z" }, + { url = "https://files.pythonhosted.org/packages/23/73/bcee1c2b79bc275e964d1446c55c54441a461938e70267c86afaae6fba27/aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a", size = 1773023, upload-time = "2026-03-31T21:59:01.776Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ef/720e639df03004fee2d869f771799d8c23046dec47d5b81e396c7cda583a/aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be", size = 1853795, upload-time = "2026-03-31T21:59:04.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b", size = 1730405, upload-time = "2026-03-31T21:59:07.221Z" }, + { url = "https://files.pythonhosted.org/packages/ce/75/ee1fd286ca7dc599d824b5651dad7b3be7ff8d9a7e7b3fe9820d9180f7db/aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94", size = 1558082, upload-time = "2026-03-31T21:59:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/c3/20/1e9e6650dfc436340116b7aa89ff8cb2bbdf0abc11dfaceaad8f74273a10/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d", size = 1692346, upload-time = "2026-03-31T21:59:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/d8/40/8ebc6658d48ea630ac7903912fe0dd4e262f0e16825aa4c833c56c9f1f56/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7", size = 1698891, upload-time = "2026-03-31T21:59:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/d8/78/ea0ae5ec8ba7a5c10bdd6e318f1ba5e76fcde17db8275188772afc7917a4/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772", size = 1742113, upload-time = "2026-03-31T21:59:17.068Z" }, + { url = "https://files.pythonhosted.org/packages/8a/66/9d308ed71e3f2491be1acb8769d96c6f0c47d92099f3bc9119cada27b357/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5", size = 1553088, upload-time = "2026-03-31T21:59:19.541Z" }, + { url = "https://files.pythonhosted.org/packages/da/a6/6cc25ed8dfc6e00c90f5c6d126a98e2cf28957ad06fa1036bd34b6f24a2c/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1", size = 1757976, upload-time = "2026-03-31T21:59:22.311Z" }, + { url = "https://files.pythonhosted.org/packages/c1/2b/cce5b0ffe0de99c83e5e36d8f828e4161e415660a9f3e58339d07cce3006/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b", size = 1712444, upload-time = "2026-03-31T21:59:24.635Z" }, + { url = "https://files.pythonhosted.org/packages/6c/cf/9e1795b4160c58d29421eafd1a69c6ce351e2f7c8d3c6b7e4ca44aea1a5b/aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3", size = 438128, upload-time = "2026-03-31T21:59:27.291Z" }, + { url = "https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162", size = 464029, upload-time = "2026-03-31T21:59:29.429Z" }, + { url = "https://files.pythonhosted.org/packages/79/11/c27d9332ee20d68dd164dc12a6ecdef2e2e35ecc97ed6cf0d2442844624b/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a", size = 778758, upload-time = "2026-03-31T21:59:31.547Z" }, + { url = "https://files.pythonhosted.org/packages/04/fb/377aead2e0a3ba5f09b7624f702a964bdf4f08b5b6728a9799830c80041e/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254", size = 512883, upload-time = "2026-03-31T21:59:34.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a6/aa109a33671f7a5d3bd78b46da9d852797c5e665bfda7d6b373f56bff2ec/aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36", size = 516668, upload-time = "2026-03-31T21:59:36.497Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/ca078f9f2fa9563c36fb8ef89053ea2bb146d6f792c5104574d49d8acb63/aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f", size = 1883461, upload-time = "2026-03-31T21:59:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e3/a7ad633ca1ca497b852233a3cce6906a56c3225fb6d9217b5e5e60b7419d/aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800", size = 1747661, upload-time = "2026-03-31T21:59:41.187Z" }, + { url = "https://files.pythonhosted.org/packages/33/b9/cd6fe579bed34a906d3d783fe60f2fa297ef55b27bb4538438ee49d4dc41/aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf", size = 1863800, upload-time = "2026-03-31T21:59:43.84Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3f/2c1e2f5144cefa889c8afd5cf431994c32f3b29da9961698ff4e3811b79a/aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b", size = 1958382, upload-time = "2026-03-31T21:59:46.187Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/f31ec3f1013723b3babe3609e7f119c2c2fb6ef33da90061a705ef3e1bc8/aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a", size = 1803724, upload-time = "2026-03-31T21:59:48.656Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b4/57712dfc6f1542f067daa81eb61da282fab3e6f1966fca25db06c4fc62d5/aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8", size = 1640027, upload-time = "2026-03-31T21:59:51.284Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/734c878fb43ec083d8e31bf029daae1beafeae582d1b35da234739e82ee7/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be", size = 1806644, upload-time = "2026-03-31T21:59:53.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/a5/f671e5cbec1c21d044ff3078223f949748f3a7f86b14e34a365d74a5d21f/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b", size = 1791630, upload-time = "2026-03-31T21:59:56.239Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/fb8d0ad63a0b8a99be97deac8c04dacf0785721c158bdf23d679a87aa99e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6", size = 1809403, upload-time = "2026-03-31T21:59:59.103Z" }, + { url = "https://files.pythonhosted.org/packages/59/0c/bfed7f30662fcf12206481c2aac57dedee43fe1c49275e85b3a1e1742294/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037", size = 1634924, upload-time = "2026-03-31T22:00:02.116Z" }, + { url = "https://files.pythonhosted.org/packages/17/d6/fd518d668a09fd5a3319ae5e984d4d80b9a4b3df4e21c52f02251ef5a32e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500", size = 1836119, upload-time = "2026-03-31T22:00:04.756Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/15fb7a9d52e112a25b621c67b69c167805cb1f2ab8f1708a5c490d1b52fe/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9", size = 1772072, upload-time = "2026-03-31T22:00:07.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/df/57ba7f0c4a553fc2bd8b6321df236870ec6fd64a2a473a8a13d4f733214e/aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8", size = 471819, upload-time = "2026-03-31T22:00:10.277Z" }, + { url = "https://files.pythonhosted.org/packages/62/29/2f8418269e46454a26171bfdd6a055d74febf32234e474930f2f60a17145/aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9", size = 505441, upload-time = "2026-03-31T22:00:12.791Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "astroid" +version = "3.3.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/74/dfb75f9ccd592bbedb175d4a32fc643cf569d7c218508bfbd6ea7ef9c091/astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", size = 400439, upload-time = "2025-07-13T18:04:23.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec", size = 275612, upload-time = "2025-07-13T18:04:21.07Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "black" +version = "26.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/a8/11170031095655d36ebc6664fe0897866f6023892396900eec0e8fdc4299/black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2", size = 1866562, upload-time = "2026-03-12T03:39:58.639Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/9e7548d719c3248c6c2abfd555d11169457cbd584d98d179111338423790/black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b", size = 1703623, upload-time = "2026-03-12T03:40:00.347Z" }, + { url = "https://files.pythonhosted.org/packages/7f/0a/8d17d1a9c06f88d3d030d0b1d4373c1551146e252afe4547ed601c0e697f/black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac", size = 1768388, upload-time = "2026-03-12T03:40:01.765Z" }, + { url = "https://files.pythonhosted.org/packages/52/79/c1ee726e221c863cde5164f925bacf183dfdf0397d4e3f94889439b947b4/black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a", size = 1412969, upload-time = "2026-03-12T03:40:03.252Z" }, + { url = "https://files.pythonhosted.org/packages/73/a5/15c01d613f5756f68ed8f6d4ec0a1e24b82b18889fa71affd3d1f7fad058/black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a", size = 1220345, upload-time = "2026-03-12T03:40:04.892Z" }, + { url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" }, + { url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" }, + { url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" }, + { url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" }, + { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" }, + { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" }, + { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" }, + { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" }, + { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" }, + { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" }, + { url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" }, + { url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" }, + { url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" }, +] + +[[package]] +name = "cachetools" +version = "7.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e2/85f227594656000ff4d8adadae91a21f536d4a84c6c716a86bd6685874be/cachetools-7.1.1.tar.gz", hash = "sha256:27bdf856d68fd3c71c26c01b5edc312124ed427524d1ddb31aa2b7746fe20d4b", size = 40202, upload-time = "2026-05-03T20:00:29.391Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/0f/f897abe4ea0a8c408ae65c8c83bffab4936ad65d6032d4fb4cd35bbdc3ee/cachetools-7.1.1-py3-none-any.whl", hash = "sha256:0335cd7a0952d2b22327441fb0628139e234c565559eeb91a8a4ac7551c5353d", size = 16775, upload-time = "2026-05-03T20:00:27.857Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "click" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, +] + +[[package]] +name = "codespell" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/9d/1d0903dff693160f893ca6abcabad545088e7a2ee0a6deae7c24e958be69/codespell-2.4.2.tar.gz", hash = "sha256:3c33be9ae34543807f088aeb4832dfad8cb2dae38da61cac0a7045dd376cfdf3", size = 352058, upload-time = "2026-03-05T18:10:42.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/a1/52fa05533e95fe45bcc09bcf8a503874b1c08f221a4e35608017e0938f55/codespell-2.4.2-py3-none-any.whl", hash = "sha256:97e0c1060cf46bd1d5db89a936c98db8c2b804e1fdd4b5c645e82a1ec6b1f886", size = 353715, upload-time = "2026-03-05T18:10:41.398Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, + { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, + { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cryptography" +version = "46.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, + { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, + { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, + { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, + { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, + { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, + { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, + { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, + { url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/be/8bd693a0b9d53d48c8978fa5d889e06f3b5b03e45fd1ea1e78267b4887cb/debugpy-1.8.20-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:157e96ffb7f80b3ad36d808646198c90acb46fdcfd8bb1999838f0b6f2b59c64", size = 2099192, upload-time = "2026-01-29T23:03:29.707Z" }, + { url = "https://files.pythonhosted.org/packages/77/1b/85326d07432086a06361d493d2743edd0c4fc2ef62162be7f8618441ac37/debugpy-1.8.20-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:c1178ae571aff42e61801a38b007af504ec8e05fde1c5c12e5a7efef21009642", size = 3088568, upload-time = "2026-01-29T23:03:31.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/60/3e08462ee3eccd10998853eb35947c416e446bfe2bc37dbb886b9044586c/debugpy-1.8.20-cp310-cp310-win32.whl", hash = "sha256:c29dd9d656c0fbd77906a6e6a82ae4881514aa3294b94c903ff99303e789b4a2", size = 5284399, upload-time = "2026-01-29T23:03:33.678Z" }, + { url = "https://files.pythonhosted.org/packages/72/43/09d49106e770fe558ced5e80df2e3c2ebee10e576eda155dcc5670473663/debugpy-1.8.20-cp310-cp310-win_amd64.whl", hash = "sha256:3ca85463f63b5dd0aa7aaa933d97cbc47c174896dcae8431695872969f981893", size = 5316388, upload-time = "2026-01-29T23:03:35.095Z" }, + { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, + { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "deepdiff" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "orderly-set" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/20/63dd34163ed07393968128dc8c7ab948c96e47c4ce76976ea533de64909d/deepdiff-9.0.0.tar.gz", hash = "sha256:4872005306237b5b50829803feff58a1dfd20b2b357a55de22e7ded65b2008a7", size = 151952, upload-time = "2026-03-30T05:52:23.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/c4/da7089cd7aa4ab554f56e18a7fb08dcfed8fd2ae91fa528f5b1be207a148/deepdiff-9.0.0-py3-none-any.whl", hash = "sha256:b1ae0dd86290d86a03de5fbee728fde43095c1472ae4974bdab23ab4656305bd", size = 170540, upload-time = "2026-03-30T05:52:22.008Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastapi" +version = "0.137.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/29/cc5819dc24d3daa80cdaa1aec023bf8652a70dd7fd1c96b0b225c99a7690/fastapi-0.137.2.tar.gz", hash = "sha256:b9d893bebc97dcfbdcb1917e88a292d062844ea19445a5fa4f7eb28c4baea9e3", size = 410332, upload-time = "2026-06-18T06:58:24.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/ed/0c6b644e99fb5697d8bdcd36cdb47c52e77a63fc7a1514b1f03a6ecab955/fastapi-0.137.2-py3-none-any.whl", hash = "sha256:791d36261e916a98b25ac85ee591bc3db159394070f6d3d096d94fb378f60ce2", size = 122252, upload-time = "2026-06-18T06:58:26.074Z" }, +] + +[[package]] +name = "filelock" +version = "3.29.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, +] + +[[package]] +name = "freezegun" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/dd/23e2f4e357f8fd3bdff613c1fe4466d21bfb00a6177f238079b17f7b1c84/freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a", size = 35914, upload-time = "2025-08-09T10:39:08.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/2e/b41d8a1a917d6581fc27a35d05561037b048e47df50f27f8ac9c7e27a710/freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2", size = 19266, upload-time = "2025-08-09T10:39:06.636Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + +[[package]] +name = "idna" +version = "3.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ipython", version = "9.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, +] + +[[package]] +name = "ipython" +version = "8.39.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, +] + +[[package]] +name = "ipython" +version = "9.13.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", + "python_full_version >= '3.12' and python_full_version < '3.14'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "psutil", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/87cda5842cf5c31837c06ddb588e11c3c35d8ece89b7a0108c06b8c9b00a/ipython-9.13.0.tar.gz", hash = "sha256:7e834b6afc99f020e3f05966ced34792f40267d64cb1ea9043886dab0dde5967", size = 4430549, upload-time = "2026-04-24T12:24:55.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/86/3060e8029b7cc505cce9a0137431dda81d0a3fde93a8f0f50ee0bf37a795/ipython-9.13.0-py3-none-any.whl", hash = "sha256:57f9d4639e20818d328d287c7b549af3d05f12486ea8f2e7f73e52a36ec4d201", size = 627274, upload-time = "2026-04-24T12:24:53.038Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "isort" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/82/fa43935523efdfcce6abbae9da7f372b627b27142c3419fcf13bf5b0c397/isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481", size = 824325, upload-time = "2025-10-01T16:26:45.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/cc/9b681a170efab4868a032631dea1e8446d8ec718a7f657b94d49d1a12643/isort-6.1.0-py3-none-any.whl", hash = "sha256:58d8927ecce74e5087aef019f778d4081a3b6c98f15a80ba35782ca8a2097784", size = 94329, upload-time = "2025-10-01T16:26:43.291Z" }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "librt" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/cb/c1945e506893b5b8577fb45a60c80e3ffe4a82092a04a6f29b0b951d9a24/librt-0.10.0.tar.gz", hash = "sha256:1aba1e8aa4e3307a7be68a74149545fde7451964dc0235a8bec5704a17bdda42", size = 191799, upload-time = "2026-05-05T16:31:23.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/18/827e5c1262a88c2602e86f99aee0f288ffea3280dbd2ff448858ef9dc6e9/librt-0.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dc99f9642100b86e5f6bb14cdc9970009e31a9ef7d64df6704b7018451524a3", size = 76461, upload-time = "2026-05-05T16:29:00.422Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/54254e30287f5a5abec6fef22d976987476e966be5fdff51fe8c2d5d73d1/librt-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8298cedfcfaff3790000bd057aaaa3df1b0ab54cf7b48eeab16184cbb1bc66b9", size = 79740, upload-time = "2026-05-05T16:29:01.926Z" }, + { url = "https://files.pythonhosted.org/packages/4c/20/e93264b52113669d98d3b63ff94d4ce0c4dd49ae0503f1788440a884e5f0/librt-0.10.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7dbe312dbf76468255b79a7ba311236fde620f2f7055fc09d421e31340314e", size = 243472, upload-time = "2026-05-05T16:29:03.373Z" }, + { url = "https://files.pythonhosted.org/packages/35/ad/34a5141178e8b18a4cfa45d1a0d523c84397e2abd5d06fea2d846da687e8/librt-0.10.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:56ed90c48c19249012dadfd79a1bc13bd5168ea60a70722d330a3a600c0b1852", size = 232073, upload-time = "2026-05-05T16:29:04.815Z" }, + { url = "https://files.pythonhosted.org/packages/97/1f/67240e910cd9f9ab1498c1470738345fc29dce5dc9719db1e0e09d1e861f/librt-0.10.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d74ca0f4b2b09c117f913d4df01f6b934dff8a271096b35167d5264a31649f0", size = 256956, upload-time = "2026-05-05T16:29:06.516Z" }, + { url = "https://files.pythonhosted.org/packages/22/50/3a2b3482c27d607f6e8216d913c6bc592b9a2141d96990309452340a78e3/librt-0.10.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8eb2daa9375f93c0e55ff5e44a4bbe98f39e5fe52e1abf9c97acb67743b61bf8", size = 250593, upload-time = "2026-05-05T16:29:08.324Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1c/07dba133d79f93322fa17514062f1a2a50d6bdfb7baec4acf78193d7fad1/librt-0.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7b09b90e634e6dff57978cd358070046071e2b120501f10787aeb35425f504f6", size = 263582, upload-time = "2026-05-05T16:29:09.866Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ac/033f2c6d6ab0b48f15f02e5bf065521b11a51922806017f8b6274df30d69/librt-0.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2cf22fd379d60c739b800d4295ed34045f8b04aa8df9c12bd2f8f43f7fe672b7", size = 259307, upload-time = "2026-05-05T16:29:11.675Z" }, + { url = "https://files.pythonhosted.org/packages/6e/10/679046cd75d5a52c0104c890d8f69574ef4e619c683e59c15584d03a2457/librt-0.10.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:74c798793fcf29a84d442278ebe0bb1fff79fe58ac4106eeff7019cbba861423", size = 257342, upload-time = "2026-05-05T16:29:13.14Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d5/dbaac9c0884f78a53dda22b9ec92bb788e1400e762ed7623fa96928c8da5/librt-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc4f1573401e8dbe6c26511fe027620b0fb30ae9a7ab814e02e510626b8b5f9c", size = 280141, upload-time = "2026-05-05T16:29:14.922Z" }, + { url = "https://files.pythonhosted.org/packages/cc/81/71f18cf8eb340d9fda011498870910f6a8697aeb50833005d3d8107653fd/librt-0.10.0-cp310-cp310-win32.whl", hash = "sha256:e1428275f5fe3d4db6822e58d8b005a5b28ffca55e8433ebc051247fbe46429f", size = 62257, upload-time = "2026-05-05T16:29:16.226Z" }, + { url = "https://files.pythonhosted.org/packages/df/52/6bcebc2f870c4836bcb372be885fae7f17a1d25037d3a8250ef79fbe0124/librt-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:0708e9408f585b0f065081680583a577652099680ccf820c7538904322b679c3", size = 70321, upload-time = "2026-05-05T16:29:17.41Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a3/1472717d2325adacc8d335ba2e4078015c09d75b599f3cf48e967b3d306e/librt-0.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01b4500ca3a625450c032a9142a8e843923ce263fa8a92ad1b38927cabe2fe72", size = 76045, upload-time = "2026-05-05T16:29:18.731Z" }, + { url = "https://files.pythonhosted.org/packages/a6/31/bfe32355d4b369aef3d7aa442df663bb5558c2ffa2de286cb2956346bc24/librt-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b7e42d1b3e300d20bfc87e72ffd62f0a92a2cb3c35f7bf90df90c9d2a49f74c", size = 79466, upload-time = "2026-05-05T16:29:20.052Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f1/83f8a2c715ba2cac9b7387a5a5cea25f717f7184320cfe48b36bed9c58e9/librt-0.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8ef7b8c61ce3a1b597cd3e15348ff1574325165c2e7ce09a718154cde2a7950", size = 242283, upload-time = "2026-05-05T16:29:21.596Z" }, + { url = "https://files.pythonhosted.org/packages/cc/94/c3a4ce94857f0004a542f86662806383611858f522722db58efaec0a1472/librt-0.10.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e73c84f72d1fa0d6eaa7a1930b436ba8d2c90c58d77bfabb09995a69ad35f6c0", size = 230735, upload-time = "2026-05-05T16:29:23.335Z" }, + { url = "https://files.pythonhosted.org/packages/d1/41/e962bb26c7728eb7b3a69e490d0c800fd9968a6970e390c1f18ddb56093d/librt-0.10.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9728cb98713bd862fb8f4fd6a642d1896c86058a41d77c70f3d5cee75e725275", size = 256606, upload-time = "2026-05-05T16:29:24.91Z" }, + { url = "https://files.pythonhosted.org/packages/66/3a/4e46a707b1ecc993fd691071623b9beab89703a63bd21cc7807e06c28209/librt-0.10.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:648b7e941d20acd72f9652115e0e53facd98156d61f9ebf7a812bdef8bdccea9", size = 249739, upload-time = "2026-05-05T16:29:26.648Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f5/dc5b7eb294656ad23d4ff4cf8514208d54fe1026b909d726a0dc026689c9/librt-0.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c3e33747c068e86a9007c20fdb777eb5ba8d3d19136d7812f88e69a713041b6f", size = 261414, upload-time = "2026-05-05T16:29:28.702Z" }, + { url = "https://files.pythonhosted.org/packages/58/e4/990ed8d12c7f114ac8f8ccd47f7d9bd9704ef61acfcb1df4a05047da7710/librt-0.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d509c745bf7e77d1107cf05e6abb249dc03fad13eb39f2286a49deedaeb2bcd7", size = 256614, upload-time = "2026-05-05T16:29:30.357Z" }, + { url = "https://files.pythonhosted.org/packages/60/eb/52d2726c7fb22818507dc3cc166c8f36dd4a4b68a7be67f12006ac8777c1/librt-0.10.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:786ad5a15e99d0e0e74f3adbeecc198a5ac58f340be07e984723d1e0074838de", size = 255144, upload-time = "2026-05-05T16:29:32.106Z" }, + { url = "https://files.pythonhosted.org/packages/bc/df/bd5591a78f7531fce4b6eb9962aadc6adc9560a01570442a884b6e554abe/librt-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:075582d877a97ee3d8e77bda3689dbe617b14f6469224a2d80b4b6c38e3951aa", size = 279121, upload-time = "2026-05-05T16:29:33.688Z" }, + { url = "https://files.pythonhosted.org/packages/fd/df/7c2b838dfc89a1762dd156d8b0c39848a7a2845d725a50be5a6e021fb8ba/librt-0.10.0-cp311-cp311-win32.whl", hash = "sha256:75ecdc3f5a90065aa2af2e574706c5495adc392520762dcf10b1aa716f0b8090", size = 62593, upload-time = "2026-05-05T16:29:35.152Z" }, + { url = "https://files.pythonhosted.org/packages/91/19/22ff572981049a9d436a083dbea1572d0f5dc068b7353637d2dd9977c8f1/librt-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:b6f6084884131d8a52cb9d7095ff2aa52c1e786d9fdaefab1fb4515415e9e083", size = 70914, upload-time = "2026-05-05T16:29:36.407Z" }, + { url = "https://files.pythonhosted.org/packages/12/22/1697cc64f4a5c7e9bce55e99c6d234a346beaedaefcd1e2ca90dd285f98c/librt-0.10.0-cp311-cp311-win_arm64.whl", hash = "sha256:0140bd62151160047e89b2730cb6f8506cdac5127baa1afb9231e4dd3fe7f681", size = 61176, upload-time = "2026-05-05T16:29:37.62Z" }, + { url = "https://files.pythonhosted.org/packages/12/8e/cbb5b6f6e45e65c10a42449a69eaccc44d73e6a081ea752fbc5221c6dc1c/librt-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b4b58a44b407e91f633dafee008de9ddea6aa2a555ed94929c099260910bd0ba", size = 77327, upload-time = "2026-05-05T16:29:38.919Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3d/8233cbee8e99e6a8992f02bfc2dec8d787509566a511d1fde2574ee7473f/librt-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:950b79b11762531bdf45a9df909d2f9a2a8445c70c88665c01d14c8511a27dc5", size = 79971, upload-time = "2026-05-05T16:29:40.96Z" }, + { url = "https://files.pythonhosted.org/packages/87/6f/5264b298cef2b72fc97d2dde56c66181eda35204bf5dcd1ed0c3d0a0a782/librt-0.10.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4538453f51be197633b425912c150e25b0667252d3741c53e8368176d98d9d37", size = 246559, upload-time = "2026-05-05T16:29:42.701Z" }, + { url = "https://files.pythonhosted.org/packages/07/7b/19b1b859cc60d5f99276cc2b3144d91556c6d1b1e4ebb50359696bebf7a8/librt-0.10.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:70b955f091beac93e994a0b7ec616934f63b3ea5c3d6d7af847562f935aceca7", size = 235216, upload-time = "2026-05-05T16:29:44.193Z" }, + { url = "https://files.pythonhosted.org/packages/6e/56/a2f40717142a8af46289f57874ef914353d8faccd5e4f8e594ab1e16e8c7/librt-0.10.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:483e685e06b6163728ba6c85d74315176be7190f432ec2a41226e5e14355d5f0", size = 263108, upload-time = "2026-05-05T16:29:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/67/ca/15c625c3bdc0167c01e04ef8878317e9713f3bfa788438342f7a94c7b22c/librt-0.10.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ac53d946a009d1a38c44a60812708c9458fb2a239a5f630d8e625571386650f", size = 255280, upload-time = "2026-05-05T16:29:48.087Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c5/ba301d571d9e05844e2435b73aba30bee77bb75ce155c9affcfd2173dd03/librt-0.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc8771c9fcf0ea894ca41fdc2abd83572c2fbda221f232d86e718614e57ff513", size = 268829, upload-time = "2026-05-05T16:29:49.628Z" }, + { url = "https://files.pythonhosted.org/packages/8b/60/af70e135bc1f1fe15dd3894b1e4bbefc7ecdf911749a925a39eb86ceb2a1/librt-0.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:70805dbc5257892ac572f86290a61e3c8d90224ecce1a8b2d1f7ed51965417f4", size = 262051, upload-time = "2026-05-05T16:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/c2/c8236eb8b421bac5a172ba208f965abaa89805da2a3fa112bdf1764caf8f/librt-0.10.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d3b4f300f7bcba6e2ff73fb8bef1898479e9772bfa2682998c636391633ec826", size = 264347, upload-time = "2026-05-05T16:29:53.013Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/15b6d32bc25dacd4a60886a683d8128d6219910c122202b995a40dd4f8d2/librt-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:943bc943f92f4fb3408fae62485c6a3ad68ce4f2ee205643a39641525c19a276", size = 286482, upload-time = "2026-05-05T16:29:54.675Z" }, + { url = "https://files.pythonhosted.org/packages/fb/8e/b1b959bacd323eb4360579db992513e1406d1c6ef7edb57b5511fd0666fd/librt-0.10.0-cp312-cp312-win32.whl", hash = "sha256:6065c1a758fba1010b41401013903d3d5d2750eab425ddedd584abac31d0630e", size = 62955, upload-time = "2026-05-05T16:29:56.39Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4c/d4cd6e4b9fc24098e63cc85537d1b6689682aee96809c38f08072067cc2b/librt-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:d788ecbe208ab352dab0e105cc06057bf9a2fc7e58cabb0d751ad9e30062b9e2", size = 71191, upload-time = "2026-05-05T16:29:57.682Z" }, + { url = "https://files.pythonhosted.org/packages/2b/19/8641da1f63d24b92354a492f893c022d6b3a0df44e70c8eff49364613983/librt-0.10.0-cp312-cp312-win_arm64.whl", hash = "sha256:6003d1f295bdba02656dc81308208fc060d0a51d8c0d0a6db70f7f3c57b9ba0a", size = 61432, upload-time = "2026-05-05T16:29:58.971Z" }, + { url = "https://files.pythonhosted.org/packages/e5/29/681a75c82f4cc90d29e4b257a3299b79fe13fe927a04c57b8109d70b6957/librt-0.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f0ede79d682e73f91c1b599a76d78b7464b9b5d213754cedb13372d9df36e596", size = 77299, upload-time = "2026-05-05T16:30:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/62/24/0c7ca445a55d04be79cac19819437fd094782347fa116f6681844fa6143e/librt-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0ba0b131fdb336c8b9c948e397f4a7e649d0f783b529f07b647bf4961df392e", size = 79930, upload-time = "2026-05-05T16:30:01.555Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/1e2b8f6443ef9e9a81e89486ca70e22f3684f93db003ce6eaefc3d0839b9/librt-0.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2728117da2afb96fb957768725ee43dc9a2d73b031e02da424b818a3cdd3a275", size = 246195, upload-time = "2026-05-05T16:30:03.261Z" }, + { url = "https://files.pythonhosted.org/packages/74/61/9dc9e03de0439ad84c1c240aac8b747f12c90cb797ea6042f7bdb8d3410f/librt-0.10.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:723ba80594c49cdf0584196fc430752262605dc9449902fc9bd3d9b79976cb77", size = 234951, upload-time = "2026-05-05T16:30:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/635223117d7590875bca441275065a3bf491203ad4208bd1cc3ffd90c5a1/librt-0.10.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7292edaaca294a61a978c53a3c7d6130d099b0dfbc8f0a65916cdc6b891b9852", size = 262768, upload-time = "2026-05-05T16:30:06.638Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/b04152d0cd8b6ca2b428a8bd3230343230c35ed304a932f35b5375f2f828/librt-0.10.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89fe9d539f2c10a1666633eeeac507ce95dd06d9ecc58de3c6390dba156a3d3a", size = 255075, upload-time = "2026-05-05T16:30:08.216Z" }, + { url = "https://files.pythonhosted.org/packages/35/1e/25bac4c7f2ca36f0e612cade186970683cf79153d96beccc3a11a9e19b97/librt-0.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4efa7b9587503fa5b67f40593302b9c8836d211d222ff9f7cafe67be5f8f0b10", size = 268559, upload-time = "2026-05-05T16:30:10.1Z" }, + { url = "https://files.pythonhosted.org/packages/18/54/4601faab35b6632a13200faa146ca62bfd111ffbe2568be430d65c89493a/librt-0.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:22dc982ef59df0136df36092ccbdbb570ced8aafb33e49585739b2f1de1c13b6", size = 261753, upload-time = "2026-05-05T16:30:11.912Z" }, + { url = "https://files.pythonhosted.org/packages/1b/cf/39f4023509e94fade8b074666fa3292db9cb6b34ea5dcbe7af53df9fca1d/librt-0.10.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6f2e5f3606253a84cea719c94a3bb1c54487b5d617d0254d46e0920d8a06be3f", size = 264055, upload-time = "2026-05-05T16:30:13.465Z" }, + { url = "https://files.pythonhosted.org/packages/8e/00/40247209fc46a8e308a91412d5206aedf8efb667ee89eb625820106a5c2f/librt-0.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40884bfaa1e29f6b6a9be255007d8f359bfc9e61d68bdef8ed3158bfcbc95df9", size = 286190, upload-time = "2026-05-05T16:30:15.073Z" }, + { url = "https://files.pythonhosted.org/packages/d8/6e/5566beb94431a985abe1787af5ef86e087750172ff9d0bbf20f93e88132d/librt-0.10.0-cp313-cp313-win32.whl", hash = "sha256:3cd34cd8254eba756660bff6c2da91278248184301054fe3e4feb073bdd49b14", size = 62949, upload-time = "2026-05-05T16:30:16.503Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c2/3ea3301d6c8dff51d39dbe8ed75db3dc92896947d4afb5eeadf821c1e67f/librt-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:7baac5313e2d8dce1386f97777a8d03ab28f5fe1e780b3b9ac2ee7544551fedc", size = 71152, upload-time = "2026-05-05T16:30:17.766Z" }, + { url = "https://files.pythonhosted.org/packages/3c/de/5d49cb92cadcbc77d3abc27b93fd6030ed8437487dde2eae38cab5e6704d/librt-0.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:afc5b4406c8e2515698d922a5c7823a009312835ea58196671fff40e35cb8166", size = 61336, upload-time = "2026-05-05T16:30:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/6a/64/7165e08108cc185a13a9c069f0685e6ef92e70e07fddf7edf5e7348c6316/librt-0.10.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f09588a30e6a22ec624090d72a3ab1a6d4d5485c3ed739603e76aa3c16efa688", size = 76794, upload-time = "2026-05-05T16:30:20.392Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ef/bf8613febf651b90c5222ee79dea5ae58d4cc2b544df69d3033424448934/librt-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:131ade118d12bd7a0adc4e655474a553f1b76cf78385868885944d21d51e45e0", size = 79662, upload-time = "2026-05-05T16:30:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/b6/67/9eddd165c1d8397bdf99b38bf12b5a55b3def5035b49eedb49f2775d1430/librt-0.10.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8b9ab28e40d011c373a189eae900c916e66d6fbecf7983e9e4883089ee085ef", size = 242390, upload-time = "2026-05-05T16:30:23.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/d1/d95da80334501866cd37004ab5d7483220d05862fab4b5405394f0264f0d/librt-0.10.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:67c39bb30da73bae1f293d1ed8bc2f8f6642649dd0928d3600aeff3041ac23d6", size = 232603, upload-time = "2026-05-05T16:30:25.198Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fa/e6d64d28718bc1be4e1736fcb037ca1c4dfca927e7167df75a7d5215665e/librt-0.10.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8c3273c6b774614f093c8927c2bf1b077d0fefde988fe98f46a333734e5597ab", size = 259187, upload-time = "2026-05-05T16:30:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/72/3f/3fdb77e7f937dad59cfd76b720be7e7643400ec76b2da35befab8d66ba30/librt-0.10.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9dd7c1b86a4baa583ab5db977484b93a2c474e69e96ef3e9538387ea54229cb9", size = 251846, upload-time = "2026-05-05T16:30:28.56Z" }, + { url = "https://files.pythonhosted.org/packages/18/ca/f4d49133dd86a6f55d79eca30bf412fa722f511a9abe67f62f57aa64e66a/librt-0.10.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a77385c5a202e831149f7ad03be9e67cf80e957e52c614e83dcb822c95222eb8", size = 264936, upload-time = "2026-05-05T16:30:30.491Z" }, + { url = "https://files.pythonhosted.org/packages/de/66/a8df2fbadc1f6c1827a096d11c40175bd526133480bd3bc88ec64a03d257/librt-0.10.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c6a5eafa74b5655bad59886138ed68426f098a6beb8cb95a71f2cc3cd8bb33fe", size = 258699, upload-time = "2026-05-05T16:30:32.002Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/1e3c83613fe05451bb969e27b68a573d177f08d5f63533cc29fec0989658/librt-0.10.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1fc93d0439204c50ab4d1512611ce2c206f1b369b419f69c7c27c761561e3291", size = 259825, upload-time = "2026-05-05T16:30:35.077Z" }, + { url = "https://files.pythonhosted.org/packages/09/24/5e2f926ee9d3ef348d9339526d7062abb5c44d8419e3179528c01d78c102/librt-0.10.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:79e713c178bc7a744adfbee6b4619a288eecc0c914da2a9313a20255abe2f0cf", size = 282548, upload-time = "2026-05-05T16:30:36.639Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7d/3e89ed6ad0162561fa8bef9df3195e24263104c955713cd0237d3711fad2/librt-0.10.0-cp314-cp314-win32.whl", hash = "sha256:2eba9d955a68c41d9f326be3da42f163ec3518b7ab20f1c826224e7bed71e0bf", size = 58970, upload-time = "2026-05-05T16:30:38.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/579e731c94a7086a268bfa3e7a4945cd47836bebd3cbf3faeafd2e7eaef9/librt-0.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbfaf7f5145e9917f5d18bffa298eff6a19d74e7b8b11dabdca95785befe8dbf", size = 67260, upload-time = "2026-05-05T16:30:39.804Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f8/235822b7ae0b2334f12ee18bcf2476d07924077a5efeea57dbe927704be2/librt-0.10.0-cp314-cp314-win_arm64.whl", hash = "sha256:8d6d385d1969849a6b1397114df22714b6ded917bada98668e3e974dc663477e", size = 57156, upload-time = "2026-05-05T16:30:41.412Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e3/9b919cbf1e8eb770bf91bb7df28125e0f1daf4587169afefd95402636e9a/librt-0.10.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:6c3a82d3bd32631ef5c79922dfc028520c9ad840255979ab4d908271818039ee", size = 79150, upload-time = "2026-05-05T16:30:42.761Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f5/72a944aa3bc3498169a168087eff58ca48b58bf1b704e59d091fd30739f3/librt-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d64cc66005dc324c9bb1fa3fc2841f529002f6eb15966d55e46d430f56955a6a", size = 82304, upload-time = "2026-05-05T16:30:44.082Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e3/fcc290a33e295019759472dfa794d204e43504b276ac65eab7fd9da20ea3/librt-0.10.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb562cd28c88cd2c6a9a6c78f99dc39348d6b16c94adc25de0e574acf1176e9", size = 272556, upload-time = "2026-05-05T16:30:45.497Z" }, + { url = "https://files.pythonhosted.org/packages/fd/54/546975e4c997573885e7f040a05012f8838e06fb12b0c3c1fbb76254e9d7/librt-0.10.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b809aa2854d019c28773b03605df22adc675ee4f3f4402d673581313e8906119", size = 256941, upload-time = "2026-05-05T16:30:47.059Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f1d03401571b331653acddbd4e8cd955c06d945241dd08b25192fac0d04b/librt-0.10.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc15acabdd519bd4176fdadc2119e5e3093485d86f89138daf47e5b4cedb983a", size = 285855, upload-time = "2026-05-05T16:30:48.86Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/62cf80ff046c339faf56718b3a940244d4beb70f1c6407289b5830ec11e9/librt-0.10.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b1b2d835307d08ddadd94568e2369648ec9173bd3eea6d7f52a1abe717c81f98", size = 275321, upload-time = "2026-05-05T16:30:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ea/da5918d4070362e9a4d2ee9cd34f9dc84902daad8fd4275f8504a727ff4e/librt-0.10.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d261c6a2f93335a5167887fb0223e8b98ffce20ee3fde242e8e58a37ece6d0e5", size = 293993, upload-time = "2026-05-05T16:30:52.577Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8d/68b6086bed1fcdc314c640ea04e31e52d18052e08059fa595409d66a51a9/librt-0.10.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e2ffd44963f8e7f68995504d90f9881d64e94dc1d8e310039b9526108fc0c0f7", size = 284254, upload-time = "2026-05-05T16:30:55.086Z" }, + { url = "https://files.pythonhosted.org/packages/06/c8/b810f1d84ec34a5a7ed93d7b510ab04164d75fbdf23088d5c3fbe6b08357/librt-0.10.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f285f6455ed495791c4d8630e5af732960adea93cac4c893d15619f2eae53e8", size = 284925, upload-time = "2026-05-05T16:30:56.728Z" }, + { url = "https://files.pythonhosted.org/packages/5a/00/3c82d4158c5a2c62528b8fccce65a8c9ad700e480e86f9389387435089a5/librt-0.10.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f6034ff52e663d34c7b82ef2aa2f94ad7c1d939e2368e63b06844bc4d127d2e1", size = 307830, upload-time = "2026-05-05T16:30:58.377Z" }, + { url = "https://files.pythonhosted.org/packages/99/3a/9c635ac3e8a00383ff689161d3eac8a30b3b2ddc711b40471e6b8983ea29/librt-0.10.0-cp314-cp314t-win32.whl", hash = "sha256:657860fd877fba6a241ea088ef99f63ca819945d3c715265da670bad56c37ebe", size = 60147, upload-time = "2026-05-05T16:31:00.293Z" }, + { url = "https://files.pythonhosted.org/packages/dc/e8/6f65f3e565d4ac212cddddd552eacc8035ffdf941ca0ad6fe945a211d41f/librt-0.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:56ded2d66010203a0cb5af063b609e3f079531a0e5e576d618dece859fd2e1af", size = 68649, upload-time = "2026-05-05T16:31:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/51/78/a0705a67cacd81e5fa01a5035b3adbdfbb43a7b8d4bd27e2b282ae61baf2/librt-0.10.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1ee63f30abf18ed4830fdbaf87b2b6f4bba1e198d46085c314edde4045e56715", size = 58247, upload-time = "2026-05-05T16:31:03.191Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "mypy" +version = "1.20.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/af/e3d4b3e9ec91a0ff9aabfdb38692952acf49bbb899c2e4c29acb3a6da3ae/mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665", size = 3817349, upload-time = "2026-04-21T17:12:28.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/97/ce2502df2cecf2ef997b6c6527c4a223b92feb9e7b790cdc8dcd683f3a8a/mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4", size = 14457059, upload-time = "2026-04-21T17:06:14.935Z" }, + { url = "https://files.pythonhosted.org/packages/c9/34/417ee60b822cc80c0f3dc9f495ad7fd8dbb8d8b2cf4baf22d4046d25d01d/mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997", size = 13346816, upload-time = "2026-04-21T17:10:41.433Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/e20951978702df58379d0bcc2e8f7ccdca4e78cd7dc66dd3ddbf9b29d517/mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14", size = 13772593, upload-time = "2026-04-21T17:08:11.24Z" }, + { url = "https://files.pythonhosted.org/packages/63/a5/5441a13259ec516c56fd5de0fd96a69a9590ae6c5e5d3e5174aa84b97973/mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99", size = 14656635, upload-time = "2026-04-21T17:09:54.042Z" }, + { url = "https://files.pythonhosted.org/packages/3b/51/b89c69157c5e1f19fd125a65d991166a26906e7902f026f00feebbcfa2b9/mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c", size = 14943278, upload-time = "2026-04-21T17:09:15.599Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/6b0eeecfe96d7cce1d71c66b8e03cb304aa70ec11f1955dc1d6b46aca3c3/mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd", size = 10851915, upload-time = "2026-04-21T17:06:03.5Z" }, + { url = "https://files.pythonhosted.org/packages/3c/36/6593dc88545d75fb96416184be5392da5e2a8e8c2802a8597913e16ae25c/mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2", size = 9786676, upload-time = "2026-04-21T17:07:02.035Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4d/9ebeae211caccbdaddde7ed5e31dfcf57faac66be9b11deb1dc6526c8078/mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c", size = 14371307, upload-time = "2026-04-21T17:08:56.442Z" }, + { url = "https://files.pythonhosted.org/packages/95/d7/93473d34b61f04fac1aecc01368485c89c5c4af7a4b9a0cab5d77d04b63f/mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3", size = 13258917, upload-time = "2026-04-21T17:05:50.978Z" }, + { url = "https://files.pythonhosted.org/packages/e2/30/3dd903e8bafb7b5f7bf87fcd58f8382086dea2aa19f0a7b357f21f63071b/mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254", size = 13700516, upload-time = "2026-04-21T17:11:33.161Z" }, + { url = "https://files.pythonhosted.org/packages/07/05/c61a140aba4c729ac7bc99ae26fc627c78a6e08f5b9dd319244ea71a3d7e/mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98", size = 14562889, upload-time = "2026-04-21T17:05:27.674Z" }, + { url = "https://files.pythonhosted.org/packages/fd/87/da78243742ffa8a36d98c3010f0d829f93d5da4e6786f1a1a6f2ad616502/mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac", size = 14803844, upload-time = "2026-04-21T17:10:06.2Z" }, + { url = "https://files.pythonhosted.org/packages/37/52/10a1ddf91b40f843943a3c6db51e2df59c9e237f29d355e95eaab427461f/mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67", size = 10846300, upload-time = "2026-04-21T17:12:23.886Z" }, + { url = "https://files.pythonhosted.org/packages/20/02/f9a4415b664c53bd34d6709be59da303abcae986dc4ac847b402edb6fa1e/mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100", size = 9779498, upload-time = "2026-04-21T17:09:23.695Z" }, + { url = "https://files.pythonhosted.org/packages/71/4e/7560e4528db9e9b147e4c0f22660466bf30a0a1fe3d63d1b9d3b0fd354ee/mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b", size = 14539393, upload-time = "2026-04-21T17:07:12.52Z" }, + { url = "https://files.pythonhosted.org/packages/32/d9/34a5efed8124f5a9234f55ac6a4ced4201e2c5b81e1109c49ad23190ec8c/mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4", size = 13361642, upload-time = "2026-04-21T17:06:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/d1/14/eb377acf78c03c92d566a1510cda8137348215b5335085ef662ab82ecd3a/mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6", size = 13740347, upload-time = "2026-04-21T17:12:04.73Z" }, + { url = "https://files.pythonhosted.org/packages/b9/94/7e4634a32b641aa1c112422eed1bbece61ee16205f674190e8b536f884de/mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066", size = 14734042, upload-time = "2026-04-21T17:07:43.16Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f3/f7e62395cb7f434541b4491a01149a4439e28ace4c0c632bbf5431e92d1f/mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102", size = 14964958, upload-time = "2026-04-21T17:11:00.665Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0d/47e3c3a0ec2a876e35aeac365df3cac7776c36bbd4ed18cc521e1b9d255b/mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9", size = 10911340, upload-time = "2026-04-21T17:10:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b2/6c852d72e0ea8b01f49da817fb52539993cde327e7d010e0103dc12d0dac/mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58", size = 9833947, upload-time = "2026-04-21T17:09:05.267Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c4/b93812d3a192c9bcf5df405bd2f30277cd0e48106a14d1023c7f6ed6e39b/mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026", size = 14524670, upload-time = "2026-04-21T17:10:30.737Z" }, + { url = "https://files.pythonhosted.org/packages/f3/47/42c122501bff18eaf1e8f457f5c017933452d8acdc52918a9f59f6812955/mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943", size = 13336218, upload-time = "2026-04-21T17:08:44.069Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/75bbc92f41725fbd585fb17b440b1119b576105df1013622983e18640a93/mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517", size = 13724906, upload-time = "2026-04-21T17:08:01.02Z" }, + { url = "https://files.pythonhosted.org/packages/a1/32/4c49da27a606167391ff0c39aa955707a00edc500572e562f7c36c08a71f/mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15", size = 14726046, upload-time = "2026-04-21T17:11:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fc/4e354a1bd70216359deb0c9c54847ee6b32ef78dfb09f5131ff99b494078/mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee", size = 14955587, upload-time = "2026-04-21T17:12:16.033Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/c0f2056e9eb8f08c62cafd9715e4584b89132bdc832fcf85d27d07b5f3e5/mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f", size = 10922681, upload-time = "2026-04-21T17:06:35.842Z" }, + { url = "https://files.pythonhosted.org/packages/e5/14/065e333721f05de8ef683d0aa804c23026bcc287446b61cac657b902ccac/mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330", size = 9830560, upload-time = "2026-04-21T17:07:51.023Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d1/b4ec96b0ecc620a4443570c6e95c867903428cfcde4206518eafdd5880c3/mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30", size = 14524561, upload-time = "2026-04-21T17:06:27.325Z" }, + { url = "https://files.pythonhosted.org/packages/3a/63/d2c2ff4fa66bc49477d32dfa26e8a167ba803ea6a69c5efb416036909d30/mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924", size = 13363883, upload-time = "2026-04-21T17:11:11.239Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/983916806bf4eddeaaa2c9230903c3669c6718552a921154e1c5182c701f/mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb", size = 13742945, upload-time = "2026-04-21T17:08:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/19/65/0cd9285ab010ee8214c83d67c6b49417c40d86ce46f1aa109457b5a9b8d7/mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc", size = 14706163, upload-time = "2026-04-21T17:05:15.51Z" }, + { url = "https://files.pythonhosted.org/packages/94/97/48ff3b297cafcc94d185243a9190836fb1b01c1b0918fff64e941e973cc9/mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558", size = 14938677, upload-time = "2026-04-21T17:05:39.562Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a1/1b4233d255bdd0b38a1f284feeb1c143ca508c19184964e22f8d837ec851/mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8", size = 11089322, upload-time = "2026-04-21T17:06:44.29Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/ce7ee2ba36aeb954ba50f18fa25d9c1188578654b97d02a66a15b6f09531/mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3", size = 10017775, upload-time = "2026-04-21T17:07:20.732Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a1/9d93a7d0b5859af0ead82b4888b46df6c8797e1bc5e1e262a08518c6d48e/mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609", size = 15549002, upload-time = "2026-04-21T17:08:23.107Z" }, + { url = "https://files.pythonhosted.org/packages/00/d2/09a6a10ee1bf0008f6c144d9676f2ca6a12512151b4e0ad0ff6c4fac5337/mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2", size = 14401942, upload-time = "2026-04-21T17:07:31.837Z" }, + { url = "https://files.pythonhosted.org/packages/57/da/9594b75c3c019e805250bed3583bdf4443ff9e6ef08f97e39ae308cb06f2/mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c", size = 15041649, upload-time = "2026-04-21T17:09:34.653Z" }, + { url = "https://files.pythonhosted.org/packages/97/77/f75a65c278e6e8eba2071f7f5a90481891053ecc39878cc444634d892abe/mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744", size = 15864588, upload-time = "2026-04-21T17:11:44.936Z" }, + { url = "https://files.pythonhosted.org/packages/d7/46/1a4e1c66e96c1a3246ddf5403d122ac9b0a8d2b7e65730b9d6533ba7a6d3/mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6", size = 16093956, upload-time = "2026-04-21T17:10:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2c/78a8851264dec38cd736ca5b8bc9380674df0dd0be7792f538916157716c/mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec", size = 12568661, upload-time = "2026-04-21T17:11:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/83/01/cd7318aa03493322ce275a0e14f4f52b8896335e4e79d4fb8153a7ad2b77/mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382", size = 10389240, upload-time = "2026-04-21T17:09:42.719Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/f23c163e25b11074188251b0b5a0342625fc1cdb6af604757174fa9acc9b/mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563", size = 2637314, upload-time = "2026-04-21T17:05:54.5Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "openbb-core" +version = "2.0.0" +source = { editable = "../../core" } +dependencies = [ + { name = "aiohttp" }, + { name = "fastapi" }, + { name = "importlib-metadata" }, + { name = "pydantic" }, + { name = "pyjwt" }, + { name = "python-dotenv" }, + { name = "python-multipart" }, + { name = "requests" }, + { name = "ruff" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "uuid7" }, + { name = "uvicorn" }, + { name = "websockets" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = ">=3.13.3" }, + { name = "fastapi", specifier = ">=0.136.3" }, + { name = "importlib-metadata", specifier = ">=6.8.0" }, + { name = "pandas", marker = "extra == 'pandas'", specifier = ">=1.5.3" }, + { name = "pydantic", specifier = ">=2.12.3,<3.0.0" }, + { name = "pyjwt", specifier = ">=2.13.0" }, + { name = "python-dotenv", specifier = ">=1.0.0" }, + { name = "python-multipart", specifier = ">=0.0.29" }, + { name = "requests", specifier = ">=2.34.2" }, + { name = "ruff", specifier = ">=0.15,<0.16" }, + { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0.0" }, + { name = "uuid7", specifier = ">=0.1.0" }, + { name = "uvicorn", specifier = ">=0.48.0" }, + { name = "websockets", specifier = ">=15.0" }, +] +provides-extras = ["pandas"] + +[package.metadata.requires-dev] +dev = [{ name = "openbb-devtools", editable = "../devtools" }] + +[[package]] +name = "openbb-devtools" +version = "2.0.0" +source = { editable = "../devtools" } +dependencies = [ + { name = "aiohttp" }, + { name = "codespell" }, + { name = "ipykernel" }, + { name = "pre-commit" }, + { name = "pydocstyle" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-freezer" }, + { name = "pytest-order" }, + { name = "pytest-recorder" }, + { name = "pytest-subtests" }, + { name = "ruff" }, + { name = "tox" }, + { name = "ty" }, + { name = "types-python-dateutil" }, + { name = "types-requests" }, + { name = "types-toml" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = "<3.14" }, + { name = "codespell", specifier = ">=2.2.5,<3.0.0" }, + { name = "ipykernel", specifier = ">=6.30.1" }, + { name = "pre-commit", specifier = ">=3.5.0,<4.0.0" }, + { name = "pydocstyle", specifier = ">=6.3.0,<7.0.0" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "pytest-asyncio", specifier = ">=1.3.0,<2.0.0" }, + { name = "pytest-cov", specifier = ">=4.1.0,<5.0.0" }, + { name = "pytest-freezer", specifier = ">=0.4.9,<1.0.0" }, + { name = "pytest-order", specifier = ">=1.3.0,<2.0.0" }, + { name = "pytest-recorder", specifier = ">=0.6.5" }, + { name = "pytest-subtests", specifier = ">=0.11.0,<1.0.0" }, + { name = "ruff", specifier = ">=0.15,<0.16" }, + { name = "tox", specifier = ">=4.11.3,<5.0.0" }, + { name = "ty", specifier = ">=0.0.40" }, + { name = "types-python-dateutil", specifier = ">=2.8.19.14,<3.0.0" }, + { name = "types-requests", specifier = ">=2.32,<3.0.0" }, + { name = "types-toml", specifier = ">=0.10.8.7,<1.0.0" }, +] + +[[package]] +name = "openbb-platform-api" +version = "2.0.0" +source = { editable = "." } +dependencies = [ + { name = "deepdiff" }, + { name = "openbb-core" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "httpx" }, + { name = "openbb-devtools" }, +] + +[package.metadata] +requires-dist = [ + { name = "deepdiff", specifier = ">=8.6.2" }, + { name = "openbb-core", editable = "../../core" }, + { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "httpx", specifier = ">=0.28.0,<1.0.0" }, + { name = "openbb-devtools", editable = "../devtools" }, +] + +[[package]] +name = "orderly-set" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/88/39c83c35d5e97cc203e9e77a4f93bf87ec89cf6a22ac4818fdcc65d66584/orderly_set-5.5.0.tar.gz", hash = "sha256:e87185c8e4d8afa64e7f8160ee2c542a475b738bc891dc3f58102e654125e6ce", size = 27414, upload-time = "2025-07-10T20:10:55.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl", hash = "sha256:46f0b801948e98f427b412fcabb831677194c05c3b699b80de260374baa0b1e7", size = 13068, upload-time = "2025-07-10T20:10:54.377Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/10/97ee2fa54dff1e9da9badbc5e35d0bbaef0776271ea5907eccf64140f72f/pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", size = 177815, upload-time = "2024-07-28T19:59:01.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/92/caae8c86e94681b42c246f0bca35c059a2f0529e5b92619f6aba4cf7e7b6/pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f", size = 204643, upload-time = "2024-07-28T19:58:59.335Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db", size = 79534, upload-time = "2025-10-08T19:46:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8", size = 45526, upload-time = "2025-10-08T19:46:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925", size = 47263, upload-time = "2025-10-08T19:46:05.405Z" }, + { url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21", size = 201012, upload-time = "2025-10-08T19:46:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5", size = 209491, upload-time = "2025-10-08T19:46:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db", size = 215319, upload-time = "2025-10-08T19:46:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7", size = 196856, upload-time = "2025-10-08T19:46:12.003Z" }, + { url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4", size = 193241, upload-time = "2025-10-08T19:46:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60", size = 190552, upload-time = "2025-10-08T19:46:14.938Z" }, + { url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f", size = 200113, upload-time = "2025-10-08T19:46:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900", size = 200778, upload-time = "2025-10-08T19:46:18.023Z" }, + { url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c", size = 193047, upload-time = "2025-10-08T19:46:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb", size = 38093, upload-time = "2025-10-08T19:46:20.643Z" }, + { url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37", size = 41638, upload-time = "2025-10-08T19:46:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581", size = 38229, upload-time = "2025-10-08T19:46:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { 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" } +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" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +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/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { 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" }, +] + +[[package]] +name = "pydocstyle" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "snowballstemmer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/d5385ca59fd065e3c6a5fe19f9bc9d5ea7f2509fa8c9c22fb6b2031dd953/pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1", size = 36796, upload-time = "2023-01-17T20:29:19.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/ea/99ddefac41971acad68f14114f38261c1f27dac0b3ec529824ebc739bdaa/pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019", size = 38038, upload-time = "2023-01-17T20:29:18.094Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, +] + +[[package]] +name = "pylint" +version = "3.3.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "dill" }, + { name = "isort" }, + { name = "mccabe" }, + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/9d/81c84a312d1fa8133b0db0c76148542a98349298a01747ab122f9314b04e/pylint-3.3.9.tar.gz", hash = "sha256:d312737d7b25ccf6b01cc4ac629b5dcd14a0fcf3ec392735ac70f137a9d5f83a", size = 1525946, upload-time = "2025-10-05T18:41:43.786Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/a7/69460c4a6af7575449e615144aa2205b89408dc2969b87bc3df2f262ad0b/pylint-3.3.9-py3-none-any.whl", hash = "sha256:01f9b0462c7730f94786c283f3e52a1fbdf0494bbe0971a78d7277ef46a751e7", size = 523465, upload-time = "2025-10-05T18:41:41.766Z" }, +] + +[[package]] +name = "pyproject-api" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/7b/c0e1333b61d41c69e59e5366e727b18c4992688caf0de1be10b3e5265f6b/pyproject_api-1.10.0.tar.gz", hash = "sha256:40c6f2d82eebdc4afee61c773ed208c04c19db4c4a60d97f8d7be3ebc0bbb330", size = 22785, upload-time = "2025-10-09T19:12:27.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl", hash = "sha256:8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09", size = 13218, upload-time = "2025-10-09T19:12:24.428Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6", size = 63245, upload-time = "2023-05-24T18:44:56.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a", size = 21949, upload-time = "2023-05-24T18:44:54.079Z" }, +] + +[[package]] +name = "pytest-freezer" +version = "0.4.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "freezegun" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/f0/98dcbc5324064360b19850b14c84cea9ca50785d921741dbfc442346e925/pytest_freezer-0.4.9.tar.gz", hash = "sha256:21bf16bc9cc46bf98f94382c4b5c3c389be7056ff0be33029111ae11b3f1c82a", size = 3177, upload-time = "2024-12-12T08:53:08.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/e9/30252bc05bcf67200a17f4f0b4cc7598f0a68df4fa9fa356193aa899f145/pytest_freezer-0.4.9-py3-none-any.whl", hash = "sha256:8b6c50523b7d4aec4590b52bfa5ff766d772ce506e2bf4846c88041ea9ccae59", size = 3192, upload-time = "2024-12-12T08:53:07.641Z" }, +] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, +] + +[[package]] +name = "pytest-order" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/76/c7b4a2ad3758a3c2757f532c6e931aa5e387781512a71b67b6ddc19d9ef8/pytest_order-1.4.0.tar.gz", hash = "sha256:327fb6eee1ae771051da13d2a0d9306d947e87f9ab8f4d6302e5d122c7472691", size = 49891, upload-time = "2026-04-26T12:37:43.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d0/c62c07141151f259faddff6bd591f28235c37dd0c486160d0d2a0d4e6e5a/pytest_order-1.4.0-py3-none-any.whl", hash = "sha256:05b1710cf16bb2123294eec5bdfaee513322ff1926c0dfa86eb8be632eb264a1", size = 14918, upload-time = "2026-04-26T12:37:41.123Z" }, +] + +[[package]] +name = "pytest-recorder" +version = "0.6.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "black" }, + { name = "cryptography" }, + { name = "idna" }, + { name = "mypy" }, + { name = "pylint" }, + { name = "pytest" }, + { name = "pytest-mock" }, + { name = "time-machine" }, + { name = "vcrpy", marker = "platform_python_implementation == 'CPython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/7e/5400e95bf66a29703e8a89da065e1b66b9f8ce1707635f49ef0bb8849771/pytest_recorder-0.6.5.tar.gz", hash = "sha256:b24c9c4d3f4f1245f3e0467218dee4ad0a807b086a6beff508be77fbb8071517", size = 16990, upload-time = "2026-04-13T19:00:17.187Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/31/e185d05db20d15ee69d479f39298322a0d5e395ec4c050bf8d74c027f234/pytest_recorder-0.6.5-py3-none-any.whl", hash = "sha256:c35a36439ca1e9d781344d56faffbe07eee70f976afa3ebd1acd0b59277070ea", size = 22019, upload-time = "2026-04-13T19:00:18.679Z" }, +] + +[[package]] +name = "pytest-subtests" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/d9/20097971a8d315e011e055d512fa120fd6be3bdb8f4b3aa3e3c6bf77bebc/pytest_subtests-0.15.0.tar.gz", hash = "sha256:cb495bde05551b784b8f0b8adfaa27edb4131469a27c339b80fd8d6ba33f887c", size = 18525, upload-time = "2025-10-20T16:26:18.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/cc5a8653e9a24f6cf84768f05064aa8ed5a83dcefd5e2a043db14a1c5f44/python_discovery-1.3.0.tar.gz", hash = "sha256:d098f1e86be5d45fe4d14bf1029294aabbd332f4321179dec85e76cddce834b0", size = 63925, upload-time = "2026-05-05T14:38:39.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl", hash = "sha256:441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f", size = 33124, upload-time = "2026-05-05T14:38:38.539Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, + { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, + { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, + { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, + { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, + { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, + { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "starlette" +version = "0.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + +[[package]] +name = "time-machine" +version = "2.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/1b5fdd165f61b67f445fac2a7feb0c655118edef429cd09ff5a8067f7f1d/time_machine-2.19.0.tar.gz", hash = "sha256:7c5065a8b3f2bbb449422c66ef71d114d3f909c276a6469642ecfffb6a0fcd29", size = 14576, upload-time = "2025-08-19T17:22:08.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/8f/19125611ebbcb3a14da14cd982b9eb4573e2733db60c9f1fbf6a39534f40/time_machine-2.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5169018ef47206997b46086ce01881cd3a4666fd2998c9d76a87858ca3e49e9", size = 19659, upload-time = "2025-08-19T17:20:30.062Z" }, + { url = "https://files.pythonhosted.org/packages/74/da/9b0a928321e7822a3ff96dbd1eae089883848e30e9e1b149b85fb96ba56b/time_machine-2.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85bb7ed440fccf6f6d0c8f7d68d849e7c3d1f771d5e0b2cdf871fa6561da569f", size = 15157, upload-time = "2025-08-19T17:20:31.931Z" }, + { url = "https://files.pythonhosted.org/packages/36/ff/d7e943422038f5f2161fe2c2d791e64a45be691ef946020b20f3a6efc4d4/time_machine-2.19.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a3b12028af1cdc09ccd595be2168b7b26f206c1e190090b048598fbe278beb8e", size = 32860, upload-time = "2025-08-19T17:20:33.241Z" }, + { url = "https://files.pythonhosted.org/packages/fc/80/2b0f1070ed9808ee7da7a6da62a4a0b776957cb4d861578348f86446e778/time_machine-2.19.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c261f073086cf081d1443cbf7684148c662659d3d139d06b772bfe3fe7cc71a6", size = 34510, upload-time = "2025-08-19T17:20:34.221Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b4/48038691c8d89924b36c83335a73adeeb68c884f5a1da08a5b17b8a956f3/time_machine-2.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:011954d951230a9f1079f22b39ed1a3a9abb50ee297dfb8c557c46351659d94d", size = 36204, upload-time = "2025-08-19T17:20:35.163Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/60e8adb541df195e83cb74b720b2cfb1f22ed99c5a7f8abf2a9ab3442cb5/time_machine-2.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b0f83308b29c7872006803f2e77318874eb84d0654f2afe0e48e3822e7a2e39b", size = 34936, upload-time = "2025-08-19T17:20:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/5e/72/e8cee59c6cd99dd3b25b8001a0253e779a286aa8f44d5b40777cbd66210b/time_machine-2.19.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:39733ef844e2984620ec9382a42d00cccc4757d75a5dd572be8c2572e86e50b9", size = 32932, upload-time = "2025-08-19T17:20:37.901Z" }, + { url = "https://files.pythonhosted.org/packages/2c/eb/83f300d93c1504965d944e03679f1c943a923bce2d0fdfadef0e2e22cc13/time_machine-2.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8db99f6334432e9ffbf00c215caf2ae9773f17cec08304d77e9e90febc3507b", size = 34010, upload-time = "2025-08-19T17:20:39.202Z" }, + { url = "https://files.pythonhosted.org/packages/e1/77/f35f2500e04daac5033a22fbfd17e68467822b8406ee77966bf222ccaa26/time_machine-2.19.0-cp310-cp310-win32.whl", hash = "sha256:72bf66cd19e27ffd26516b9cbe676d50c2e0b026153289765dfe0cf406708128", size = 17121, upload-time = "2025-08-19T17:20:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/db/df/32d3e0404be1760a64a44caab2af34b07e952bfe00a23134fea9ddba3e8a/time_machine-2.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:46f1c945934ce3d6b4f388b8e581fce7f87ec891ea90d7128e19520e434f96f0", size = 17957, upload-time = "2025-08-19T17:20:41.079Z" }, + { url = "https://files.pythonhosted.org/packages/66/df/598a71a1afb4b509a4587273b76590b16d9110a3e9106f01eedc68d02bb2/time_machine-2.19.0-cp310-cp310-win_arm64.whl", hash = "sha256:fb4897c7a5120a4fd03f0670f332d83b7e55645886cd8864a71944c4c2e5b35b", size = 16821, upload-time = "2025-08-19T17:20:41.967Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ed/4815ebcc9b6c14273f692b9be38a9b09eae52a7e532407cc61a51912b121/time_machine-2.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5ee91664880434d98e41585c3446dac7180ec408c786347451ddfca110d19296", size = 19342, upload-time = "2025-08-19T17:20:43.207Z" }, + { url = "https://files.pythonhosted.org/packages/ee/08/154cce8b11b60d8238b0b751b8901d369999f4e8f7c3a5f917caa5d95b0b/time_machine-2.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed3732b83a893d1c7b8cabde762968b4dc5680ee0d305b3ecca9bb516f4e3862", size = 14978, upload-time = "2025-08-19T17:20:44.134Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b7/b689d8c8eeca7af375cfcd64973e49e83aa817cc00f80f98548d42c0eb50/time_machine-2.19.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6ba0303e9cc9f7f947e344f501e26bedfb68fab521e3c2729d370f4f332d2d55", size = 30964, upload-time = "2025-08-19T17:20:45.366Z" }, + { url = "https://files.pythonhosted.org/packages/80/91/38bf9c79674e95ce32e23c267055f281dff651eec77ed32a677db3dc011a/time_machine-2.19.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2851825b524a988ee459c37c1c26bdfaa7eff78194efb2b562ea497a6f375b0a", size = 32606, upload-time = "2025-08-19T17:20:46.693Z" }, + { url = "https://files.pythonhosted.org/packages/19/4a/e9222d85d4de68975a5e799f539a9d32f3a134a9101fca0a61fa6aa33d68/time_machine-2.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68d32b09ecfd7fef59255c091e8e7c24dd117f882c4880b5c7ab8c5c32a98f89", size = 34405, upload-time = "2025-08-19T17:20:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/09480d608d42d6876f9ff74593cfc9197a7eb2c31381a74fb2b145575b65/time_machine-2.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60c46ab527bf2fa144b530f639cc9e12803524c9e1f111dc8c8f493bb6586eeb", size = 33181, upload-time = "2025-08-19T17:20:48.937Z" }, + { url = "https://files.pythonhosted.org/packages/84/64/f9359e000fad32d9066305c48abc527241d608bcdf77c19d67d66e268455/time_machine-2.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:56f26ab9f0201c453d18fe76bb7d1cf05fe58c1b9d9cb0c7d243d05132e01292", size = 31036, upload-time = "2025-08-19T17:20:50.276Z" }, + { url = "https://files.pythonhosted.org/packages/71/0d/fab2aacec71e3e482bd7fce0589381f9414a4a97f8766bddad04ad047b7b/time_machine-2.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6c806cf3c1185baa1d807b7f51bed0db7a6506832c961d5d1b4c94c775749bc0", size = 32145, upload-time = "2025-08-19T17:20:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/44/fb/faeba2405fb27553f7b28db441a500e2064ffdb2dcba001ee315fdd2c121/time_machine-2.19.0-cp311-cp311-win32.whl", hash = "sha256:b30039dfd89855c12138095bee39c540b4633cbc3684580d684ef67a99a91587", size = 17004, upload-time = "2025-08-19T17:20:52.38Z" }, + { url = "https://files.pythonhosted.org/packages/2f/84/87e483d660ca669426192969280366635c845c3154a9fe750be546ed3afc/time_machine-2.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:13ed8b34430f1de79905877f5600adffa626793ab4546a70a99fb72c6a3350d8", size = 17822, upload-time = "2025-08-19T17:20:53.348Z" }, + { url = "https://files.pythonhosted.org/packages/41/f4/ebf7bbf5047854a528adaf54a5e8780bc5f7f0104c298ab44566a3053bf8/time_machine-2.19.0-cp311-cp311-win_arm64.whl", hash = "sha256:cc29a50a0257d8750b08056b66d7225daab47606832dea1a69e8b017323bf511", size = 16680, upload-time = "2025-08-19T17:20:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/9b/aa/7e00614d339e4d687f6e96e312a1566022528427d237ec639df66c4547bc/time_machine-2.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c85cf437dc3c07429456d8d6670ac90ecbd8241dcd0fbf03e8db2800576f91ff", size = 19308, upload-time = "2025-08-19T17:20:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/ab/3c/bde3c757394f5bca2fbc1528d4117960a26c38f9b160bf471b38d2378d8f/time_machine-2.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9238897e8ef54acdf59f5dff16f59ca0720e7c02d820c56b4397c11db5d3eb9", size = 15019, upload-time = "2025-08-19T17:20:56.204Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e0/8ca916dd918018352d377f1f5226ee071cfbeb7dbbde2b03d14a411ac2b1/time_machine-2.19.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e312c7d5d6bfffb96c6a7b39ff29e3046de100d7efaa3c01552654cfbd08f14c", size = 33079, upload-time = "2025-08-19T17:20:57.166Z" }, + { url = "https://files.pythonhosted.org/packages/48/69/184a0209f02dd0cb5e01e8d13cd4c97a5f389c4e3d09b95160dd676ad1e7/time_machine-2.19.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:714c40b2c90d1c57cc403382d5a9cf16e504cb525bfe9650095317da3c3d62b5", size = 34925, upload-time = "2025-08-19T17:20:58.117Z" }, + { url = "https://files.pythonhosted.org/packages/43/42/4bbf4309e8e57cea1086eb99052d97ff6ddecc1ab6a3b07aa4512f8bf963/time_machine-2.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2eaa1c675d500dc3ccae19e9fb1feff84458a68c132bbea47a80cc3dd2df7072", size = 36384, upload-time = "2025-08-19T17:20:59.108Z" }, + { url = "https://files.pythonhosted.org/packages/b1/af/9f510dc1719157348c1a2e87423aed406589070b54b503cb237d9bf3a4fe/time_machine-2.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e77a414e9597988af53b2b2e67242c9d2f409769df0d264b6d06fda8ca3360d4", size = 34881, upload-time = "2025-08-19T17:21:00.116Z" }, + { url = "https://files.pythonhosted.org/packages/ca/28/61764a635c70cc76c76ba582dfdc1a84834cddaeb96789023af5214426b2/time_machine-2.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd93996970e11c382b04d4937c3cd0b0167adeef14725ece35aae88d8a01733c", size = 32931, upload-time = "2025-08-19T17:21:01.095Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e0/f028d93b266e6ade8aca5851f76ebbc605b2905cdc29981a2943b43e1a6c/time_machine-2.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e20a6d8d6e23174bd7e931e134d9610b136db460b249d07e84ecdad029ec352", size = 34241, upload-time = "2025-08-19T17:21:02.052Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a6/36d1950ed1d3f613158024cf1dcc73db1d9ef0b9117cf51ef2e37dc06499/time_machine-2.19.0-cp312-cp312-win32.whl", hash = "sha256:95afc9bc65228b27be80c2756799c20b8eb97c4ef382a9b762b6d7888bc84099", size = 17021, upload-time = "2025-08-19T17:21:03.374Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0d/e2dce93355abda3cac69e77fe96566757e98b8fe7fdcbddce89c9ced3f5f/time_machine-2.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:e84909af950e2448f4e2562ea5759c946248c99ab380d2b47d79b62bd76fa236", size = 17857, upload-time = "2025-08-19T17:21:04.331Z" }, + { url = "https://files.pythonhosted.org/packages/eb/28/50ae6fb83b7feeeca7a461c0dc156cf7ef5e6ef594a600d06634fde6a2cb/time_machine-2.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:0390a1ea9fa7e9d772a39b7c61b34fdcca80eb9ffac339cc0441c6c714c81470", size = 16677, upload-time = "2025-08-19T17:21:05.39Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b8/24ebce67aa531bae2cbe164bb3f4abc6467dc31f3aead35e77f5a075ea3e/time_machine-2.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5e172866753e6041d3b29f3037dc47c20525176a494a71bbd0998dfdc4f11f2f", size = 19373, upload-time = "2025-08-19T17:21:06.701Z" }, + { url = "https://files.pythonhosted.org/packages/53/a5/c9a5240fd2f845d3ff9fa26f8c8eaa29f7239af9d65007e61d212250f15b/time_machine-2.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f70f68379bd6f542ae6775cce9a4fa3dcc20bf7959c42eaef871c14469e18097", size = 15056, upload-time = "2025-08-19T17:21:07.667Z" }, + { url = "https://files.pythonhosted.org/packages/b9/92/66cce5d2fb2a5e68459aca85fd18a7e2d216f725988940cd83f96630f2f1/time_machine-2.19.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e69e0b0f694728a00e72891ef8dd00c7542952cb1c87237db594b6b27d504a96", size = 33172, upload-time = "2025-08-19T17:21:08.619Z" }, + { url = "https://files.pythonhosted.org/packages/ae/20/b499e9ab4364cd466016c33dcdf4f56629ca4c20b865bd4196d229f31d92/time_machine-2.19.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3ae0a8b869574301ec5637e32c270c7384cca5cd6e230f07af9d29271a7fa293", size = 35042, upload-time = "2025-08-19T17:21:09.622Z" }, + { url = "https://files.pythonhosted.org/packages/41/32/b252d3d32791eb16c07d553c820dbc33d9c7fa771de3d1c602190bded2b7/time_machine-2.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:554e4317de90e2f7605ff80d153c8bb56b38c0d0c0279feb17e799521e987b8c", size = 36535, upload-time = "2025-08-19T17:21:10.571Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/4d0470062b9742e1b040ab81bad04d1a5d1de09806507bb6188989cfa1a7/time_machine-2.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6567a5ec5538ed550539ac29be11b3cb36af1f9894e2a72940cba0292cc7c3c9", size = 34945, upload-time = "2025-08-19T17:21:11.538Z" }, + { url = "https://files.pythonhosted.org/packages/24/71/2f741b29d98b1c18f6777a32236497c3d3264b6077e431cea4695684c8a1/time_machine-2.19.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82e9ffe8dfff07b0d810a2ad015a82cd78c6a237f6c7cf185fa7f747a3256f8a", size = 33014, upload-time = "2025-08-19T17:21:12.858Z" }, + { url = "https://files.pythonhosted.org/packages/e8/83/ca8dba6106562843fd99f672e5aaf95badbc10f4f13f7cfe8d8640a7019d/time_machine-2.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e1c4e578cdd69b3531d8dd3fbcb92a0cd879dadb912ee37af99c3a9e3c0d285", size = 34350, upload-time = "2025-08-19T17:21:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/21/7f/34fe540450e18d0a993240100e4b86e8d03d831b92af8bb6ddb2662dc6fc/time_machine-2.19.0-cp313-cp313-win32.whl", hash = "sha256:72dbd4cbc3d96dec9dd281ddfbb513982102776b63e4e039f83afb244802a9e5", size = 17047, upload-time = "2025-08-19T17:21:14.874Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5d/c8be73df82c7ebe7cd133279670e89b8b110af3ce1412c551caa9d08e625/time_machine-2.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:e17e3e089ac95f9a145ce07ff615e3c85674f7de36f2d92aaf588493a23ffb4b", size = 17868, upload-time = "2025-08-19T17:21:15.819Z" }, + { url = "https://files.pythonhosted.org/packages/92/13/2dfd3b8fb285308f61cd7aa9bfa96f46ddf916e3549a0f0afd094c556599/time_machine-2.19.0-cp313-cp313-win_arm64.whl", hash = "sha256:149072aff8e3690e14f4916103d898ea0d5d9c95531b6aa0995251c299533f7b", size = 16710, upload-time = "2025-08-19T17:21:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/05/c1/deebb361727d2c5790f9d4d874be1b19afd41f4375581df465e6718b46a2/time_machine-2.19.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f3589fee1ed0ab6ee424a55b0ea1ec694c4ba64cc26895bcd7d99f3d1bc6a28a", size = 20053, upload-time = "2025-08-19T17:21:17.704Z" }, + { url = "https://files.pythonhosted.org/packages/45/e8/fe3376951e6118d8ec1d1f94066a169b791424fe4a26c7dfc069b153ee08/time_machine-2.19.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7887e85275c4975fe54df03dcdd5f38bd36be973adc68a8c77e17441c3b443d6", size = 15423, upload-time = "2025-08-19T17:21:18.668Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c7/f88d95cd1a87c650cf3749b4d64afdaf580297aa18ad7f4b44ec9d252dfc/time_machine-2.19.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ce0be294c209928563fcce1c587963e60ec803436cf1e181acd5bc1e425d554b", size = 39630, upload-time = "2025-08-19T17:21:19.645Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5d/65a5c48a65357e56ec6f032972e4abd1c02d4fca4b0717a3aaefd19014d4/time_machine-2.19.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a62fd1ab380012c86f4c042010418ed45eb31604f4bf4453e17c9fa60bc56a29", size = 41242, upload-time = "2025-08-19T17:21:20.979Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/fe5209e1615fde0a8cad6c4e857157b150333ed1fe31a7632b08cfe0ebdd/time_machine-2.19.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b25ec853a4530a5800731257f93206b12cbdee85ede964ebf8011b66086a7914", size = 44278, upload-time = "2025-08-19T17:21:21.984Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3a/a5e5fe9c5d614cde0a9387ff35e8dfd12c5ef6384e4c1a21b04e6e0b905d/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a430e4d0e0556f021a9c78e9b9f68e5e8910bdace4aa34ed4d1a73e239ed9384", size = 42321, upload-time = "2025-08-19T17:21:23.755Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c5/56eca774e9162bc1ce59111d2bd69140dc8908c9478c92ec7bd15d547600/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2415b7495ec4364c8067071e964fbadfe746dd4cdb43983f2f0bd6ebed13315c", size = 39270, upload-time = "2025-08-19T17:21:26.009Z" }, + { url = "https://files.pythonhosted.org/packages/9b/69/5dd0c420667578169a12acc8c8fd7452e8cfb181e41c9b4ac7e88fa36686/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbfc6b90c10f288594e1bf89a728a98cc0030791fd73541bbdc6b090aff83143", size = 40193, upload-time = "2025-08-19T17:21:27.054Z" }, + { url = "https://files.pythonhosted.org/packages/75/a7/de974d421bd55c9355583427c2a38fb0237bb5fd6614af492ba89dacb2f9/time_machine-2.19.0-cp313-cp313t-win32.whl", hash = "sha256:16f5d81f650c0a4d117ab08036dc30b5f8b262e11a4a0becc458e7f1c011b228", size = 17542, upload-time = "2025-08-19T17:21:28.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/aa0d05becd5d06ae8d3f16d657dc8cc9400c8d79aef80299de196467ff12/time_machine-2.19.0-cp313-cp313t-win_amd64.whl", hash = "sha256:645699616ec14e147094f601e6ab9553ff6cea37fad9c42720a6d7ed04bcd5dc", size = 18703, upload-time = "2025-08-19T17:21:29.663Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c0/f785a4c7c73aa176510f7c48b84b49c26be84af0d534deb222e0327f750e/time_machine-2.19.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b32daa965d13237536ea3afaa5ad61ade2b2d9314bc3a20196a0d2e1d7b57c6a", size = 17020, upload-time = "2025-08-19T17:21:30.653Z" }, + { url = "https://files.pythonhosted.org/packages/ed/97/c5fb51def06c0b2b6735332ad118ab35b4d9b85368792e5b638e99b1b686/time_machine-2.19.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:31cb43c8fd2d961f31bed0ff4e0026964d2b35e5de9e0fabbfecf756906d3612", size = 19360, upload-time = "2025-08-19T17:21:31.94Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4e/2d795f7d6b7f5205ffe737a05bb1cf19d8038233b797062b2ef412b8512b/time_machine-2.19.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:bdf481a75afc6bff3e520db594501975b652f7def21cd1de6aa971d35ba644e6", size = 15033, upload-time = "2025-08-19T17:21:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/dd/32/9bad501e360b4e758c58fae616ca5f8c7ad974b343f2463a15b2bf77a366/time_machine-2.19.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:00bee4bb950ac6a08d62af78e4da0cf2b4fc2abf0de2320d0431bf610db06e7c", size = 33379, upload-time = "2025-08-19T17:21:33.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/45/eda0ca4d793dfd162478d6163759b1c6ce7f6e61daa7fd7d62b31f21f87f/time_machine-2.19.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f02199490906582302ce09edd32394fb393271674c75d7aa76c7a3245f16003", size = 35123, upload-time = "2025-08-19T17:21:34.945Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5a/97e16325442ae5731fcaac794f0a1ef9980eff8a5491e58201d7eb814a34/time_machine-2.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e35726c7ba625f844c13b1fc0d4f81f394eefaee1d3a094a9093251521f2ef15", size = 36588, upload-time = "2025-08-19T17:21:35.975Z" }, + { url = "https://files.pythonhosted.org/packages/e8/9d/bf0b2ccc930cc4a316f26f1c78d3f313cd0fa13bb7480369b730a8f129db/time_machine-2.19.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:304315023999cd401ff02698870932b893369e1cfeb2248d09f6490507a92e97", size = 35013, upload-time = "2025-08-19T17:21:37.017Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5a/39ac6a3078174f9715d88364871348b249631f12e76de1b862433b3f8862/time_machine-2.19.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9765d4f003f263ea8bfd90d2d15447ca4b3dfa181922cf6cf808923b02ac180a", size = 33303, upload-time = "2025-08-19T17:21:38.352Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ac/d8646baf9f95f2e792a6d7a7b35e92fca253c4a992afff801beafae0e5c2/time_machine-2.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7837ef3fd5911eb9b480909bb93d922737b6bdecea99dfcedb0a03807de9b2d3", size = 34440, upload-time = "2025-08-19T17:21:39.382Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8b/8b6568c5ae966d80ead03ab537be3c6acf2af06fb501c2d466a3162c6295/time_machine-2.19.0-cp314-cp314-win32.whl", hash = "sha256:4bb5bd43b1bdfac3007b920b51d8e761f024ed465cfeec63ac4296922a4ec428", size = 17162, upload-time = "2025-08-19T17:21:40.381Z" }, + { url = "https://files.pythonhosted.org/packages/46/a5/211c1ab4566eba5308b2dc001b6349e3a032e3f6afa67ca2f27ea6b27af5/time_machine-2.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:f583bbd0aa8ab4a7c45a684bf636d9e042d466e30bcbae1d13e7541e2cbe7207", size = 18040, upload-time = "2025-08-19T17:21:41.363Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fc/4c2fb705f6371cb83824da45a8b967514a922fc092a0ef53979334d97a70/time_machine-2.19.0-cp314-cp314-win_arm64.whl", hash = "sha256:f379c6f8a6575a8284592179cf528ce89373f060301323edcc44f1fa1d37be12", size = 16752, upload-time = "2025-08-19T17:21:42.336Z" }, + { url = "https://files.pythonhosted.org/packages/79/ab/6437d18f31c666b5116c97572a282ac2590a82a0a9867746a6647eaf4613/time_machine-2.19.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a3b8981f9c663b0906b05ab4d0ca211fae4b63b47c6ec26de5374fe56c836162", size = 20057, upload-time = "2025-08-19T17:21:43.35Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a2/e03639ec2ba7200328bbcad8a2b2b1d5fccca9cceb9481b164a1cabdcb33/time_machine-2.19.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8e9c6363893e7f52c226afbebb23e825259222d100e67dfd24c8a6d35f1a1907", size = 15430, upload-time = "2025-08-19T17:21:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ff/39e63a48e840f3e36ce24846ee51dd99c6dba635659b1750a2993771e88e/time_machine-2.19.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:206fcd6c9a6f00cac83db446ad1effc530a8cec244d2780af62db3a2d0a9871b", size = 39622, upload-time = "2025-08-19T17:21:45.821Z" }, + { url = "https://files.pythonhosted.org/packages/9a/2e/ee5ac79c4954768705801e54817c7d58e07e25a0bb227e775f501f3e2122/time_machine-2.19.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf33016a1403c123373ffaeff25e26e69d63bf2c63b6163932efed94160db7ef", size = 41235, upload-time = "2025-08-19T17:21:46.783Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3e/9af5f39525e779185c77285b8bbae15340eeeaa0afb33d458bc8b47d459b/time_machine-2.19.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9247c4bb9bbd3ff584ef4efbdec8efd9f37aa08bcfc4728bde1e489c2cb445bd", size = 44276, upload-time = "2025-08-19T17:21:47.759Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/572c7443cc27140bbeae3947279bbd4a120f9e8622253a20637f260b7813/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:77f9bb0b86758d1f2d9352642c874946ad5815df53ef4ca22eb9d532179fe50d", size = 42330, upload-time = "2025-08-19T17:21:48.881Z" }, + { url = "https://files.pythonhosted.org/packages/cf/24/1a81c2e08ee7dae13ec8ceed27a29afa980c3d63852e42f1e023bf0faa03/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0b529e262df3b9c449f427385f4d98250828c879168c2e00eec844439f40b370", size = 39281, upload-time = "2025-08-19T17:21:49.907Z" }, + { url = "https://files.pythonhosted.org/packages/d2/60/6f0d6e5108978ca1a2a4ffb4d1c7e176d9199bb109fd44efe2680c60b52a/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9199246e31cdc810e5d89cb71d09144c4d745960fdb0824da4994d152aca3303", size = 40201, upload-time = "2025-08-19T17:21:50.953Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/3ea4951e8293b0643feb98c0b9a176fa822154f1810835db3f282968ab10/time_machine-2.19.0-cp314-cp314t-win32.whl", hash = "sha256:0fe81bae55b7aefc2c2a34eb552aa82e6c61a86b3353a3c70df79b9698cb02ca", size = 17743, upload-time = "2025-08-19T17:21:51.948Z" }, + { url = "https://files.pythonhosted.org/packages/e4/8b/cd802884ca8a98e2b6cdc2397d57dd12ff8a7d1481e06fc3fad3d4e7e5ff/time_machine-2.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7253791b8d7e7399fbeed7a8193cb01bc004242864306288797056badbdaf80b", size = 18956, upload-time = "2025-08-19T17:21:52.997Z" }, + { url = "https://files.pythonhosted.org/packages/c6/49/cabb1593896082fd55e34768029b8b0ca23c9be8b2dc127e0fc14796d33e/time_machine-2.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:536bd1ac31ab06a1522e7bf287602188f502dc19d122b1502c4f60b1e8efac79", size = 17068, upload-time = "2025-08-19T17:21:54.064Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, + { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, + { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, + { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, +] + +[[package]] +name = "tox" +version = "4.53.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "python-discovery" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomli-w" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/d7/a8e0f889eb6872740e2f013a93a8f9c6c23c3f02fe0911bbd91673615636/tox-4.53.1.tar.gz", hash = "sha256:7be9805ed4a34242510c7acc9a7e3a01a35942e08f31f8bd69067c3a37130afc", size = 276809, upload-time = "2026-05-02T08:34:41.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/a7/5719188f4ace9445b0fb139290e2abce14cc19e6a5bd616e72dad085ebe8/tox-4.53.1-py3-none-any.whl", hash = "sha256:4a9948607e976a337c22d64a1b4fafd486125e82f00ab6ce32fa6cacc23f48b1", size = 213827, upload-time = "2026-05-02T08:34:39.786Z" }, +] + +[[package]] +name = "traitlets" +version = "5.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/22/40f55b26baeab80c2d7b3f1db0682f8954e4617fee7d90ce634022ef05c6/traitlets-5.15.0.tar.gz", hash = "sha256:4fead733f81cf1c4c938e06f8ca4633896833c9d89eff878159457f4d4392971", size = 163197, upload-time = "2026-05-06T08:05:58.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/98/a9937a969d018a23badfea0b381f66783649d48e0ea6c41923265c3cbeb3/traitlets-5.15.0-py3-none-any.whl", hash = "sha256:fb36a18867a6803deab09f3c5e0fa81bb7b26a5c9e82501c9933f759166eff40", size = 85877, upload-time = "2026-05-06T08:05:55.853Z" }, +] + +[[package]] +name = "ty" +version = "0.0.51" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/ce/352fcdba5c72ea20e5d2e46e28809cdb617575b71209d971eff2ace8e6c4/ty-0.0.51.tar.gz", hash = "sha256:b90172d46365bb9d51a7011cbb5c60cc4f514f42c86635df6c092b717f85e1ac", size = 5953151, upload-time = "2026-06-19T01:48:58.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/8f/8fe7cab79a45320b2cdcd602f16d44c8108d2f418ff7ec316c6212f1f0cc/ty-0.0.51-py3-none-linux_armv6l.whl", hash = "sha256:947986bd82d324b3a5c58ce03f1dad160cdf36443d3e8f64b3484b861ba9bc64", size = 11884805, upload-time = "2026-06-19T01:48:20.184Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/56fdc39a3f44c0564fd157e1e59e1f9c3fc5ba57ae4472ded85c67c63d74/ty-0.0.51-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25a5b31e6f23fd5dc63ad29087ded09932409e4154e2fe07bbaed015035990bb", size = 11633593, upload-time = "2026-06-19T01:48:22.998Z" }, + { url = "https://files.pythonhosted.org/packages/33/57/136e83f24fc04f5afdcabff42f40fa27eae5ac3f0e3f12627d072a55f679/ty-0.0.51-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2faed19a8f1505370de071c008df52a994fc03a204f3267c3a33a32ca26f854f", size = 11063076, upload-time = "2026-06-19T01:48:25.223Z" }, + { url = "https://files.pythonhosted.org/packages/32/f8/5d32f0df5692446440ab781b9b119aa3e0c0dbfa78c583fe9be8417d54fa/ty-0.0.51-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08adbe53fb8bc9e7f00e89bf1d3c875a02cda76d83f109d2e6ab1ff35a7bfa8c", size = 11579542, upload-time = "2026-06-19T01:48:27.302Z" }, + { url = "https://files.pythonhosted.org/packages/7f/0c/4f54ef338e9623886809ecd508931b0cd5b3aba1e591586a2f6aeaa8bd11/ty-0.0.51-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc5e93695ab5dcbf1eef663aee60ec23a413547cc9cb06adcb0d842e9166bd0f", size = 11676189, upload-time = "2026-06-19T01:48:29.518Z" }, + { url = "https://files.pythonhosted.org/packages/56/27/31729066f9b9d3596941edaf267894eefc0b30df4518f003dba5f7276258/ty-0.0.51-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd92913bc90d1705ef9391ff8c6822b61e2e827fa295eb30bf0dfabcf815645", size = 12188154, upload-time = "2026-06-19T01:48:31.68Z" }, + { url = "https://files.pythonhosted.org/packages/2f/38/d4301aa12d2283c7130908baf1417a37dfe3e10f5669cb4ce2853c2540b4/ty-0.0.51-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:429a997394dac73870d71b87cc90efc54da3efaf319e72ca18aeef35a78aef90", size = 12780597, upload-time = "2026-06-19T01:48:33.839Z" }, + { url = "https://files.pythonhosted.org/packages/c1/52/4b2e67e53f126d39abe201bd2299e467e27463a284e965ad195cbc217fa0/ty-0.0.51-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62d94f06e8c317e89b6884f2bde443040e596b88c7c79bd944c84c105b06257a", size = 12491115, upload-time = "2026-06-19T01:48:36.169Z" }, + { url = "https://files.pythonhosted.org/packages/74/50/aabfe55c132ebe72b4d639cbf772d931e11b0990d29c1f691922b6ccabc1/ty-0.0.51-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8f52952cff665bc52a36147e610c10f5699d30007d7a14ab7f345cff93476ff", size = 12230135, upload-time = "2026-06-19T01:48:38.445Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1b/9aa428052dbed91c50919cd080426a313cf20ce14c6bfe2b71345e548671/ty-0.0.51-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c1bd1355aee86af01e4e21b0bc16fc460fb05905761f0d8b8d70841de0feade8", size = 12468123, upload-time = "2026-06-19T01:48:40.47Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5a/f6ce69f2575259386c950c40e02578d0902760cb61f95045e9971182c24e/ty-0.0.51-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:79d1877e93460f936bc10ed1a31525702b7ce51075763ccba993be17f0b9e905", size = 11541672, upload-time = "2026-06-19T01:48:42.635Z" }, + { url = "https://files.pythonhosted.org/packages/35/3a/2af48924a683e959e95e5cc4dc88e5a8595206a0812b869032b95196f2b0/ty-0.0.51-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:cc233a6235fb23e2a44b14731a10043e37ba2f30f2c361cf49ad3633c5b9da9c", size = 11694015, upload-time = "2026-06-19T01:48:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/12/899875d8a60b198c8121cb92ce18e18cc072d23ca2130fcdaa176383ef72/ty-0.0.51-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bc7459348a253247bbfb2669a021e614281b86bbea24c36112b8a6e1a2499a16", size = 11832856, upload-time = "2026-06-19T01:48:47.028Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a2/88f681d826d97cc96ef9f6cadd4935f775758944cee07340aa46113bce28/ty-0.0.51-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49a21237f6fd1de56beaff0a3e85fe022a09a3401e67e3abec41ce838a5d4d2e", size = 12333449, upload-time = "2026-06-19T01:48:49.091Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/535a4163b4452c6978c31fedfd7b5803cf3a2253e9455cde350f86638d6a/ty-0.0.51-py3-none-win32.whl", hash = "sha256:61b4b6a003c3ebe53a63a1125c9b6542aa01bc1b6c9a235d01ee328d000d61a9", size = 11177338, upload-time = "2026-06-19T01:48:51.433Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4d/2334fbb74291a20129fa7aaa8f789619ec9b6883b27f997b8baa27e4674f/ty-0.0.51-py3-none-win_amd64.whl", hash = "sha256:608d417cd1eaf79bcbd713d9830d5e3db9d57ec225c3af3e4ac9a9ff66b45d70", size = 12325675, upload-time = "2026-06-19T01:48:53.774Z" }, + { url = "https://files.pythonhosted.org/packages/50/b5/d49096cd5f3694becb86a5a6ccd0f229ead695fc7430d6bc4dd0a104c6fe/ty-0.0.51-py3-none-win_arm64.whl", hash = "sha256:62ced5e380284f12b2dc4802a3e4ed3dac39913fc6719afde7978814a4c7f169", size = 11657350, upload-time = "2026-06-19T01:48:55.904Z" }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20260408" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/f3/2427775f80cd5e19a0a71ba8e5ab7645a01a852f43a5fd0ffc24f66338e0/types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582", size = 16981, upload-time = "2026-04-08T04:28:10.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/c6/eeba37bfee282a6a97f889faef9352d6172c6a5088eb9a4daf570d9d748d/types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f", size = 18437, upload-time = "2026-04-08T04:28:10.095Z" }, +] + +[[package]] +name = "types-requests" +version = "2.33.0.20260518" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/01/c5a19253fe1ac159159ddf9a3a07cec8bb5e486ec4d9002ad2821da0e5d2/types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e", size = 24752, upload-time = "2026-05-18T06:07:37.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/bc/b139710a3b6018f7fb2b9508b35c8af564e61bf2bf4fa619d088f3e16f85/types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0", size = 21391, upload-time = "2026-05-18T06:07:37.044Z" }, +] + +[[package]] +name = "types-toml" +version = "0.10.8.20260408" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/9b/887564a51a84c96ba08b715570e546f0ea793df6372b736bfbc596ca5536/types_toml-0.10.8.20260408.tar.gz", hash = "sha256:6b30b031235565a12febb1388900b129f1adeabfcfa594da46d0372b2ac107ad", size = 9341, upload-time = "2026-04-08T04:27:54.394Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/f1/942d95ba026779bc6e3064f8b094216588dc3276cc328cf8e03a0541918d/types_toml-0.10.8.20260408-py3-none-any.whl", hash = "sha256:e958d4c660385e548705a298f17dc162baf44c8b6d6aff79aeefe75f4f77ac87", size = 9677, upload-time = "2026-04-08T04:27:53.526Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "uuid7" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/19/7472bd526591e2192926247109dbf78692e709d3e56775792fec877a7720/uuid7-0.1.0.tar.gz", hash = "sha256:8c57aa32ee7456d3cc68c95c4530bc571646defac01895cfc73545449894a63c", size = 14052, upload-time = "2021-12-29T01:38:21.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/77/8852f89a91453956582a85024d80ad96f30a41fed4c2b3dce0c9f12ecc7e/uuid7-0.1.0-py2.py3-none-any.whl", hash = "sha256:5e259bb63c8cb4aded5927ff41b444a80d0c7124e8a0ced7cf44efa1f5cccf61", size = 7477, upload-time = "2021-12-29T01:38:20.418Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, +] + +[[package]] +name = "vcrpy" +version = "8.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/07/bcfd5ebd7cb308026ab78a353e091bd699593358be49197d39d004e5ad83/vcrpy-8.1.1.tar.gz", hash = "sha256:58e3053e33b423f3594031cb758c3f4d1df931307f1e67928e30cf352df7709f", size = 85770, upload-time = "2026-01-04T19:22:03.886Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d7/f79b05a5d728f8786876a7d75dfb0c5cae27e428081b2d60152fb52f155f/vcrpy-8.1.1-py3-none-any.whl", hash = "sha256:2d16f31ad56493efb6165182dd99767207031b0da3f68b18f975545ede8ac4b9", size = 42445, upload-time = "2026-01-04T19:22:02.532Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/0d/915c02c94d207b85580eb09bffab54438a709e7288524094fe781da526c2/virtualenv-21.3.1.tar.gz", hash = "sha256:c2305bc1fddeec40699b8370d13f8d431b0701f00ce895061ce493aeded4426b", size = 7613791, upload-time = "2026-05-05T01:34:31.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl", hash = "sha256:d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35", size = 7594539, upload-time = "2026-05-05T01:34:28.98Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" }, + { url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" }, + { url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" }, + { url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" }, + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] + +[[package]] +name = "wrapt" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/d2/387594fb592d027366645f3d7cc9b4d7ca7be93845fbaba6d835a912ef3c/wrapt-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a86d99a14f76facb269dc148590c01aaf47584071809a70da30555228158c", size = 60669, upload-time = "2026-03-06T02:52:40.671Z" }, + { url = "https://files.pythonhosted.org/packages/c9/18/3f373935bc5509e7ac444c8026a56762e50c1183e7061797437ca96c12ce/wrapt-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a819e39017f95bf7aede768f75915635aa8f671f2993c036991b8d3bfe8dbb6f", size = 61603, upload-time = "2026-03-06T02:54:21.032Z" }, + { url = "https://files.pythonhosted.org/packages/c2/7a/32758ca2853b07a887a4574b74e28843919103194bb47001a304e24af62f/wrapt-2.1.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5681123e60aed0e64c7d44f72bbf8b4ce45f79d81467e2c4c728629f5baf06eb", size = 113632, upload-time = "2026-03-06T02:53:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d5/eeaa38f670d462e97d978b3b0d9ce06d5b91e54bebac6fbed867809216e7/wrapt-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8b28e97a44d21836259739ae76284e180b18abbb4dcfdff07a415cf1016c3e", size = 115644, upload-time = "2026-03-06T02:54:53.33Z" }, + { url = "https://files.pythonhosted.org/packages/e3/09/2a41506cb17affb0bdf9d5e2129c8c19e192b388c4c01d05e1b14db23c00/wrapt-2.1.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef91c95a50596fcdc31397eb6955476f82ae8a3f5a8eabdc13611b60ee380ba", size = 112016, upload-time = "2026-03-06T02:54:43.274Z" }, + { url = "https://files.pythonhosted.org/packages/64/15/0e6c3f5e87caadc43db279724ee36979246d5194fa32fed489c73643ba59/wrapt-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dad63212b168de8569b1c512f4eac4b57f2c6934b30df32d6ee9534a79f1493f", size = 114823, upload-time = "2026-03-06T02:54:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/56/b2/0ad17c8248f4e57bedf44938c26ec3ee194715f812d2dbbd9d7ff4be6c06/wrapt-2.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d307aa6888d5efab2c1cde09843d48c843990be13069003184b67d426d145394", size = 111244, upload-time = "2026-03-06T02:54:02.149Z" }, + { url = "https://files.pythonhosted.org/packages/ff/04/bcdba98c26f2c6522c7c09a726d5d9229120163493620205b2f76bd13c01/wrapt-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c87cf3f0c85e27b3ac7d9ad95da166bf8739ca215a8b171e8404a2d739897a45", size = 113307, upload-time = "2026-03-06T02:54:12.428Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1b/5e2883c6bc14143924e465a6fc5a92d09eeabe35310842a481fb0581f832/wrapt-2.1.2-cp310-cp310-win32.whl", hash = "sha256:d1c5fea4f9fe3762e2b905fdd67df51e4be7a73b7674957af2d2ade71a5c075d", size = 57986, upload-time = "2026-03-06T02:54:26.823Z" }, + { url = "https://files.pythonhosted.org/packages/42/5a/4efc997bccadd3af5749c250b49412793bc41e13a83a486b2b54a33e240c/wrapt-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:d8f7740e1af13dff2684e4d56fe604a7e04d6c94e737a60568d8d4238b9a0c71", size = 60336, upload-time = "2026-03-06T02:54:18Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f5/a2bb833e20181b937e87c242645ed5d5aa9c373006b0467bfe1a35c727d0/wrapt-2.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c6cc827c00dc839350155f316f1f8b4b0c370f52b6a19e782e2bda89600c7dc", size = 58757, upload-time = "2026-03-06T02:53:51.545Z" }, + { url = "https://files.pythonhosted.org/packages/c7/81/60c4471fce95afa5922ca09b88a25f03c93343f759aae0f31fb4412a85c7/wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb", size = 60666, upload-time = "2026-03-06T02:52:58.934Z" }, + { url = "https://files.pythonhosted.org/packages/6b/be/80e80e39e7cb90b006a0eaf11c73ac3a62bbfb3068469aec15cc0bc795de/wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d", size = 61601, upload-time = "2026-03-06T02:53:00.487Z" }, + { url = "https://files.pythonhosted.org/packages/b0/be/d7c88cd9293c859fc74b232abdc65a229bb953997995d6912fc85af18323/wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894", size = 114057, upload-time = "2026-03-06T02:52:44.08Z" }, + { url = "https://files.pythonhosted.org/packages/ea/25/36c04602831a4d685d45a93b3abea61eca7fe35dab6c842d6f5d570ef94a/wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842", size = 116099, upload-time = "2026-03-06T02:54:56.74Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4e/98a6eb417ef551dc277bec1253d5246b25003cf36fdf3913b65cb7657a56/wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8", size = 112457, upload-time = "2026-03-06T02:53:52.842Z" }, + { url = "https://files.pythonhosted.org/packages/cb/a6/a6f7186a5297cad8ec53fd7578533b28f795fdf5372368c74bd7e6e9841c/wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6", size = 115351, upload-time = "2026-03-06T02:53:32.684Z" }, + { url = "https://files.pythonhosted.org/packages/97/6f/06e66189e721dbebd5cf20e138acc4d1150288ce118462f2fcbff92d38db/wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9", size = 111748, upload-time = "2026-03-06T02:53:08.455Z" }, + { url = "https://files.pythonhosted.org/packages/ef/43/4808b86f499a51370fbdbdfa6cb91e9b9169e762716456471b619fca7a70/wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15", size = 113783, upload-time = "2026-03-06T02:53:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/91/2c/a3f28b8fa7ac2cefa01cfcaca3471f9b0460608d012b693998cd61ef43df/wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b", size = 57977, upload-time = "2026-03-06T02:53:27.844Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c3/2b1c7bd07a27b1db885a2fab469b707bdd35bddf30a113b4917a7e2139d2/wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1", size = 60336, upload-time = "2026-03-06T02:54:28.104Z" }, + { url = "https://files.pythonhosted.org/packages/ec/5c/76ece7b401b088daa6503d6264dd80f9a727df3e6042802de9a223084ea2/wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a", size = 58756, upload-time = "2026-03-06T02:53:16.319Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b6/1db817582c49c7fcbb7df6809d0f515af29d7c2fbf57eb44c36e98fb1492/wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9", size = 61255, upload-time = "2026-03-06T02:52:45.663Z" }, + { url = "https://files.pythonhosted.org/packages/a2/16/9b02a6b99c09227c93cd4b73acc3678114154ec38da53043c0ddc1fba0dc/wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748", size = 61848, upload-time = "2026-03-06T02:53:48.728Z" }, + { url = "https://files.pythonhosted.org/packages/af/aa/ead46a88f9ec3a432a4832dfedb84092fc35af2d0ba40cd04aea3889f247/wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e", size = 121433, upload-time = "2026-03-06T02:54:40.328Z" }, + { url = "https://files.pythonhosted.org/packages/3a/9f/742c7c7cdf58b59085a1ee4b6c37b013f66ac33673a7ef4aaed5e992bc33/wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8", size = 123013, upload-time = "2026-03-06T02:53:26.58Z" }, + { url = "https://files.pythonhosted.org/packages/e8/44/2c3dd45d53236b7ed7c646fcf212251dc19e48e599debd3926b52310fafb/wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c", size = 117326, upload-time = "2026-03-06T02:53:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/74/e2/b17d66abc26bd96f89dec0ecd0ef03da4a1286e6ff793839ec431b9fae57/wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c", size = 121444, upload-time = "2026-03-06T02:54:09.5Z" }, + { url = "https://files.pythonhosted.org/packages/3c/62/e2977843fdf9f03daf1586a0ff49060b1b2fc7ff85a7ea82b6217c1ae36e/wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1", size = 116237, upload-time = "2026-03-06T02:54:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/27fc67914e68d740bce512f11734aec08696e6b17641fef8867c00c949fc/wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2", size = 120563, upload-time = "2026-03-06T02:53:20.412Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9f/b750b3692ed2ef4705cb305bd68858e73010492b80e43d2a4faa5573cbe7/wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0", size = 58198, upload-time = "2026-03-06T02:53:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b2/feecfe29f28483d888d76a48f03c4c4d8afea944dbee2b0cd3380f9df032/wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63", size = 60441, upload-time = "2026-03-06T02:52:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/44/e1/e328f605d6e208547ea9fd120804fcdec68536ac748987a68c47c606eea8/wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf", size = 58836, upload-time = "2026-03-06T02:53:22.053Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" }, + { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" }, + { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" }, + { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" }, + { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" }, + { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" }, + { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" }, + { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" }, + { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" }, + { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" }, + { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" }, + { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" }, + { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" }, + { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" }, + { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" }, + { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" }, + { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" }, + { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" }, + { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" }, + { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" }, + { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" }, +] + +[[package]] +name = "yarl" +version = "1.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0d/9cc638702f6fc3c7a3685bcc8cf2a9ed7d6206e932a49f5242658047ef51/yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107", size = 123764, upload-time = "2026-03-01T22:04:09.7Z" }, + { url = "https://files.pythonhosted.org/packages/7a/35/5a553687c5793df5429cd1db45909d4f3af7eee90014888c208d086a44f0/yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d", size = 86282, upload-time = "2026-03-01T22:04:11.892Z" }, + { url = "https://files.pythonhosted.org/packages/68/2e/c5a2234238f8ce37a8312b52801ee74117f576b1539eec8404a480434acc/yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05", size = 86053, upload-time = "2026-03-01T22:04:13.292Z" }, + { url = "https://files.pythonhosted.org/packages/74/3f/bbd8ff36fb038622797ffbaf7db314918bb4d76f1cc8a4f9ca7a55fe5195/yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d", size = 99395, upload-time = "2026-03-01T22:04:15.133Z" }, + { url = "https://files.pythonhosted.org/packages/77/04/9516bc4e269d2a3ec9c6779fcdeac51ce5b3a9b0156f06ac7152e5bba864/yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748", size = 92143, upload-time = "2026-03-01T22:04:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/c7/63/88802d1f6b1cb1fc67d67a58cd0cf8a1790de4ce7946e434240f1d60ab4a/yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764", size = 107643, upload-time = "2026-03-01T22:04:18.519Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/4f9b838f4d8bdd6f0f385aed8bbf21c71ed11a0b9983305c302cbd557815/yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007", size = 108700, upload-time = "2026-03-01T22:04:20.373Z" }, + { url = "https://files.pythonhosted.org/packages/50/12/95a1d33f04a79c402664070d43b8b9f72dc18914e135b345b611b0b1f8cc/yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4", size = 102769, upload-time = "2026-03-01T22:04:23.055Z" }, + { url = "https://files.pythonhosted.org/packages/86/65/91a0285f51321369fd1a8308aa19207520c5f0587772cfc2e03fc2467e90/yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26", size = 101114, upload-time = "2026-03-01T22:04:25.031Z" }, + { url = "https://files.pythonhosted.org/packages/58/80/c7c8244fc3e5bc483dc71a09560f43b619fab29301a0f0a8f936e42865c7/yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769", size = 98883, upload-time = "2026-03-01T22:04:27.281Z" }, + { url = "https://files.pythonhosted.org/packages/86/e7/71ca9cc9ca79c0b7d491216177d1aed559d632947b8ffb0ee60f7d8b23e3/yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716", size = 94172, upload-time = "2026-03-01T22:04:28.554Z" }, + { url = "https://files.pythonhosted.org/packages/6a/3f/6c6c8a0fe29c26fb2db2e8d32195bb84ec1bfb8f1d32e7f73b787fcf349b/yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993", size = 107010, upload-time = "2026-03-01T22:04:30.385Z" }, + { url = "https://files.pythonhosted.org/packages/56/38/12730c05e5ad40a76374d440ed8b0899729a96c250516d91c620a6e38fc2/yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0", size = 100285, upload-time = "2026-03-01T22:04:31.752Z" }, + { url = "https://files.pythonhosted.org/packages/34/92/6a7be9239f2347234e027284e7a5f74b1140cc86575e7b469d13fba1ebfe/yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750", size = 108230, upload-time = "2026-03-01T22:04:33.844Z" }, + { url = "https://files.pythonhosted.org/packages/5e/81/4aebccfa9376bd98b9d8bfad20621a57d3e8cfc5b8631c1fa5f62cdd03f4/yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6", size = 103008, upload-time = "2026-03-01T22:04:35.856Z" }, + { url = "https://files.pythonhosted.org/packages/38/0f/0b4e3edcec794a86b853b0c6396c0a888d72dfce19b2d88c02ac289fb6c1/yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d", size = 83073, upload-time = "2026-03-01T22:04:38.268Z" }, + { url = "https://files.pythonhosted.org/packages/a0/71/ad95c33da18897e4c636528bbc24a1dd23fe16797de8bc4ec667b8db0ba4/yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb", size = 87328, upload-time = "2026-03-01T22:04:39.558Z" }, + { url = "https://files.pythonhosted.org/packages/e2/14/dfa369523c79bccf9c9c746b0a63eb31f65db9418ac01275f7950962e504/yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220", size = 82463, upload-time = "2026-03-01T22:04:41.454Z" }, + { url = "https://files.pythonhosted.org/packages/a2/aa/60da938b8f0997ba3a911263c40d82b6f645a67902a490b46f3355e10fae/yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99", size = 123641, upload-time = "2026-03-01T22:04:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/24/84/e237607faf4e099dbb8a4f511cfd5efcb5f75918baad200ff7380635631b/yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c", size = 86248, upload-time = "2026-03-01T22:04:44.757Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432", size = 85988, upload-time = "2026-03-01T22:04:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/8c/6c/4a90d59c572e46b270ca132aca66954f1175abd691f74c1ef4c6711828e2/yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a", size = 100566, upload-time = "2026-03-01T22:04:47.639Z" }, + { url = "https://files.pythonhosted.org/packages/49/fb/c438fb5108047e629f6282a371e6e91cf3f97ee087c4fb748a1f32ceef55/yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05", size = 92079, upload-time = "2026-03-01T22:04:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/d9/13/d269aa1aed3e4f50a5a103f96327210cc5fa5dd2d50882778f13c7a14606/yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83", size = 108741, upload-time = "2026-03-01T22:04:50.838Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/115b16f22c37ea4437d323e472945bea97301c8ec6089868fa560abab590/yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c", size = 108099, upload-time = "2026-03-01T22:04:52.499Z" }, + { url = "https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598", size = 102678, upload-time = "2026-03-01T22:04:55.176Z" }, + { url = "https://files.pythonhosted.org/packages/85/59/cd98e556fbb2bf8fab29c1a722f67ad45c5f3447cac798ab85620d1e70af/yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b", size = 100803, upload-time = "2026-03-01T22:04:56.588Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/b39770b56d4a9f0bb5f77e2f1763cd2d75cc2f6c0131e3b4c360348fcd65/yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c", size = 100163, upload-time = "2026-03-01T22:04:58.492Z" }, + { url = "https://files.pythonhosted.org/packages/e7/64/6980f99ab00e1f0ff67cb84766c93d595b067eed07439cfccfc8fb28c1a6/yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788", size = 93859, upload-time = "2026-03-01T22:05:00.268Z" }, + { url = "https://files.pythonhosted.org/packages/38/69/912e6c5e146793e5d4b5fe39ff5b00f4d22463dfd5a162bec565ac757673/yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222", size = 108202, upload-time = "2026-03-01T22:05:02.273Z" }, + { url = "https://files.pythonhosted.org/packages/59/97/35ca6767524687ad64e5f5c31ad54bc76d585585a9fcb40f649e7e82ffed/yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb", size = 99866, upload-time = "2026-03-01T22:05:03.597Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1c/1a3387ee6d73589f6f2a220ae06f2984f6c20b40c734989b0a44f5987308/yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc", size = 107852, upload-time = "2026-03-01T22:05:04.986Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b8/35c0750fcd5a3f781058bfd954515dd4b1eab45e218cbb85cf11132215f1/yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2", size = 102919, upload-time = "2026-03-01T22:05:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/9a1979aec4a81896d597bcb2177827f2dbee3f5b7cc48b2d0dadb644b41d/yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5", size = 82602, upload-time = "2026-03-01T22:05:08.444Z" }, + { url = "https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46", size = 87461, upload-time = "2026-03-01T22:05:10.145Z" }, + { url = "https://files.pythonhosted.org/packages/93/95/07e3553fe6f113e6864a20bdc53a78113cda3b9ced8784ee52a52c9f80d8/yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928", size = 82336, upload-time = "2026-03-01T22:05:11.554Z" }, + { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, + { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, + { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547, upload-time = "2026-03-01T22:05:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, + { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, + { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, + { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356, upload-time = "2026-03-01T22:06:06.04Z" }, + { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515, upload-time = "2026-03-01T22:06:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785, upload-time = "2026-03-01T22:06:10.181Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, + { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690, upload-time = "2026-03-01T22:06:13.429Z" }, + { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, + { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, + { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, + { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, + { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, + { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, + { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, + { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, + { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, + { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, + { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, + { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, + { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, + { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, + { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, + { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, + { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, + { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, + { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, + { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, + { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, +]