From e97007c85c74147a81e796f2139a5b41259950d3 Mon Sep 17 00:00:00 2001 From: Stefan Hackmann Date: Wed, 29 Jul 2026 11:14:53 +0200 Subject: [PATCH 1/3] fix: increase max_attempts for retry configuration to improve resilience --- README.md | 2 +- config.sample.yaml | 2 +- src/jointfm_client/configuration.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0316004..7c6312e 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ transport: connect_seconds: 10.0 read_seconds: 120.0 retry: - max_attempts: 10 + max_attempts: 20 backoff_seconds: 2 ``` diff --git a/config.sample.yaml b/config.sample.yaml index c7340d5..a2bedaf 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -29,7 +29,7 @@ transport: connect_seconds: 10.0 read_seconds: 120.0 retry: - max_attempts: 10 + max_attempts: 20 backoff_seconds: 2 max_backoff_seconds: 60.0 status_codes: diff --git a/src/jointfm_client/configuration.py b/src/jointfm_client/configuration.py index a7ca806..fa496dc 100644 --- a/src/jointfm_client/configuration.py +++ b/src/jointfm_client/configuration.py @@ -144,7 +144,7 @@ def _validate_positive_finite(cls, value: float) -> float: class RetryConfig(_ConfigModel): """Default retry policy for transient HTTP failures.""" - max_attempts: int = 10 + max_attempts: int = 20 backoff_seconds: float = 2 max_backoff_seconds: float = 60.0 status_codes: tuple[int, ...] = (408, 429, 500, 502, 503, 504) From daf66c97dd84fdd5a2eadfe8720d68ddc778909d Mon Sep 17 00:00:00 2001 From: Stefan Hackmann Date: Thu, 30 Jul 2026 21:46:21 +0200 Subject: [PATCH 2/3] fix: add status code 470 for retry configuration to handle non-standard prediction failures --- config.sample.yaml | 1 + src/jointfm_client/configuration.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/config.sample.yaml b/config.sample.yaml index a2bedaf..ecf4bea 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -35,6 +35,7 @@ transport: status_codes: - 408 - 429 + - 470 - 500 - 502 - 503 diff --git a/src/jointfm_client/configuration.py b/src/jointfm_client/configuration.py index fa496dc..c2205f1 100644 --- a/src/jointfm_client/configuration.py +++ b/src/jointfm_client/configuration.py @@ -147,7 +147,10 @@ class RetryConfig(_ConfigModel): max_attempts: int = 20 backoff_seconds: float = 2 max_backoff_seconds: float = 60.0 - status_codes: tuple[int, ...] = (408, 429, 500, 502, 503, 504) + # 470 is DataRobot's non-standard "prediction was not successful" code, + # returned while a deployment's custom model is stopped or still warming + # up; retrying bridges warm-up windows after a deployment (re)start. + status_codes: tuple[int, ...] = (408, 429, 470, 500, 502, 503, 504) @field_validator("max_attempts") @classmethod From 137902a623977ea1a1db7ded763ad67cd7d32959 Mon Sep 17 00:00:00 2001 From: Stefan Hackmann Date: Sat, 1 Aug 2026 20:03:17 +0000 Subject: [PATCH 3/3] fix: remove yfinance dependency from optional dependencies in pyproject.toml --- README.md | 2 +- notebooks/forecast_trading.ipynb | 567 ------------------------------- pyproject.toml | 1 - tests/test_notebooks.py | 1 - uv.lock | 230 +------------ 5 files changed, 4 insertions(+), 797 deletions(-) delete mode 100644 notebooks/forecast_trading.ipynb diff --git a/README.md b/README.md index 7c6312e..bf9f6da 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ The current V1 forecast request contract is: V1 column descriptors support the server fields `name`, `modality`, `role`, `nullable`, `vocabulary_size`, `level_count`, `mapping`, `lower_bound`, `upper_bound`, `time_value_kind`, `time_value_scale_seconds`, `time_value_use_local_normalized_time`, `time_value_calendar_id`, and `time_value_timezone`. -DataFrame helpers and the notebook examples are available through one optional extra that pulls in `pandas` and `yfinance` (the latter powers the Yahoo Finance download in `notebooks/forecast_trading.ipynb`): +DataFrame helpers and the notebook examples are available through one optional extra that pulls in `pandas`: ```bash uv add "jointfm-client[notebooks]" diff --git a/notebooks/forecast_trading.ipynb b/notebooks/forecast_trading.ipynb deleted file mode 100644 index d328bca..0000000 --- a/notebooks/forecast_trading.ipynb +++ /dev/null @@ -1,567 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": { - "id": "license-header", - "language": "markdown" - }, - "source": [ - "Copyright 2026 DataRobot, Inc. and its affiliates.\n", - "\n", - "Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "you may not use this file except in compliance with the License.\n", - "You may obtain a copy of the License at\n", - "\n", - " http://www.apache.org/licenses/LICENSE-2.0\n", - "\n", - "Unless required by applicable law or agreed to in writing, software\n", - "distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "See the License for the specific language governing permissions and\n", - "limitations under the License." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1", - "metadata": { - "id": "trading-bootstrap", - "language": "python" - }, - "outputs": [], - "source": [ - "from jointfm_client import bootstrap_notebook\n", - "\n", - "bootstrap_notebook(add_src_root=True)" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": { - "id": "trading-description", - "language": "markdown" - }, - "source": [ - "# Forecast-Driven Long / Flat / Short Trading on Bitcoin\n", - "\n", - "Worked example of a trading decision rule built on top of `forecast_samples` for a JointFM model trained on **absolute prices**. We download daily Yahoo Finance closes for Bitcoin, a few other major crypto assets, and the most important macro drivers, send the joint history to a JointFM deployment, and apply a long / flat / short classifier on the resulting Bitcoin samples.\n", - "\n", - "## Inputs\n", - "\n", - "- **Classifier target**: `BTC-USD` (the only requested forecast column; the long / flat / short rule acts on its samples).\n", - "- **Other crypto majors**: `ETH-USD`, `SOL-USD`, `BNB-USD`, `XRP-USD`. Sent as additional targets so the joint model conditions BTC on the rest of the crypto complex.\n", - "- **Macro drivers**: S&P 500 (`^GSPC`), Nasdaq Composite (`^IXIC`), VIX (`^VIX`), 10-year US Treasury yield (`^TNX`), US Dollar index futures (`DX=F`), Gold futures (`GC=F`), WTI crude futures (`CL=F`). These cover risk-on / off equities, real rates, the dollar, and the commodity backdrop — the dominant non-crypto inputs that move BTC.\n", - "\n", - "The Yahoo download requires the optional `notebooks` extra (which also pulls in `pandas`):\n", - "\n", - "```bash\n", - "uv add \"jointfm-client[notebooks]\"\n", - "```\n", - "\n", - "## Workflow\n", - "\n", - "1. Discover the deployment's data-generation envelope through `client.health()` (`n_input`, `n_output`, capacity caps).\n", - "2. Pull daily closes for every requested symbol via `yfinance`, snap them to a business-day calendar, forward-fill within each series to bridge per-symbol gaps, and keep the most recent `n_input` rows.\n", - "3. Forecast all symbols with `forecast_samples` while requesting samples only for `BTC-USD`.\n", - "4. Convert each price sample to a holding-period **simple return** relative to the last observed BTC close: `R_h = p_h / p_last - 1`. Simple (not log) returns are used because the Kelly bet multiplier is `(1 + f * R)`.\n", - "5. From the empirical return distribution per horizon, derive:\n", - " - **Action**: `long` / `flat` / `short`, gated by tail probabilities crossing configurable dead-zone thresholds. The dead zone should be at least the round-trip transaction cost; shorting can use a wider threshold to absorb borrow costs.\n", - " - **Position size**: the Kelly fraction `f* = argmax_f E[log(1 + f R)]` solved numerically on the empirical samples and capped by a configurable leverage limit.\n", - "6. Two decision flavors:\n", - " - **Single horizon** — e.g. 7 business days ahead.\n", - " - **Multi-horizon** — e.g. 5 / 7 / 9 business days ahead. Trade only when every horizon agrees on sign; size with the mean of per-horizon Kelly fractions.\n", - "\n", - "## Interpreting the `position` output\n", - "\n", - "`position` is a **unitless fraction of bankroll** in `[-KELLY_CAP, +KELLY_CAP]`. It comes directly from Kelly's objective `E[log(1 + f * R)]`, where `f` is the fraction of current equity committed to the trade:\n", - "\n", - "- `+1.0` — go long with 100% of equity.\n", - "- `-1.0` — go short with 100% of equity.\n", - "- `+0.25` — go long with 25% of equity.\n", - "- `0.0` — flat (either the gating rule said `flat`, or Kelly disagreed in sign with the gated action).\n", - "\n", - "To turn `position` into something a broker can accept:\n", - "\n", - "```\n", - "notional = position * equity # USD allocated to the trade (sign = side)\n", - "units = notional / current_price # coins (or contracts for BTC futures)\n", - "```\n", - "\n", - "Two practical knobs:\n", - "\n", - "- **`KELLY_CAP` is the \"fractional Kelly\" lever.** Full Kelly (`KELLY_CAP = 1.0`) is famously aggressive and assumes the empirical sample distribution equals the true distribution. Most practitioners run quarter- or half-Kelly to absorb model error and reduce drawdowns; lower `KELLY_CAP` to `0.25` or `0.5` to do that without changing the code.\n", - "- **Leverage > 1 is possible if you allow it.** Setting `KELLY_CAP = 2.0` lets the rule recommend up to `2.0 * equity` exposure. The feasibility bracket inside `kelly_fraction` keeps `1 + f * R > 0` for every drawn sample, so the suggested size can never bankrupt you on the *sampled* tail — but that guarantee is only as good as the forecast's tail.\n", - "\n", - "This is a client-side rule on top of a probabilistic forecaster. The forthcoming architecture-world model supports native classification/regression heads; this notebook will become redundant once that lands.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3", - "metadata": { - "id": "trading-config", - "language": "python" - }, - "outputs": [], - "source": [ - "import warnings\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "import yfinance as yf\n", - "\n", - "from jointfm_client import JointFMClient, plan_forecast_columns\n", - "\n", - "# BTC-USD is the classifier target. Other crypto majors and macro drivers are\n", - "# sent alongside as additional targets so the joint model conditions the\n", - "# Bitcoin marginal on the rest of the crypto complex and the macro backdrop.\n", - "PRICE_TARGET = \"BTC-USD\"\n", - "ADDITIONAL_CRYPTO_SYMBOLS = (\"ETH-USD\", \"SOL-USD\", \"BNB-USD\", \"XRP-USD\")\n", - "ECONOMIC_DRIVER_SYMBOLS = (\n", - " \"^GSPC\", # S&P 500 index level\n", - " \"^IXIC\", # Nasdaq Composite index level\n", - " \"^VIX\", # CBOE volatility index\n", - " \"^TNX\", # 10-year US Treasury yield (quoted as yield-in-percent x 10)\n", - " \"DX-Y.NYB\", # ICE US Dollar index spot\n", - " \"GC=F\", # Gold futures (USD/oz)\n", - " \"CL=F\", # WTI crude oil futures (USD/bbl)\n", - ")\n", - "ALL_SYMBOLS = (PRICE_TARGET,) + ADDITIONAL_CRYPTO_SYMBOLS + ECONOMIC_DRIVER_SYMBOLS\n", - "\n", - "# Holding horizons (business days ahead of the last observed close).\n", - "SINGLE_HORIZON = 7\n", - "MULTI_HORIZONS = (5, 7, 9)\n", - "HORIZONS = tuple(sorted({SINGLE_HORIZON, *MULTI_HORIZONS}))\n", - "\n", - "# Monte-Carlo sample budget for the empirical return distribution per horizon.\n", - "N_SAMPLES = 10_000\n", - "SEED = 7\n", - "\n", - "# Dead-zone thresholds, in simple-return units per holding period.\n", - "# 50 bps ~ round-trip cost; shorting carries borrow cost on top.\n", - "EPS_LONG = 0.005\n", - "EPS_SHORT = 0.010\n", - "\n", - "# Minimum tail probability above the threshold required to take a position.\n", - "CONFIDENCE_THRESHOLD = 0.55\n", - "\n", - "# Hard cap on |position|. 1.0 = fully invested, <1.0 = de-levered.\n", - "KELLY_CAP = 1.0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4", - "metadata": { - "id": "trading-load-history", - "language": "python" - }, - "outputs": [], - "source": [ - "client = JointFMClient.from_env()\n", - "health = client.health(cache=True)\n", - "capacity = health.data_generation\n", - "if capacity is None:\n", - " raise ValueError(\n", - " \"Deployment /healthz response is missing the data_generation block\"\n", - " )\n", - "if max(HORIZONS) > capacity.n_output:\n", - " raise ValueError(\n", - " f\"Largest horizon {max(HORIZONS)} exceeds deployment n_output={capacity.n_output}\"\n", - " )\n", - "\n", - "# yfinance pins a few internal calls to the soon-to-be-removed pandas\n", - "# Timestamp.utcnow API; mute that upstream-only deprecation noise here so\n", - "# the notebook output stays readable. Errors and our own warnings still bubble.\n", - "with warnings.catch_warnings():\n", - " warnings.filterwarnings(\n", - " \"ignore\",\n", - " message=r\"Timestamp\\.utcnow is deprecated\",\n", - " module=r\"yfinance\\..*\",\n", - " )\n", - " raw_yahoo = yf.download(\n", - " list(ALL_SYMBOLS),\n", - " period=\"max\",\n", - " interval=\"1d\",\n", - " auto_adjust=False,\n", - " progress=False,\n", - " actions=False,\n", - " threads=False,\n", - " )\n", - "if not isinstance(\n", - " raw_yahoo.columns, pd.MultiIndex\n", - ") or \"Close\" not in raw_yahoo.columns.get_level_values(0):\n", - " raise ValueError(\"Yahoo Finance response does not expose a Close field\")\n", - "close_frame = raw_yahoo[\"Close\"].sort_index()\n", - "missing_symbols = [\n", - " symbol for symbol in ALL_SYMBOLS if symbol not in close_frame.columns\n", - "]\n", - "if missing_symbols:\n", - " raise ValueError(\n", - " f\"Yahoo Finance did not return Close columns for {missing_symbols!r}\"\n", - " )\n", - "close_frame = close_frame.loc[:, list(ALL_SYMBOLS)].astype(float)\n", - "\n", - "# Crypto trades 7 days a week, equities and futures 5 days. Snap to a\n", - "# business-day calendar and forward-fill within each series so per-symbol\n", - "# holidays do not punch holes through the joint history.\n", - "business_index = pd.date_range(\n", - " close_frame.index.min(), close_frame.index.max(), freq=\"B\"\n", - ")\n", - "business_close = close_frame.reindex(business_index).ffill()\n", - "\n", - "# A column is \"dead\" when Yahoo returned the symbol but every row is NaN\n", - "# (typical for delisted/renamed tickers like the retired DX=F future).\n", - "# Flag those explicitly instead of letting first_valid_index() return None\n", - "# and trip max() with an unrelated TypeError.\n", - "first_valid_per_symbol = {\n", - " symbol: business_close[symbol].first_valid_index() for symbol in ALL_SYMBOLS\n", - "}\n", - "dead_symbols = [symbol for symbol, ts in first_valid_per_symbol.items() if ts is None]\n", - "if dead_symbols:\n", - " raise ValueError(\n", - " f\"Yahoo Finance returned no usable Close data for {dead_symbols!r}; \"\n", - " f\"the ticker may have been delisted or renamed. Replace it in \"\n", - " f\"ECONOMIC_DRIVER_SYMBOLS / ADDITIONAL_CRYPTO_SYMBOLS.\"\n", - " )\n", - "\n", - "# Trim leading rows where any symbol has not yet started trading (e.g. SOL-USD\n", - "# lists only from 2020). n_input is the deployment's training-time upper bound,\n", - "# so feed at most that many of the most recent business days; shorter joint\n", - "# histories are accepted by the service.\n", - "common_start = max(first_valid_per_symbol.values())\n", - "aligned = business_close.loc[common_start:]\n", - "if aligned.isna().any().any():\n", - " bad_columns = aligned.columns[aligned.isna().any()].tolist()\n", - " raise ValueError(f\"Residual NaN after alignment in columns: {bad_columns!r}\")\n", - "if len(aligned) == 0:\n", - " raise ValueError(f\"No aligned Yahoo history available for {list(ALL_SYMBOLS)!r}\")\n", - "history_length = min(capacity.n_input, len(aligned))\n", - "history = aligned.iloc[-history_length:].copy().reset_index(drop=True)\n", - "\n", - "last_btc_price = float(history[PRICE_TARGET].iloc[-1])\n", - "if last_btc_price <= 0.0:\n", - " raise ValueError(\n", - " f\"Last observed {PRICE_TARGET} must be positive, got {last_btc_price}\"\n", - " )\n", - "\n", - "window_start = aligned.index[-history_length].date()\n", - "window_end = aligned.index[-1].date()\n", - "print(\n", - " f\"History window: {window_start} -> {window_end} \"\n", - " f\"({history_length} business days; deployment cap n_input={capacity.n_input})\"\n", - ")\n", - "print(f\"Symbols ({len(ALL_SYMBOLS)}): {list(ALL_SYMBOLS)}\")\n", - "print(f\"Last observed {PRICE_TARGET}: {last_btc_price:,.2f}\")\n", - "print(f\"Forecast horizons (business days ahead): {HORIZONS}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5", - "metadata": { - "id": "trading-forecast-samples", - "language": "python" - }, - "outputs": [], - "source": [ - "# query_time for h-step-ahead is len(history) - 1 + h (matches the convention\n", - "# in forecast_samples.ipynb where the last history row corresponds to h=1).\n", - "query_times = [len(history) - 1 + h for h in HORIZONS]\n", - "\n", - "# All Yahoo symbols are passed as targets so the plan stays valid on\n", - "# max_features=0 deployments. plan_forecast_columns downgrades features to\n", - "# targets automatically when the deployment allows it.\n", - "plan = plan_forecast_columns(\n", - " health=health,\n", - " feature_columns=(),\n", - " target_columns=list(ALL_SYMBOLS),\n", - " history_length=len(history),\n", - " query_times_length=len(query_times),\n", - ")\n", - "result = client.forecast_samples(\n", - " history,\n", - " query_times=query_times,\n", - " requested_columns=[PRICE_TARGET],\n", - " columns=plan.columns,\n", - " n_samples=N_SAMPLES,\n", - " seed=SEED,\n", - ")\n", - "\n", - "price_samples = result.to_numpy() # (sample, horizon, column)\n", - "expected_shape = (N_SAMPLES, len(HORIZONS), 1)\n", - "if price_samples.shape != expected_shape:\n", - " raise ValueError(\n", - " f\"Expected sample tensor of shape {expected_shape}, got {price_samples.shape}\"\n", - " )\n", - "price_samples = price_samples[:, :, 0] # drop the singleton requested-column axis" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6", - "metadata": { - "id": "trading-convert-returns", - "language": "python" - }, - "outputs": [], - "source": [ - "# BTC-USD cannot trade below zero in the real world. The joint model emits an\n", - "# unbounded continuous marginal, so its tail can occasionally cross zero on\n", - "# absolute-price targets. Clamp such samples at 0 (capping the worst-case\n", - "# holding-period return at -100%) and surface a warning so a fat negative tail\n", - "# does not silently degrade calibration.\n", - "negative_count = int(np.sum(price_samples < 0.0))\n", - "if negative_count > 0:\n", - " fraction = negative_count / price_samples.size\n", - " print(\n", - " f\"WARNING: {negative_count} of {price_samples.size} {PRICE_TARGET} samples \"\n", - " f\"({fraction:.2%}) were negative; clamped to 0.0.\"\n", - " )\n", - " price_samples = np.maximum(price_samples, 0.0)\n", - "\n", - "simple_returns_by_horizon = {\n", - " horizon: price_samples[:, index] / last_btc_price - 1.0\n", - " for index, horizon in enumerate(HORIZONS)\n", - "}\n", - "\n", - "pd.DataFrame(\n", - " {\n", - " f\"R_{horizon}\": pd.Series(simple_returns_by_horizon[horizon]).describe()\n", - " for horizon in HORIZONS\n", - " }\n", - ").round(6)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7", - "metadata": { - "id": "trading-decision-helpers", - "language": "python" - }, - "outputs": [], - "source": [ - "KELLY_BISECTION_ITERATIONS = 80\n", - "FEASIBLE_BRACKET_SAFETY = 1e-6\n", - "\n", - "\n", - "def decision_probabilities(\n", - " returns: np.ndarray, *, eps_long: float, eps_short: float\n", - ") -> dict[str, float]:\n", - " \"\"\"Tail probabilities of crossing the long / short thresholds.\n", - "\n", - " Returns the empirical mass strictly above ``+eps_long``, strictly below ``-eps_short``,\n", - " and the residual mass inside the dead zone.\n", - " \"\"\"\n", - " p_long = float(np.mean(returns > eps_long))\n", - " p_short = float(np.mean(returns < -eps_short))\n", - " return {\"p_long\": p_long, \"p_short\": p_short, \"p_flat\": 1.0 - p_long - p_short}\n", - "\n", - "\n", - "def kelly_fraction(returns: np.ndarray, *, max_leverage: float) -> float:\n", - " \"\"\"Numerical Kelly fraction maximizing ``E[log(1 + f R)]`` on empirical samples.\n", - "\n", - " Returns the unconstrained interior optimum when one exists inside the feasible\n", - " bracket ``(-1/R_max, -1/R_min)`` (where ``1 + f R > 0`` for every sample),\n", - " clipped to ``[-max_leverage, +max_leverage]``. Returns the closest boundary\n", - " when the derivative does not change sign on the bracket.\n", - " \"\"\"\n", - " r = np.asarray(returns, dtype=float)\n", - " r_max = float(r.max())\n", - " r_min = float(r.min())\n", - " feasible_upper = (-1.0 / r_min) if r_min < 0.0 else float(\"inf\")\n", - " feasible_lower = (-1.0 / r_max) if r_max > 0.0 else float(\"-inf\")\n", - " f_upper = min(feasible_upper - FEASIBLE_BRACKET_SAFETY, max_leverage)\n", - " f_lower = max(feasible_lower + FEASIBLE_BRACKET_SAFETY, -max_leverage)\n", - " if f_lower >= f_upper:\n", - " return 0.0\n", - "\n", - " def derivative(f: float) -> float:\n", - " \"\"\"First-order condition of ``E[log(1 + f R)]`` evaluated at ``f``.\"\"\"\n", - " return float(np.mean(r / (1.0 + f * r)))\n", - "\n", - " g_lower = derivative(f_lower)\n", - " g_upper = derivative(f_upper)\n", - " if g_lower > 0.0 and g_upper > 0.0:\n", - " return f_upper\n", - " if g_lower < 0.0 and g_upper < 0.0:\n", - " return f_lower\n", - " a, b = f_lower, f_upper\n", - " for _ in range(KELLY_BISECTION_ITERATIONS):\n", - " m = 0.5 * (a + b)\n", - " if derivative(m) > 0.0:\n", - " a = m\n", - " else:\n", - " b = m\n", - " return 0.5 * (a + b)\n", - "\n", - "\n", - "def decide(\n", - " returns: np.ndarray,\n", - " *,\n", - " eps_long: float,\n", - " eps_short: float,\n", - " confidence_threshold: float,\n", - " kelly_cap: float,\n", - ") -> dict[str, float | str]:\n", - " \"\"\"Combine tail-probability gating with a Kelly-sized position.\n", - "\n", - " The action is ``long`` or ``short`` only when the corresponding tail probability\n", - " clears ``confidence_threshold`` and dominates the other tail; otherwise the\n", - " action is ``flat`` and the position is zero. When the Kelly optimum disagrees\n", - " in sign with the gated action, the position is clipped to zero.\n", - " \"\"\"\n", - " probabilities = decision_probabilities(\n", - " returns, eps_long=eps_long, eps_short=eps_short\n", - " )\n", - " p_long = probabilities[\"p_long\"]\n", - " p_short = probabilities[\"p_short\"]\n", - " if p_long >= confidence_threshold and p_long > p_short:\n", - " action = \"long\"\n", - " elif p_short >= confidence_threshold and p_short > p_long:\n", - " action = \"short\"\n", - " else:\n", - " action = \"flat\"\n", - " raw_kelly = kelly_fraction(returns, max_leverage=kelly_cap)\n", - " if action == \"long\":\n", - " position = float(np.clip(raw_kelly, 0.0, kelly_cap))\n", - " elif action == \"short\":\n", - " position = float(np.clip(raw_kelly, -kelly_cap, 0.0))\n", - " else:\n", - " position = 0.0\n", - " return {\n", - " \"action\": action,\n", - " \"p_long\": p_long,\n", - " \"p_short\": p_short,\n", - " \"p_flat\": probabilities[\"p_flat\"],\n", - " \"mean_return\": float(np.mean(returns)),\n", - " \"std_return\": float(np.std(returns, ddof=1)),\n", - " \"kelly_fraction\": raw_kelly,\n", - " \"position\": position,\n", - " }" - ] - }, - { - "cell_type": "markdown", - "id": "8", - "metadata": { - "id": "trading-single-horizon-description", - "language": "markdown" - }, - "source": [ - "## Single horizon\n", - "\n", - "Decide at one fixed holding period (`SINGLE_HORIZON` = 7 steps ahead). The reported row contains the action, the long/short/flat tail probabilities, the empirical mean/std of the simple return, the unconstrained Kelly optimum, and the gated, capped position.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9", - "metadata": { - "id": "trading-single-horizon", - "language": "python" - }, - "outputs": [], - "source": [ - "single_decision = decide(\n", - " simple_returns_by_horizon[SINGLE_HORIZON],\n", - " eps_long=EPS_LONG,\n", - " eps_short=EPS_SHORT,\n", - " confidence_threshold=CONFIDENCE_THRESHOLD,\n", - " kelly_cap=KELLY_CAP,\n", - ")\n", - "pd.Series(single_decision, name=f\"horizon_{SINGLE_HORIZON}\").to_frame()" - ] - }, - { - "cell_type": "markdown", - "id": "10", - "metadata": { - "id": "trading-multi-horizon-description", - "language": "markdown" - }, - "source": [ - "## Multi-horizon\n", - "\n", - "Compute the same decision independently for each horizon in `MULTI_HORIZONS` = (5, 7, 9). The combined rule trades only when every horizon agrees on sign (`long` everywhere or `short` everywhere) and sizes the position with the mean of per-horizon Kelly fractions, capped by `KELLY_CAP`. Disagreement collapses to `flat` — the cheapest way to encode \"I am not confident across the holding-period choice\".\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "11", - "metadata": { - "id": "trading-multi-horizon", - "language": "python" - }, - "outputs": [], - "source": [ - "per_horizon = pd.DataFrame(\n", - " {\n", - " f\"horizon_{horizon}\": decide(\n", - " simple_returns_by_horizon[horizon],\n", - " eps_long=EPS_LONG,\n", - " eps_short=EPS_SHORT,\n", - " confidence_threshold=CONFIDENCE_THRESHOLD,\n", - " kelly_cap=KELLY_CAP,\n", - " )\n", - " for horizon in MULTI_HORIZONS\n", - " }\n", - ").T\n", - "\n", - "action_set = set(per_horizon[\"action\"].tolist())\n", - "if action_set == {\"long\"}:\n", - " combined_action = \"long\"\n", - "elif action_set == {\"short\"}:\n", - " combined_action = \"short\"\n", - "else:\n", - " combined_action = \"flat\"\n", - "\n", - "mean_kelly = float(per_horizon[\"kelly_fraction\"].mean())\n", - "if combined_action == \"long\":\n", - " combined_position = float(np.clip(mean_kelly, 0.0, KELLY_CAP))\n", - "elif combined_action == \"short\":\n", - " combined_position = float(np.clip(mean_kelly, -KELLY_CAP, 0.0))\n", - "else:\n", - " combined_position = 0.0\n", - "\n", - "print(f\"Combined multi-horizon action : {combined_action}\")\n", - "print(f\"Combined Kelly-sized position : {combined_position:+.4f}\")\n", - "per_horizon" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "joint-client-python (3.13.3)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pyproject.toml b/pyproject.toml index 02bec34..4b0d6d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,6 @@ jointfm-client = "jointfm_client.cli:main" [project.optional-dependencies] notebooks = [ "pandas>=3.0.2", - "yfinance>=0.2,<1", ] [dependency-groups] diff --git a/tests/test_notebooks.py b/tests/test_notebooks.py index 331fce4..76b7bda 100644 --- a/tests/test_notebooks.py +++ b/tests/test_notebooks.py @@ -132,7 +132,6 @@ def test_example_notebooks_start_with_bootstrap_cell() -> None: "forecast_mean.ipynb", "forecast_quantiles.ipynb", "forecast_samples.ipynb", - "forecast_trading.ipynb", "pandas_result_conversion.ipynb", "predict_json.ipynb", "service_health.ipynb", diff --git a/uv.lock b/uv.lock index e9a36c0..250dba3 100644 --- a/uv.lock +++ b/uv.lock @@ -11,7 +11,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-07-14T11:20:14.04426968Z" +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P14D" [[package]] @@ -59,19 +59,6 @@ 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 = "beautifulsoup4" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, -] - [[package]] name = "certifi" version = "2026.4.22" @@ -86,7 +73,7 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, + { name = "pycparser" }, ] 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 = [ @@ -394,39 +381,6 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] -[[package]] -name = "curl-cffi" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "cffi" }, - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/48/5b/89fcfebd3e5e85134147ac99e9f2b2271165fd4d71984fc65da5f17819b7/curl_cffi-0.15.0.tar.gz", hash = "sha256:ea0c67652bf6893d34ee0f82c944f37e488f6147e9421bef1771cc6545b02ded", size = 196437, upload-time = "2026-04-03T11:12:31.525Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/42/54ddd442c795f30ce5dd4e49f87ce77505958d3777cd96a91567a3975d2a/curl_cffi-0.15.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bda66404010e9ed743b1b83c20c86f24fe21a9a6873e17479d6e67e29d8ded28", size = 2795267, upload-time = "2026-04-03T11:11:46.48Z" }, - { url = "https://files.pythonhosted.org/packages/83/2d/3915e238579b3c5a92cead5c79130c3b8d20caaba7616cc4d894650e1d6b/curl_cffi-0.15.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a25620d9bf989c9c029a7d1642999c4c265abb0bad811deb2f77b0b5b2b12e5b", size = 2573544, upload-time = "2026-04-03T11:11:47.951Z" }, - { url = "https://files.pythonhosted.org/packages/2a/b3/9d2f1057749a1b07ba1989db3c1503ce8bed998310bae9aea2c43aa64f20/curl_cffi-0.15.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:582e570aa2586b96ed47cf4a17586b9a3c462cbe43f780487c3dc245c6ef1527", size = 10515369, upload-time = "2026-04-03T11:11:50.126Z" }, - { url = "https://files.pythonhosted.org/packages/b5/1d/6d10dded5ce3fd8157e558ebd97d09e551b77a62cdc1c31e93d0a633cee5/curl_cffi-0.15.0-cp310-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:838e48212447d9c81364b04707a5c861daf08f8320f9ecb3406a8919d1d5c3b3", size = 10160045, upload-time = "2026-04-03T11:11:52.664Z" }, - { url = "https://files.pythonhosted.org/packages/5c/12/c70b835487ace3b9ba1502631912e3440082b8ae3a162f60b59cb0b6444d/curl_cffi-0.15.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b6c847d86283b07ae69bb72c82eb8a59242277142aa35b89850f89e792a02fc", size = 11090433, upload-time = "2026-04-03T11:11:55.049Z" }, - { url = "https://files.pythonhosted.org/packages/ea/0d/78edcc4f71934225db99df68197a107386d59080742fc7bf6bb4d007924f/curl_cffi-0.15.0-cp310-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e5e69eee735f659287e2c84444319d68a1fa68dd37abf228943a4074864283a", size = 10479178, upload-time = "2026-04-03T11:11:57.685Z" }, - { url = "https://files.pythonhosted.org/packages/5b/84/1e101c1acb1ea2f0b4992f5c3024f596d8e21db0d53540b9d583f673c4e7/curl_cffi-0.15.0-cp310-abi3-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa1323950224db24f4c510d010b3affa02196ca853fb424191fa917a513d3f4b", size = 10317051, upload-time = "2026-04-03T11:12:00.295Z" }, - { url = "https://files.pythonhosted.org/packages/28/42/8ef236b22a6c23d096c85a1dc507efe37bfdfc7a2f8a4b34efb590197369/curl_cffi-0.15.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:41f80170ba844009273b2660da1964ec31e99e5719d16b3422ada87177e32e13", size = 11299660, upload-time = "2026-04-03T11:12:02.791Z" }, - { url = "https://files.pythonhosted.org/packages/1d/01/56aeb055d962da87a1be0d74c6c644e251c7e88129b5471dc44ac724e678/curl_cffi-0.15.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1977e1e12cfb5c11352cbb74acef1bed24eb7d226dab61ca57c168c21acd4d61", size = 11945049, upload-time = "2026-04-03T11:12:05.912Z" }, - { url = "https://files.pythonhosted.org/packages/d8/8c/2abf99a38d6340d66cf0557e0c750ef3f8883dfc5d450087e01c85861343/curl_cffi-0.15.0-cp310-abi3-win_amd64.whl", hash = "sha256:5a0c1896a0d5a5ac1eb89cd24b008d2b718dd1df6fd2f75451b59ca66e49e572", size = 1661649, upload-time = "2026-04-03T11:12:07.948Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/dfd54f2240d3a9b96d77bacc62b97813b35e2aa8ecf5cd5013c683f1ba96/curl_cffi-0.15.0-cp310-abi3-win_arm64.whl", hash = "sha256:a6d57f8389273a3a1f94370473c74897467bcc36af0a17336989780c507fa43d", size = 1410741, upload-time = "2026-04-03T11:12:10.073Z" }, - { url = "https://files.pythonhosted.org/packages/19/6a/c24df8a4fc22fa84070dcd94abeba43c15e08cc09e35869565c0bad196fd/curl_cffi-0.15.0-cp313-abi3-android_24_arm64_v8a.whl", hash = "sha256:4682dc38d4336e0eb0b185374db90a760efde63cbea994b4e63f3521d44c4c92", size = 7190427, upload-time = "2026-04-03T11:12:12.142Z" }, - { url = "https://files.pythonhosted.org/packages/11/56/132225cb3491d07cc6adcce5fe395e059bde87c68cff1ef87a31c88c7819/curl_cffi-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:967ad7355bd8e9586f8c2d02eaa99953747549e7ea4a9b25cd53353e6b67fe6d", size = 2795723, upload-time = "2026-04-03T11:12:13.668Z" }, - { url = "https://files.pythonhosted.org/packages/07/8f/f4f83cd303bef7e8f1749512e5dd157e7e5d08b0a36c8211f9640a2757bf/curl_cffi-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7e63539d0d839d0a8c5eacf86229bc68c57803547f35e0db7ee0986328b478c3", size = 2573739, upload-time = "2026-04-03T11:12:15.08Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5c/643d65c7fc9acd742876aa55c2d7823c438cb7665810acd2e66c9976c4d9/curl_cffi-0.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08c799b89740b9bc49c09fbc3d5907f13ac1f845ca52620507ef9466d4639dd5", size = 10521046, upload-time = "2026-04-03T11:12:17.034Z" }, - { url = "https://files.pythonhosted.org/packages/7f/0b/9b8037113c93f4c5323096163471fa7c35c7676c3f608eeaf1287cd99d58/curl_cffi-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b7a92767a888ee90147e18964b396d8435ff42737030d6fb00824ffd6094805", size = 11096115, upload-time = "2026-04-03T11:12:19.694Z" }, - { url = "https://files.pythonhosted.org/packages/5f/96/fff2fcbd924ef4042e0d67379f751a8a4e3186a91e75e35a4cf218b306ee/curl_cffi-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:829cc357061ecb99cc2d406301f609a039e05665322f5c025ec67c38b0dc49ce", size = 11305346, upload-time = "2026-04-03T11:12:22.151Z" }, - { url = "https://files.pythonhosted.org/packages/53/1b/304b253a45ab28691c8c5e8cca1e6cbb9cf8e46dfceae4648dd536f75e73/curl_cffi-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:408d6f14e346841cd889c2e0962832bb235ba3b6749ebf609f347f747da5e60f", size = 11949834, upload-time = "2026-04-03T11:12:24.986Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ff/4723d92f08259c707a974aba27a08d0a822b9555e35ca581bf18d055a364/curl_cffi-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b624c7ce087bfda967a013ed0a64702a525444e5b6e97d23534d567ccc6525aa", size = 1702771, upload-time = "2026-04-03T11:12:28.201Z" }, - { url = "https://files.pythonhosted.org/packages/59/8c/36bbe06d66fa2b765e4a07199f643a59a9cd1a754207a96335402a9520f4/curl_cffi-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0b6c0543b993996670e9e4b78e305a2d60809d5681903ffb5568e21a387434d3", size = 1466312, upload-time = "2026-04-03T11:12:30.054Z" }, -] - [[package]] name = "debugpy" version = "1.8.20" @@ -527,15 +481,6 @@ 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 = "frozendict" -version = "2.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/b2/2a3d1374b7780999d3184e171e25439a8358c47b481f68be883c14086b4c/frozendict-2.4.7.tar.gz", hash = "sha256:e478fb2a1391a56c8a6e10cc97c4a9002b410ecd1ac28c18d780661762e271bd", size = 317082, upload-time = "2025-11-11T22:40:14.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/74/f94141b38a51a553efef7f510fc213894161ae49b88bffd037f8d2a7cb2f/frozendict-2.4.7-py3-none-any.whl", hash = "sha256:972af65924ea25cf5b4d9326d549e69a9a4918d8a76a9d3a7cd174d98b237550", size = 16264, upload-time = "2025-11-11T22:40:12.836Z" }, -] - [[package]] name = "identify" version = "2.6.19" @@ -661,7 +606,6 @@ dependencies = [ [package.optional-dependencies] notebooks = [ { name = "pandas" }, - { name = "yfinance" }, ] [package.dev-dependencies] @@ -689,7 +633,6 @@ requires-dist = [ { name = "pyyaml", specifier = ">=6.0.3" }, { name = "requests", specifier = ">=2.33.1" }, { name = "tenacity", specifier = ">=9.0.0" }, - { name = "yfinance", marker = "extra == 'notebooks'", specifier = ">=0.2,<1" }, ] provides-extras = ["notebooks"] @@ -766,18 +709,6 @@ 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 = "markdown-it-py" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, -] - [[package]] name = "markupsafe" version = "3.0.3" @@ -864,24 +795,6 @@ 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 = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "multitasking" -version = "0.0.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/c3/ac2cc9307fb15cc28ed6d4a9266b216c83ee7fe64299f0264047982bce88/multitasking-0.0.13.tar.gz", hash = "sha256:d896b5df877c9ca5eeddbf0e5994124694d6cb535aba698fb23344c7025155a1", size = 20585, upload-time = "2026-04-23T12:14:15.049Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/1c/24dbf69b247f287401c904a396233a43c89fd4fb9b7cd2e50e430e9cd57c/multitasking-0.0.13-py3-none-any.whl", hash = "sha256:ec9243af140c67bfe52dc98d7173c294512735a88e8425c458b250db99dc2b48", size = 16380, upload-time = "2026-04-23T12:14:13.776Z" }, -] - [[package]] name = "nbformat" version = "5.10.4" @@ -1143,21 +1056,12 @@ 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 = "peewee" -version = "4.0.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/09/a3b2a32ce498f405dce4320267e99b1b076c1ea39ad01151a353bc7f81d7/peewee-4.0.6.tar.gz", hash = "sha256:ea2f78f24ff9e3660281dc5b0be8bc00d9a9514bdc40c98e416fcd042b66ac6a", size = 724591, upload-time = "2026-05-20T13:18:17.26Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/6a/e1455b94ee48f5666f2e7831b6247098794bfe9747da457111be4d0bea10/peewee-4.0.6-py3-none-any.whl", hash = "sha256:5fa665913c410f0b5faef1469ed0aa9eceb9fef262665ebbb6f29408f826eeeb", size = 146222, upload-time = "2026-05-20T13:18:15.694Z" }, -] - [[package]] name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { 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 = [ @@ -1210,21 +1114,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, ] -[[package]] -name = "protobuf" -version = "7.35.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/60/fd/5b1491d9e4b586d621c54f4c36b888714164b6875f8d6afa3f9072906a51/protobuf-7.35.0.tar.gz", hash = "sha256:a2efd84605f41e559f1881b0912b44099d0a2ac9bf46b3474823f10fb393b0e6", size = 458677, upload-time = "2026-05-19T23:02:29.197Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/ee/93d06e358a4aa32280b00e722d3ea0a1f25fc3cc5778d80581c9cca2c10e/protobuf-7.35.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:66be6c513931c794fa92c080ffee41671390da3d79da219cf9c0c0907f035dda", size = 433225, upload-time = "2026-05-19T23:02:19.884Z" }, - { url = "https://files.pythonhosted.org/packages/8b/39/1c76c2da93f3c507e958e0aecee2391cc44d4625de6c728bbc555195b5a8/protobuf-7.35.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5", size = 328847, upload-time = "2026-05-19T23:02:22.3Z" }, - { url = "https://files.pythonhosted.org/packages/91/1a/39f7ce90a238c1a987a4d81ec26379e02ca0aff367de68e4a1fa474215b9/protobuf-7.35.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4cbf5cc286130e06a6c9bbefac442431173906dfcc979712183d4adcc01b37ee", size = 344030, upload-time = "2026-05-19T23:02:23.591Z" }, - { url = "https://files.pythonhosted.org/packages/70/5b/6baf9008817964454055ff3fe65f1de0b5f1e26c80c82f7fb108b7cd4ea3/protobuf-7.35.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:6c0f98f10c8a05ea30f8993dfef2de093d27b490fdae78bb60c8343795d55011", size = 327130, upload-time = "2026-05-19T23:02:24.637Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e5/e46adb0badc388bfb84877a5f9f026aff63f60e611016cf64dbe77e05446/protobuf-7.35.0-cp310-abi3-win32.whl", hash = "sha256:4c4617b83ade0e279d1d2bfe04025a1adb87f9ed657de038620dc0ff959357f6", size = 428946, upload-time = "2026-05-19T23:02:25.741Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ab/547fbd9e16d879dd13c167478f8ae0a83a428008ca07a5e06acdc23ad473/protobuf-7.35.0-cp310-abi3-win_amd64.whl", hash = "sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201", size = 439996, upload-time = "2026-05-19T23:02:26.808Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ef/50433d346c56657a70d27f156c7b349ac59a068b01de4eb796e747eecc43/protobuf-7.35.0-py3-none-any.whl", hash = "sha256:c13f325cf242bad135c350629eeb5d54b24228eb472fb3e2e9ebbd4c5dc20ca0", size = 171659, upload-time = "2026-05-19T23:02:27.842Z" }, -] - [[package]] name = "psutil" version = "7.2.2" @@ -1483,15 +1372,6 @@ 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 = "pytz" -version = "2026.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -1646,19 +1526,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] -[[package]] -name = "rich" -version = "15.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, -] - [[package]] name = "rpds-py" version = "0.30.0" @@ -1801,15 +1668,6 @@ 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 = "soupsieve" -version = "2.8.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, -] - [[package]] name = "stack-data" version = "0.6.3" @@ -2027,65 +1885,6 @@ 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/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.2.1" @@ -2160,26 +1959,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/c0/782b86e28d1ceebeb74cccea12d2cd3d2ba0bd68e3dec20b1bc5873f6127/wrapt-2.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:f70db64e8266d7c45d3b735f2e08eeb434b5e03da9a479ae42b2e2e486a21a00", size = 80722, upload-time = "2026-05-22T14:49:23.59Z" }, { url = "https://files.pythonhosted.org/packages/53/46/29ac9daf11a86c22a8c38cd9236c62928ccae83f7ceb06bd3b0467cf9d05/wrapt-2.2.1-py3-none-any.whl", hash = "sha256:3aafea2975caef8ca49400640dde02cc7426e798f24870ed01f490bc3cffd32f", size = 61000, upload-time = "2026-05-22T14:49:41.593Z" }, ] - -[[package]] -name = "yfinance" -version = "0.2.66" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "curl-cffi" }, - { name = "frozendict" }, - { name = "multitasking" }, - { name = "numpy" }, - { name = "pandas" }, - { name = "peewee" }, - { name = "platformdirs" }, - { name = "protobuf" }, - { name = "pytz" }, - { name = "requests" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/59/73/50450b9906c5137d2d02fde6f7360865366c72baea1f8d0550cc990829ce/yfinance-0.2.66.tar.gz", hash = "sha256:fae354cc1649109444b2c84194724afcc52c2a7799551ce44c739424ded6af9c", size = 132820, upload-time = "2025-09-17T11:22:35.422Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/bf/7c0c89ff8ba53592b9cb5157f70e90d8bbb04d60094fc4f10035e158b981/yfinance-0.2.66-py2.py3-none-any.whl", hash = "sha256:511a1a40a687f277aae3a02543009a8aeaa292fce5509671f58915078aebb5c7", size = 123427, upload-time = "2025-09-17T11:22:33.972Z" }, -]