diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f535c50..780d840e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,6 +48,8 @@ jobs: uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: version: ${{ env.UV_VERSION }} + python-version: ${{ matrix.python-version }} + cache-suffix: py${{ matrix.python-version }} - name: Sync dependencies (locked) run: uv sync --locked --extra dev @@ -122,6 +124,8 @@ jobs: uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: version: ${{ env.UV_VERSION }} + python-version: ${{ matrix.python-version }} + cache-suffix: py${{ matrix.python-version }} - name: Sync dependencies (locked) run: uv sync --locked --extra dev @@ -196,6 +200,8 @@ jobs: uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: version: ${{ env.UV_VERSION }} + python-version: ${{ matrix.python-version }} + cache-suffix: py${{ matrix.python-version }} - name: Sync dependencies (locked) run: uv sync --locked --extra dev diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6c25c48..f4c77b76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,9 +17,7 @@ on: workflow_dispatch: {} push: branches: - - "*" - - "*/*" - - "**" + - master pull_request: branches: - master diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 88ec0fb5..3b49afa2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,12 +23,12 @@ repos: - --non-interactive - --config-file=pyproject.toml additional_dependencies: - - pandas-stubs - - pydantic - - scipy-stubs - - types-openpyxl - - types-python-dateutil - - types-requests + - pandas-stubs>=2.1.2 + - pydantic>=2.5.2 + - scipy-stubs>=1.14.1.0 + - types-openpyxl>=3.1.2 + - types-python-dateutil>=2.8.2 + - types-requests>=2.20.0 - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: diff --git a/docs/README.md b/docs/README.md index 9c35f030..1a3bfb20 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,12 +6,15 @@ This directory contains the documentation for the openseries project, built usin ### Prerequisites -Install the documentation dependencies using uv: +From the repository root, install the documentation dependencies from the +lockfile (requires the pinned uv version used in `Makefile` / CI): ```bash -uv pip install -e ".[docs]" +uv sync --locked --extra docs ``` +Alternatively, `make install` installs the `dev` and `docs` extras together. + ### Building HTML Documentation To build the HTML documentation: @@ -192,8 +195,8 @@ When contributing to documentation: **Missing modules:** -- Install missing dependencies: `uv pip install -e ".[docs]"` -- For ReadTheDocs builds, dependencies are managed through `pyproject.toml` +- Install missing dependencies: `uv sync --locked --extra docs` +- For ReadTheDocs builds, `docs/requirements.txt` must match the `docs` extra in `pyproject.toml` **Broken links:** diff --git a/docs/build/html/.doctrees/environment.pickle b/docs/build/html/.doctrees/environment.pickle index 21d586f6..cc65bea2 100644 Binary files a/docs/build/html/.doctrees/environment.pickle and b/docs/build/html/.doctrees/environment.pickle differ diff --git a/docs/build/html/.doctrees/user_guide/installation.doctree b/docs/build/html/.doctrees/user_guide/installation.doctree index e237b9a1..9313c000 100644 Binary files a/docs/build/html/.doctrees/user_guide/installation.doctree and b/docs/build/html/.doctrees/user_guide/installation.doctree differ diff --git a/docs/build/html/_modules/openseries/datefixer.html b/docs/build/html/_modules/openseries/datefixer.html index 39814b84..d17fca60 100644 --- a/docs/build/html/_modules/openseries/datefixer.html +++ b/docs/build/html/_modules/openseries/datefixer.html @@ -108,7 +108,7 @@

Source code for openseries.datefixer

-"""Date related utilities."""
+"""Date related utilities."""
 
 from __future__ import annotations
 
@@ -147,12 +147,12 @@ 

Source code for openseries.datefixer

     )
 
 __all__ = [
-    "date_fix",
-    "date_offset_foll",
-    "generate_calendar_date_range",
-    "get_previous_business_day_before_today",
-    "holiday_calendar",
-    "offset_business_days",
+    "date_fix",
+    "date_offset_foll",
+    "generate_calendar_date_range",
+    "get_previous_business_day_before_today",
+    "holiday_calendar",
+    "offset_business_days",
 ]
 
 
@@ -161,7 +161,7 @@ 

Source code for openseries.datefixer

     endyear: int,
     markets: str | list[str],
 ) -> list[str]:
-    """Return a list of holiday dates mapping to list of markets closed.
+    """Return a list of holiday dates mapping to list of markets closed.
 
     Args:
         startyear: First year (inclusive) to consider.
@@ -174,15 +174,15 @@ 

Source code for openseries.datefixer

     Raises:
         MarketsNotStringNorListStrError: If any market code is not supported by
             ``exchange_calendars`` or the input is not a string or list of strings.
-    """
+    """
     market_list = [markets] if isinstance(markets, str) else list(markets)
 
     supported = exchcal.get_calendar_names()
 
     if not all(m in supported for m in market_list):
         msg = (
-            "Argument markets must be a string market code or a list of market "
-            "codes supported by exchange_calendars."
+            "Argument markets must be a string market code or a list of market "
+            "codes supported by exchange_calendars."
         )
         raise MarketsNotStringNorListStrError(msg)
 
@@ -191,7 +191,7 @@ 

Source code for openseries.datefixer

         cal = exchcal.get_calendar(m)
         cal_hols = cal.regular_holidays.holidays()
         my_hols: list[str] = [
-            date.date().strftime("%Y-%m-%d")
+            date.date().strftime("%Y-%m-%d")
             for date in cal_hols
             if (startyear <= date.date().year <= endyear)
         ]
@@ -205,17 +205,17 @@ 

Source code for openseries.datefixer

 def holiday_calendar(
     startyear: int,
     endyear: int,
-    countries: CountriesType = "SE",
+    countries: CountriesType = "SE",
     markets: list[str] | str | None = None,
     custom_holidays: list[str] | str | None = None,
 ) -> busdaycalendar:
-    """Generate a business calendar.
+    """Generate a business calendar.
 
     Args:
         startyear: First year in date range generated.
         endyear: Last year in date range generated.
         countries: (List of) country code(s) according to ISO 3166-1 alpha-2.
-            Defaults to "SE".
+            Defaults to "SE".
         markets: (List of) markets code(s) supported by exchange_calendars.
         custom_holidays: Argument where missing holidays can be added.
 
@@ -225,7 +225,7 @@ 

Source code for openseries.datefixer

     Raises:
         CountriesNotStringNorListStrError: If ``countries`` is not a supported
             ISO 3166-1 alpha-2 string or a list of such strings.
-    """
+    """
     startyear -= 1
     endyear += 1
     if startyear == endyear:
@@ -243,11 +243,11 @@ 

Source code for openseries.datefixer

         for country in countries:
             staging = country_holidays(country=country, years=years)
             countryholidays += list(staging)
-        hols = cast("list[dt.date]", list(countryholidays))
+        hols = cast("list[dt.date]", list(countryholidays))
     else:
         msg = (
-            "Argument countries must be a string country code or "
-            "a list of string country codes according to ISO 3166-1 alpha-2."
+            "Argument countries must be a string country code or "
+            "a list of string country codes according to ISO 3166-1 alpha-2."
         )
         raise CountriesNotStringNorListStrError(msg)
 
@@ -268,7 +268,7 @@ 

Source code for openseries.datefixer

         )
         hols.extend([date_fix(fixerdate=ddate) for ddate in custom_list])
 
-    return busdaycalendar(holidays=array(sorted(set(hols)), dtype="datetime64[D]"))
+ return busdaycalendar(holidays=array(sorted(set(hols)), dtype="datetime64[D]"))
@@ -277,7 +277,7 @@

Source code for openseries.datefixer

 def date_fix(
     fixerdate: DateType,
 ) -> dt.date:
-    """Parse different date formats into datetime.date.
+    """Parse different date formats into datetime.date.
 
     Args:
         fixerdate: The data item to parse.
@@ -287,18 +287,18 @@ 

Source code for openseries.datefixer

 
     Raises:
         TypeError: If the provided ``fixerdate`` type is not supported.
-    """
-    msg = f"Unknown date format {fixerdate!s} of type {type(fixerdate)!s} encountered"
+    """
+    msg = f"Unknown date format {fixerdate!s} of type {type(fixerdate)!s} encountered"
     if isinstance(fixerdate, Timestamp | dt.datetime):
         return fixerdate.date()
     if isinstance(fixerdate, dt.date):
         return fixerdate
     if isinstance(fixerdate, datetime64):
         return (
-            dt.datetime.strptime(str(fixerdate)[:10], "%Y-%m-%d").astimezone().date()
+            dt.datetime.strptime(str(fixerdate)[:10], "%Y-%m-%d").astimezone().date()
         )
     if isinstance(fixerdate, str):
-        return dt.datetime.strptime(fixerdate, "%Y-%m-%d").astimezone().date()
+        return dt.datetime.strptime(fixerdate, "%Y-%m-%d").astimezone().date()
     raise TypeError(msg)
@@ -308,20 +308,20 @@

Source code for openseries.datefixer

 def date_offset_foll(
     raw_date: DateType,
     months_offset: int = 12,
-    countries: CountriesType = "SE",
+    countries: CountriesType = "SE",
     markets: list[str] | str | None = None,
     custom_holidays: list[str] | str | None = None,
     *,
     adjust: bool = False,
     following: bool = True,
 ) -> dt.date:
-    """Offset dates according to a given calendar.
+    """Offset dates according to a given calendar.
 
     Args:
         raw_date: The date to offset from.
         months_offset: Number of months as integer. Defaults to 12.
         countries: (List of) country code(s) according to ISO 3166-1 alpha-2.
-            Defaults to "SE".
+            Defaults to "SE".
         markets: (List of) markets code(s) supported by exchange_calendars.
         custom_holidays: Argument where missing holidays can be added.
         adjust: Determines if offset should adjust for business days.
@@ -331,7 +331,7 @@ 

Source code for openseries.datefixer

 
     Returns:
         Offset date.
-    """
+    """
     raw_date = date_fix(raw_date)
     month_delta = relativedelta(months=months_offset)
 
@@ -360,22 +360,22 @@ 

Source code for openseries.datefixer

 [docs]
 def get_previous_business_day_before_today(
     today: dt.date | None = None,
-    countries: CountriesType = "SE",
+    countries: CountriesType = "SE",
     markets: list[str] | str | None = None,
     custom_holidays: list[str] | str | None = None,
 ) -> dt.date:
-    """Bump date backwards to find the previous business day.
+    """Bump date backwards to find the previous business day.
 
     Args:
         today: Manual input of the day from where the previous business day is found.
         countries: (List of) country code(s) according to ISO 3166-1 alpha-2.
-            Defaults to "SE".
+            Defaults to "SE".
         markets: (List of) markets code(s) supported by exchange_calendars.
         custom_holidays: Argument where missing holidays can be added.
 
     Returns:
         The previous business day.
-    """
+    """
     if today is None:
         today = dt.datetime.now().astimezone().date()
 
@@ -396,11 +396,11 @@ 

Source code for openseries.datefixer

 def offset_business_days(
     ddate: dt.date,
     days: int,
-    countries: CountriesType = "SE",
+    countries: CountriesType = "SE",
     markets: list[str] | str | None = None,
     custom_holidays: list[str] | str | None = None,
 ) -> dt.date:
-    """Bump date by business days.
+    """Bump date by business days.
 
     It first adjusts to a valid business day and then bumps with given
     number of business days from there.
@@ -411,13 +411,13 @@ 

Source code for openseries.datefixer

             that is given.
             If days is set as anything other than an integer its value is set to zero.
         countries: (List of) country code(s) according to ISO 3166-1 alpha-2.
-            Defaults to "SE".
+            Defaults to "SE".
         markets: (List of) markets code(s) supported by exchange_calendars.
         custom_holidays: Argument where missing holidays can be added.
 
     Returns:
         The new offset business day.
-    """
+    """
     try:
         days = int(days)
     except TypeError:
@@ -468,7 +468,7 @@ 

Source code for openseries.datefixer

 
     idx = where(array(local_bdays) == ddate)[0]
 
-    return cast("dt.date", local_bdays[idx[0] + days])
+ return cast("dt.date", local_bdays[idx[0] + days])
@@ -478,26 +478,26 @@

Source code for openseries.datefixer

     trading_days: int,
     start: dt.date | None = None,
     end: dt.date | None = None,
-    countries: CountriesType = "SE",
+    countries: CountriesType = "SE",
     markets: list[str] | str | None = None,
     custom_holidays: list[str] | str | None = None,
 ) -> list[dt.date]:
-    """Generate a list of business day calendar dates.
+    """Generate a list of business day calendar dates.
 
     Args:
         trading_days: Number of days to generate. Must be greater than zero.
         start: Date when the range starts.
         end: Date when the range ends.
         countries: (List of) country code(s) according to ISO 3166-1 alpha-2.
-            Defaults to "SE".
+            Defaults to "SE".
         markets: (List of) markets code(s) supported by exchange_calendars.
         custom_holidays: Argument where missing holidays can be added.
 
     Returns:
         List of business day calendar dates.
-    """
+    """
     if trading_days < 1:
-        msg = "Argument trading_days must be greater than zero."
+        msg = "Argument trading_days must be greater than zero."
         raise TradingDaysNotAboveZeroError(msg)
 
     if start and not end:
@@ -513,7 +513,7 @@ 

Source code for openseries.datefixer

         tmp_range = date_range(
             start=adjusted_start,
             periods=trading_days * 365 // 252,
-            freq="D",
+            freq="D",
         )
         calendar = holiday_calendar(
             startyear=adjusted_start.year,
@@ -544,7 +544,7 @@ 

Source code for openseries.datefixer

         tmp_range = date_range(
             end=adjusted_end,
             periods=trading_days * 365 // 252,
-            freq="D",
+            freq="D",
         )
         calendar = holiday_calendar(
             startyear=date_fix(tmp_range.tolist()[0]).year,
@@ -563,8 +563,8 @@ 

Source code for openseries.datefixer

         ]
 
     msg = (
-        "Provide exactly one of start or end date. "
-        "Date range is inferred from number of trading days."
+        "Provide exactly one of start or end date. "
+        "Date range is inferred from number of trading days."
     )
     raise BothStartAndEndError(msg)
@@ -577,7 +577,7 @@

Source code for openseries.datefixer

     markets: list[str] | str | None = None,
     custom_holidays: list[str] | str | None = None,
 ) -> DatetimeIndex:
-    """Resample timeseries frequency to business calendar month end dates.
+    """Resample timeseries frequency to business calendar month end dates.
 
     Stubs left in place. Stubs will be aligned to the shortest stub.
 
@@ -591,7 +591,7 @@ 

Source code for openseries.datefixer

     Returns:
         A date range aligned to business period ends.
 
-    """
+    """
     copydata = data.copy()
     copydata.index = DatetimeIndex(copydata.index)
     copydata = copydata.resample(rule=freq).last()
diff --git a/docs/build/html/_modules/openseries/frame.html b/docs/build/html/_modules/openseries/frame.html
index 16a0af71..82e91883 100644
--- a/docs/build/html/_modules/openseries/frame.html
+++ b/docs/build/html/_modules/openseries/frame.html
@@ -108,7 +108,7 @@
            

Source code for openseries.frame

-"""The OpenFrame class."""
+"""The OpenFrame class."""
 
 from __future__ import annotations
 
@@ -188,31 +188,31 @@ 

Source code for openseries.frame

 
 logger = getLogger(__name__)
 
-__all__ = ["OpenFrame"]
+__all__ = ["OpenFrame"]
 
 
 
[docs] class OpenFrame(_CommonModel[SeriesFloat]): - """OpenFrame objects hold OpenTimeSeries in the list constituents. + """OpenFrame objects hold OpenTimeSeries in the list constituents. The intended use is to allow comparisons across these timeseries. Args: constituents: List of objects of Class OpenTimeSeries. weights: List of weights in float format. Optional. - """ + """ - @field_validator("constituents") + @field_validator("constituents") @classmethod def _check_labels_unique( cls: type[OpenFrame], tseries: list[OpenTimeSeries], ) -> list[OpenTimeSeries]: - """Pydantic validator ensuring that OpenFrame labels are unique.""" + """Pydantic validator ensuring that OpenFrame labels are unique.""" labls = [x.label for x in tseries] if len(set(labls)) != len(labls): - msg = "TimeSeries names/labels must be unique" + msg = "TimeSeries names/labels must be unique" raise LabelsNotUniqueError(msg) return tseries @@ -223,14 +223,14 @@

Source code for openseries.frame

         constituents: list[OpenTimeSeries],
         weights: list[float] | None = None,
     ) -> None:
-        """OpenFrame objects hold OpenTimeSeries in the list constituents.
+        """OpenFrame objects hold OpenTimeSeries in the list constituents.
 
         The intended use is to allow comparisons across these timeseries.
 
         Args:
             constituents: List of objects of Class OpenTimeSeries.
             weights: List of weights in float format. Optional.
-        """
+        """
         copied_constituents = [ts.from_deepcopy() for ts in constituents]
 
         super().__init__(
@@ -241,16 +241,16 @@ 

Source code for openseries.frame

 
 
     def _set_tsdf(self: Self) -> None:
-        """Set the tsdf DataFrame."""
+        """Set the tsdf DataFrame."""
         if self.constituents is not None and len(self.constituents) != 0:
             if len(self.constituents) == 1:
                 self.tsdf = self.constituents[0].tsdf.copy()
             else:
                 self.tsdf = concat(
-                    [x.tsdf for x in self.constituents], axis="columns", sort=True
+                    [x.tsdf for x in self.constituents], axis="columns", sort=True
                 )
         else:
-            logger.warning("OpenFrame() was passed an empty list.")
+            logger.warning("OpenFrame() was passed an empty list.")
 
     def _coerce_result(
         self: Self,
@@ -261,17 +261,17 @@ 

Source code for openseries.frame

             data=result,
             index=self.tsdf.columns,
             name=name,
-            dtype="float64",
+            dtype="float64",
         )
 
 
[docs] def from_deepcopy(self: Self) -> Self: - """Create copy of the OpenFrame object. + """Create copy of the OpenFrame object. Returns: An OpenFrame object. - """ + """ return deepcopy(self)
@@ -279,16 +279,16 @@

Source code for openseries.frame

 [docs]
     def merge_series(
         self: Self,
-        how: LiteralHowMerge = "outer",
+        how: LiteralHowMerge = "outer",
     ) -> Self:
-        """Merge index of Pandas Dataframes of the constituent OpenTimeSeries.
+        """Merge index of Pandas Dataframes of the constituent OpenTimeSeries.
 
         Args:
-            how: The Pandas merge method. Defaults to "outer".
+            how: The Pandas merge method. Defaults to "outer".
 
         Returns:
             An OpenFrame object.
-        """
+        """
         lvl_zero = list(self.columns_lvl_zero)
         self.tsdf = reduce(
             lambda left, right: merge(
@@ -306,12 +306,12 @@ 

Source code for openseries.frame

 
         if self.tsdf.empty:
             msg = (
-                "Merging OpenTimeSeries DataFrames with "
-                f"argument how={how} produced an empty DataFrame."
+                "Merging OpenTimeSeries DataFrames with "
+                f"argument how={how} produced an empty DataFrame."
             )
             raise MergingResultedInEmptyError(msg)
 
-        if how == "inner":
+        if how == "inner":
             for xerie in self.constituents:
                 xerie.tsdf = xerie.tsdf.loc[self.tsdf.index]
         return self
@@ -323,7 +323,7 @@

Source code for openseries.frame

         self: Self,
         properties: list[LiteralFrameProps] | None = None,
     ) -> DataFrame:
-        """Calculate chosen timeseries properties.
+        """Calculate chosen timeseries properties.
 
         Args:
             properties: The properties to calculate. Defaults to calculating all
@@ -331,7 +331,7 @@ 

Source code for openseries.frame

 
         Returns:
             Properties of the constituent OpenTimeSeries.
-        """
+        """
         if properties:
             props = OpenFramePropertiesList(*properties)
             prop_list = [getattr(self, x) for x in props]
@@ -339,113 +339,113 @@ 

Source code for openseries.frame

             prop_list = [
                 getattr(self, x) for x in OpenFramePropertiesList.allowed_strings
             ]
-        return cast("DataFrame", concat(prop_list, axis="columns").T)
+ return cast("DataFrame", concat(prop_list, axis="columns").T)
@property def lengths_of_items(self: Self) -> Series[int]: - """Number of observations of all constituents. + """Number of observations of all constituents. Returns: Number of observations of all constituents. - """ + """ return Series( data=[self.tsdf[col].count() for col in self.tsdf.columns], index=self.tsdf.columns, - name="observations", + name="observations", ).astype(int) @property def item_count(self: Self) -> int: - """Number of constituents. + """Number of constituents. Returns: Number of constituents. - """ + """ return len(self.constituents) @property def columns_lvl_zero(self: Self) -> list[str]: - """Level 0 values of the MultiIndex columns in the .tsdf DataFrame. + """Level 0 values of the MultiIndex columns in the .tsdf DataFrame. Returns: Level 0 values of the MultiIndex columns in the .tsdf DataFrame. - """ + """ return list(self.tsdf.columns.get_level_values(0)) @property def columns_lvl_one(self: Self) -> list[ValueType]: - """Level 1 values of the MultiIndex columns in the .tsdf DataFrame. + """Level 1 values of the MultiIndex columns in the .tsdf DataFrame. Returns: Level 1 values of the MultiIndex columns in the .tsdf DataFrame. - """ + """ return list(self.tsdf.columns.get_level_values(1)) @property def _value_types(self: Self) -> list[bool]: - """Cached value type checks for efficiency. + """Cached value type checks for efficiency. Returns: List of booleans indicating if each column is ValueType.RTRN. - """ + """ return [x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)] @property def first_indices(self: Self) -> Series[dt.date]: - """The first dates in the timeseries of all constituents. + """The first dates in the timeseries of all constituents. Returns: The first dates in the timeseries of all constituents. - """ + """ return Series( data=[i.first_idx for i in self.constituents], index=self.tsdf.columns, - name="first indices", - dtype="datetime64[ns]", + name="first indices", + dtype="datetime64[ns]", ).dt.date @property def last_indices(self: Self) -> Series[dt.date]: - """The last dates in the timeseries of all constituents. + """The last dates in the timeseries of all constituents. Returns: The last dates in the timeseries of all constituents. - """ + """ return Series( data=[i.last_idx for i in self.constituents], index=self.tsdf.columns, - name="last indices", - dtype="datetime64[ns]", + name="last indices", + dtype="datetime64[ns]", ).dt.date @property def span_of_days_all(self: Self) -> Series[int]: - """Number of days from the first date to the last for all items in the frame. + """Number of days from the first date to the last for all items in the frame. Returns: Number of days from the first date to the last for all items in the frame. - """ + """ return Series( data=[c.span_of_days for c in self.constituents], index=self.tsdf.columns, - name="span of days", + name="span of days", ).astype(int)
[docs] def value_to_ret(self: Self) -> Self: - """Convert series of values into series of returns. + """Convert series of values into series of returns. Returns: The returns of the values in the series. - """ + """ returns = self.tsdf.ffill().pct_change() returns.iloc[0] = 0 new_labels: list[ValueType] = [ValueType.RTRN] * self.item_count arrays = cast( - "Any", + "Any", [ self.tsdf.columns.get_level_values(0), new_labels, @@ -459,7 +459,7 @@

Source code for openseries.frame

 
[docs] def value_to_diff(self: Self, periods: int = 1) -> Self: - """Convert series of values to series of their period differences. + """Convert series of values to series of their period differences. Args: periods: The number of periods between observations over which @@ -467,12 +467,12 @@

Source code for openseries.frame

 
         Returns:
             An OpenFrame object.
-        """
+        """
         self.tsdf = self.tsdf.diff(periods=periods)
         self.tsdf.iloc[0] = 0
         new_labels: list[ValueType] = [ValueType.RTRN] * self.item_count
         arrays = cast(
-            "Any",
+            "Any",
             [
                 self.tsdf.columns.get_level_values(0),
                 new_labels,
@@ -485,11 +485,11 @@ 

Source code for openseries.frame

 
[docs] def to_cumret(self: Self) -> Self: - """Convert series of returns into cumulative series of values. + """Convert series of returns into cumulative series of values. Returns: An OpenFrame object. - """ + """ vtypes = self._value_types if not any(vtypes): returns = self.tsdf.ffill().pct_change() @@ -498,7 +498,7 @@

Source code for openseries.frame

             returns = self.tsdf.copy()
             returns.iloc[0] = 0
         else:
-            msg = "Mix of series types will give inconsistent results"
+            msg = "Mix of series types will give inconsistent results"
             raise MixedValuetypesError(msg)
 
         returns = returns.add(1.0)
@@ -506,7 +506,7 @@ 

Source code for openseries.frame

 
         new_labels: list[ValueType] = [ValueType.PRICE] * self.item_count
         arrays = cast(
-            "Any",
+            "Any",
             [
                 self.tsdf.columns.get_level_values(0),
                 new_labels,
@@ -520,24 +520,24 @@ 

Source code for openseries.frame

 [docs]
     def resample(
         self: Self,
-        freq: LiteralBizDayFreq | str = "BME",
+        freq: LiteralBizDayFreq | str = "BME",
     ) -> Self:
-        """Resample the timeseries frequency.
+        """Resample the timeseries frequency.
 
         Args:
             freq: The date offset string that sets the resampled frequency.
-                Defaults to "BME".
+                Defaults to "BME".
 
         Returns:
             An OpenFrame object.
-        """
+        """
         vtypes = self._value_types
         if not any(vtypes):
             value_type = ValueType.PRICE
         elif all(vtypes):
             value_type = ValueType.RTRN
         else:
-            msg = "Mix of series types will give inconsistent results"
+            msg = "Mix of series types will give inconsistent results"
             raise MixedValuetypesError(msg)
 
         self.tsdf.index = DatetimeIndex(self.tsdf.index)
@@ -561,28 +561,28 @@ 

Source code for openseries.frame

 [docs]
     def resample_to_business_period_ends(
         self: Self,
-        freq: LiteralBizDayFreq = "BME",
-        method: LiteralPandasReindexMethod = "nearest",
+        freq: LiteralBizDayFreq = "BME",
+        method: LiteralPandasReindexMethod = "nearest",
     ) -> Self:
-        """Resamples timeseries frequency to the business calendar month end dates.
+        """Resamples timeseries frequency to the business calendar month end dates.
 
         Stubs left in place. Stubs will be aligned to the shortest stub.
 
         Args:
             freq: The date offset string that sets the resampled frequency.
-                Defaults to "BME".
+                Defaults to "BME".
             method: Controls the method used to align values across columns.
                 Defaults to nearest.
 
         Returns:
             An OpenFrame object.
-        """
+        """
         vtypes = self._value_types
         if any(vtypes):
             msg = (
-                "Do not run resample_to_business_period_ends on return series. "
-                "The operation will pick the last data point in the sparser series. "
-                "It will not sum returns and therefore data will be lost."
+                "Do not run resample_to_business_period_ends on return series. "
+                "The operation will pick the last data point in the sparser series. "
+                "It will not sum returns and therefore data will be lost."
             )
             raise ResampleDataLossError(msg)
 
@@ -625,7 +625,7 @@ 

Source code for openseries.frame

         to_date: dt.date | None = None,
         periods_in_a_year_fixed: DaysInYearType | None = None,
     ) -> DataFrame:
-        """Exponentially Weighted Moving Average Volatilities and Correlation.
+        """Exponentially Weighted Moving Average Volatilities and Correlation.
 
         Exponentially Weighted Moving Average (EWMA) for Volatilities and
         Correlation.
@@ -649,7 +649,7 @@ 

Source code for openseries.frame

 
         Returns:
             Series volatilities and correlation.
-        """
+        """
         earlier, later = self.calc_range(
             months_offset=months_from_last,
             from_dt=from_date,
@@ -658,7 +658,7 @@ 

Source code for openseries.frame

         if periods_in_a_year_fixed is None:
             fraction = (later - earlier).days / 365.25
             how_many = (
-                self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)]
+                self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)]
                 .count()
                 .iloc[0]
             )
@@ -667,17 +667,17 @@ 

Source code for openseries.frame

             time_factor = periods_in_a_year_fixed
 
         corr_label = (
-            cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0]
-            + "_VS_"
-            + cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0]
+            cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0]
+            + "_VS_"
+            + cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0]
         )
         cols = [
-            cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0],
-            cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0],
+            cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0],
+            cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0],
         ]
 
         data = self.tsdf.loc[
-            cast("Timestamp", earlier) : cast("Timestamp", later)
+            cast("Timestamp", earlier) : cast("Timestamp", later)
         ].copy()
 
         for rtn in cols:
@@ -740,25 +740,25 @@ 

Source code for openseries.frame

 
     @property
     def correl_matrix(self: Self) -> DataFrame:
-        """Correlation matrix.
+        """Correlation matrix.
 
         This property returns the correlation matrix of the time series
         in the frame.
 
         Returns:
             Correlation matrix of the time series in the frame.
-        """
+        """
         corr_matrix = (
             self.tsdf.ffill()
             .pct_change()
             .corr(
-                method="pearson",
+                method="pearson",
                 min_periods=1,
             )
         )
         corr_matrix.columns = corr_matrix.columns.get_level_values(0)
         corr_matrix.index = corr_matrix.index.get_level_values(0)
-        corr_matrix.index.name = "Correlation"
+        corr_matrix.index.name = "Correlation"
         return corr_matrix
 
 
@@ -767,30 +767,30 @@

Source code for openseries.frame

         self: Self,
         new_series: OpenTimeSeries,
     ) -> Self:
-        """To add an OpenTimeSeries object.
+        """To add an OpenTimeSeries object.
 
         Args:
             new_series: The timeseries to add.
 
         Returns:
             An OpenFrame object.
-        """
+        """
         self.constituents += [new_series]
-        self.tsdf = concat([self.tsdf, new_series.tsdf], axis="columns", sort=True)
+        self.tsdf = concat([self.tsdf, new_series.tsdf], axis="columns", sort=True)
         return self
[docs] def delete_timeseries(self: Self, lvl_zero_item: str) -> Self: - """To delete an OpenTimeSeries object. + """To delete an OpenTimeSeries object. Args: lvl_zero_item: The .tsdf column level 0 value of the timeseries to delete. Returns: An OpenFrame object. - """ + """ if self.weights: new_c, new_w = [], [] for serie, weight in zip(self.constituents, self.weights, strict=True): @@ -803,7 +803,7 @@

Source code for openseries.frame

             self.constituents = [
                 item for item in self.constituents if item.label != lvl_zero_item
             ]
-        self.tsdf = self.tsdf.drop(lvl_zero_item, axis="columns", level=0)
+        self.tsdf = self.tsdf.drop(lvl_zero_item, axis="columns", level=0)
         return self
@@ -813,9 +813,9 @@

Source code for openseries.frame

         self: Self,
         start_cut: dt.date | None = None,
         end_cut: dt.date | None = None,
-        where: LiteralTrunc = "both",
+        where: LiteralTrunc = "both",
     ) -> Self:
-        """Truncate DataFrame such that all timeseries have the same time span.
+        """Truncate DataFrame such that all timeseries have the same time span.
 
         Args:
             start_cut: New first date. Optional.
@@ -825,10 +825,10 @@ 

Source code for openseries.frame

 
         Returns:
             An OpenFrame object.
-        """
-        if not start_cut and where in ["before", "both"]:
+        """
+        if not start_cut and where in ["before", "both"]:
             start_cut = self.first_indices.max()
-        if not end_cut and where in ["after", "both"]:
+        if not end_cut and where in ["after", "both"]:
             end_cut = self.last_indices.min()
         self.tsdf = self.tsdf.sort_index()
         self.tsdf = self.tsdf.truncate(before=start_cut, after=end_cut)
@@ -840,16 +840,16 @@ 

Source code for openseries.frame

             )
         if len(set(self.first_indices)) != 1:
             msg = (
-                f"One or more constituents still "
-                f"not truncated to same start dates.\n"
-                f"{self.tsdf.head()}"
+                f"One or more constituents still "
+                f"not truncated to same start dates.\n"
+                f"{self.tsdf.head()}"
             )
             logger.warning(msg)
         if len(set(self.last_indices)) != 1:
             msg = (
-                f"One or more constituents still "
-                f"not truncated to same end dates.\n"
-                f"{self.tsdf.tail()}"
+                f"One or more constituents still "
+                f"not truncated to same end dates.\n"
+                f"{self.tsdf.tail()}"
             )
             logger.warning(msg)
         return self
@@ -864,18 +864,18 @@

Source code for openseries.frame

         *,
         base_zero: bool = True,
     ) -> None:
-        """Calculate cumulative relative return between two series.
+        """Calculate cumulative relative return between two series.
 
         Args:
             long_column: Column number of timeseries bought. Defaults to 0.
             short_column: Column number of timeseries sold. Defaults to 1.
             base_zero: If set to False 1.0 is added to allow for a capital base and
                 to allow a volatility calculation. Defaults to True.
-        """
+        """
         rel_label = (
-            cast("tuple[str, str]", self.tsdf.iloc[:, long_column].name)[0]
-            + "_over_"
-            + cast("tuple[str, str]", self.tsdf.iloc[:, short_column].name)[0]
+            cast("tuple[str, str]", self.tsdf.iloc[:, long_column].name)[0]
+            + "_over_"
+            + cast("tuple[str, str]", self.tsdf.iloc[:, short_column].name)[0]
         )
         if base_zero:
             self.tsdf[rel_label, ValueType.RELRTRN] = (
@@ -900,7 +900,7 @@ 

Source code for openseries.frame

         to_date: dt.date | None = None,
         periods_in_a_year_fixed: DaysInYearType | None = None,
     ) -> Series[float]:
-        """Tracking Error.
+        """Tracking Error.
 
         Calculates Tracking Error which is the standard deviation of the
         difference between the fund and its index returns.
@@ -919,7 +919,7 @@ 

Source code for openseries.frame

 
         Returns:
             Tracking Errors.
-        """
+        """
         earlier, later = self.calc_range(
             months_offset=months_from_last,
             from_dt=from_date,
@@ -948,7 +948,7 @@ 

Source code for openseries.frame

                 terrors.append(0.0)
             else:
                 longdf = self.tsdf.loc[
-                    cast("Timestamp", earlier) : cast("Timestamp", later)
+                    cast("Timestamp", earlier) : cast("Timestamp", later)
                 ][item]
                 relative = longdf.ffill().pct_change() - shortdf_returns
                 vol = float(relative.std() * sqrt(time_factor))
@@ -957,8 +957,8 @@ 

Source code for openseries.frame

         return Series(
             data=terrors,
             index=self.tsdf.columns,
-            name=f"Tracking Errors vs {short_label}",
-            dtype="float64",
+            name=f"Tracking Errors vs {short_label}",
+            dtype="float64",
         )
@@ -972,7 +972,7 @@

Source code for openseries.frame

         to_date: dt.date | None = None,
         periods_in_a_year_fixed: DaysInYearType | None = None,
     ) -> Series[float]:
-        """Information Ratio.
+        """Information Ratio.
 
         The Information Ratio equals ( fund return less index return ) divided
         by the Tracking Error. And the Tracking Error is the standard deviation of
@@ -991,7 +991,7 @@ 

Source code for openseries.frame

 
         Returns:
             Information Ratios.
-        """
+        """
         earlier, later = self.calc_range(
             months_offset=months_from_last,
             from_dt=from_date,
@@ -1020,7 +1020,7 @@ 

Source code for openseries.frame

                 ratios.append(0.0)
             else:
                 longdf = self.tsdf.loc[
-                    cast("Timestamp", earlier) : cast("Timestamp", later)
+                    cast("Timestamp", earlier) : cast("Timestamp", later)
                 ][item]
                 relative = longdf.ffill().pct_change() - shortdf_returns
                 ret = float(relative.mean() * time_factor)
@@ -1030,8 +1030,8 @@ 

Source code for openseries.frame

         return Series(
             data=ratios,
             index=self.tsdf.columns,
-            name=f"Info Ratios vs {short_label}",
-            dtype="float64",
+            name=f"Info Ratios vs {short_label}",
+            dtype="float64",
         )
@@ -1041,7 +1041,7 @@

Source code for openseries.frame

         mask: NDArray[bool_],
         time_factor: float,
     ) -> float:
-        """Calculate CAGR from returns array with mask.
+        """Calculate CAGR from returns array with mask.
 
         Args:
             returns_array: Returns array.
@@ -1050,7 +1050,7 @@ 

Source code for openseries.frame

 
         Returns:
             CAGR value.
-        """
+        """
         masked_array = returns_array[mask] + 1.0
         if len(masked_array) == 0:
             return 0.0
@@ -1066,7 +1066,7 @@ 

Source code for openseries.frame

         down_mask: NDArray[bool_],
         time_factor: float,
     ) -> float:
-        """Calculate capture ratio for a single item.
+        """Calculate capture ratio for a single item.
 
         Args:
             ratio: Ratio type to calculate.
@@ -1081,8 +1081,8 @@ 

Source code for openseries.frame

 
         Raises:
             RatioInputError: If ratio is invalid.
-        """
-        if ratio == "up":
+        """
+        if ratio == "up":
             up_rtrn = self._calculate_cagr_from_returns(
                 longdf_returns_np, up_mask, time_factor
             )
@@ -1093,7 +1093,7 @@ 

Source code for openseries.frame

                 return 0.0
             return up_rtrn / up_idx_return
 
-        if ratio == "down":
+        if ratio == "down":
             down_return = self._calculate_cagr_from_returns(
                 longdf_returns_np, down_mask, time_factor
             )
@@ -1104,7 +1104,7 @@ 

Source code for openseries.frame

                 return 0.0
             return down_return / down_idx_return
 
-        if ratio == "both":
+        if ratio == "both":
             up_rtrn = self._calculate_cagr_from_returns(
                 longdf_returns_np, up_mask, time_factor
             )
@@ -1121,7 +1121,7 @@ 

Source code for openseries.frame

                 return 0.0
             return (up_rtrn / up_idx_return) / (down_return / down_idx_return)
 
-        msg = "ratio must be one of 'up', 'down' or 'both'."
+        msg = "ratio must be one of 'up', 'down' or 'both'."
         raise RatioInputError(msg)
 
 
@@ -1135,14 +1135,14 @@

Source code for openseries.frame

         to_date: dt.date | None = None,
         periods_in_a_year_fixed: DaysInYearType | None = None,
     ) -> Series[float]:
-        """Capture Ratio.
+        """Capture Ratio.
 
         The Up (Down) Capture Ratio is calculated by dividing the CAGR
         of the asset during periods that the benchmark returns are positive (negative)
         by the CAGR of the benchmark during the same periods.
         CaptureRatio.BOTH is the Up ratio divided by the Down ratio.
-        Source: 'Capture Ratios: A Popular Method of Measuring Portfolio Performance
-        in Practice', Don R. Cox and Delbert C. Goff, Journal of Economics and
+        Source: 'Capture Ratios: A Popular Method of Measuring Portfolio Performance
+        in Practice', Don R. Cox and Delbert C. Goff, Journal of Economics and
         Finance Education (Vol 2 Winter 2013).
 
         Reference: https://www.economics-finance.org/jefe/volume12-2/11ArticleCox.pdf.
@@ -1160,7 +1160,7 @@ 

Source code for openseries.frame

 
         Returns:
             Capture Ratios.
-        """
+        """
         loss_limit: float = 0.0
         earlier, later = self.calc_range(
             months_offset=months_from_last,
@@ -1182,7 +1182,7 @@ 

Source code for openseries.frame

             time_factor = shortdf.count() / fraction
 
         shortdf_returns = shortdf.ffill().pct_change()
-        shortdf_returns_np = cast("NDArray[float64]", shortdf_returns.to_numpy())
+        shortdf_returns_np = cast("NDArray[float64]", shortdf_returns.to_numpy())
         up_mask = shortdf_returns_np > loss_limit
         down_mask = shortdf_returns_np < loss_limit
 
@@ -1192,10 +1192,10 @@ 

Source code for openseries.frame

                 ratios.append(0.0)
             else:
                 longdf = self.tsdf.loc[
-                    cast("Timestamp", earlier) : cast("Timestamp", later)
+                    cast("Timestamp", earlier) : cast("Timestamp", later)
                 ][item]
                 longdf_returns = longdf.ffill().pct_change()
-                longdf_returns_np = cast("NDArray[float64]", longdf_returns.to_numpy())
+                longdf_returns_np = cast("NDArray[float64]", longdf_returns.to_numpy())
                 ratio_value = self._calculate_capture_ratio_for_item(
                     ratio=ratio,
                     longdf_returns_np=longdf_returns_np,
@@ -1207,9 +1207,9 @@ 

Source code for openseries.frame

                 ratios.append(ratio_value)
 
         ratio_names = {
-            "up": f"Up Capture Ratios vs {short_label}",
-            "down": f"Down Capture Ratios vs {short_label}",
-            "both": f"Up-Down Capture Ratios vs {short_label}",
+            "up": f"Up Capture Ratios vs {short_label}",
+            "down": f"Down Capture Ratios vs {short_label}",
+            "both": f"Up-Down Capture Ratios vs {short_label}",
         }
         resultname = ratio_names[ratio]
 
@@ -1217,7 +1217,7 @@ 

Source code for openseries.frame

             data=ratios,
             index=self.tsdf.columns,
             name=resultname,
-            dtype="float64",
+            dtype="float64",
         )
@@ -1225,9 +1225,9 @@

Source code for openseries.frame

         self: Self,
         column: tuple[str, ValueType] | int,
         vtypes: list[bool],
-        param_name: str = "column",
+        param_name: str = "column",
     ) -> Series[float]:
-        """Extract column value based on value types.
+        """Extract column value based on value types.
 
         Args:
             column: Column reference.
@@ -1239,8 +1239,8 @@ 

Source code for openseries.frame

 
         Raises:
             TypeError: If column type is invalid.
-        """
-        msg = f"{param_name} should be a tuple[str, ValueType] or an integer."
+        """
+        msg = f"{param_name} should be a tuple[str, ValueType] or an integer."
         if isinstance(column, tuple):
             if all(vtypes):
                 return self.tsdf[column]
@@ -1259,7 +1259,7 @@ 

Source code for openseries.frame

         market: tuple[str, ValueType] | int,
         dlta_degr_freedms: int = 1,
     ) -> float:
-        """Market Beta.
+        """Market Beta.
 
         Calculates Beta as Co-variance of asset & market divided by Variance
         of the market.
@@ -1274,14 +1274,14 @@ 

Source code for openseries.frame

 
         Returns:
             Beta as Co-variance of x & y divided by Variance of x.
-        """
+        """
         vtypes = self._value_types
         if not (all(vtypes) or not any(vtypes)):
-            msg = "Mix of series types will give inconsistent results"
+            msg = "Mix of series types will give inconsistent results"
             raise MixedValuetypesError(msg)
 
-        y_value = self._extract_column_value(asset, vtypes, param_name="asset")
-        x_value = self._extract_column_value(market, vtypes, param_name="market")
+        y_value = self._extract_column_value(asset, vtypes, param_name="asset")
+        x_value = self._extract_column_value(market, vtypes, param_name="market")
 
         covariance = cov(m=y_value, y=x_value, ddof=dlta_degr_freedms)
         beta = covariance[0, 1] / covariance[1, 1]
@@ -1298,7 +1298,7 @@ 

Source code for openseries.frame

         *,
         fitted_series: bool = True,
     ) -> dict[str, float]:
-        """Ordinary Least Squares fit.
+        """Ordinary Least Squares fit.
 
         Performs a linear regression and adds a new column with a fitted line
         using Ordinary Least Squares fit.
@@ -1311,30 +1311,30 @@ 

Source code for openseries.frame

 
         Returns:
             A dictionary with the coefficient, intercept and rsquared outputs.
-        """
-        msg = "y_column should be a tuple[str, ValueType] or an integer."
+        """
+        msg = "y_column should be a tuple[str, ValueType] or an integer."
         if isinstance(y_column, tuple):
             y_value = self.tsdf[y_column].to_numpy()
             y_label = cast(
-                "tuple[str, str]",
+                "tuple[str, str]",
                 self.tsdf[y_column].name,
             )[0]
         elif isinstance(y_column, int):
             y_value = self.tsdf.iloc[:, y_column].to_numpy()
-            y_label = cast("tuple[str, str]", self.tsdf.iloc[:, y_column].name)[0]
+            y_label = cast("tuple[str, str]", self.tsdf.iloc[:, y_column].name)[0]
         else:
             raise TypeError(msg)
 
-        msg = "x_column should be a tuple[str, ValueType] or an integer."
+        msg = "x_column should be a tuple[str, ValueType] or an integer."
         if isinstance(x_column, tuple):
             x_value = self.tsdf[x_column].to_numpy().reshape(-1, 1)
             x_label = cast(
-                "tuple[str, str]",
+                "tuple[str, str]",
                 self.tsdf[x_column].name,
             )[0]
         elif isinstance(x_column, int):
             x_value = self.tsdf.iloc[:, x_column].to_numpy().reshape(-1, 1)
-            x_label = cast("tuple[str, str]", self.tsdf.iloc[:, x_column].name)[0]
+            x_label = cast("tuple[str, str]", self.tsdf.iloc[:, x_column].name)[0]
         else:
             raise TypeError(msg)
 
@@ -1343,9 +1343,9 @@ 

Source code for openseries.frame

         if fitted_series:
             self.tsdf[y_label, x_label] = model.predict(x_value)
         return {
-            "coefficient": float(model.coef_[0]),
-            "intercept": float(model.intercept_),
-            "rsquared": model.score(x_value, y_value),
+            "coefficient": float(model.coef_[0]),
+            "intercept": float(model.intercept_),
+            "rsquared": model.score(x_value, y_value),
         }
@@ -1358,34 +1358,34 @@

Source code for openseries.frame

         riskfree_rate: float = 0.0,
         dlta_degr_freedms: int = 1,
     ) -> float:
-        """Jensen's alpha.
+        """Jensen's alpha.
 
-        The Jensen's measure, or Jensen's alpha, is a risk-adjusted performance
+        The Jensen's measure, or Jensen's alpha, is a risk-adjusted performance
         measure that represents the average return on a portfolio or investment,
         above or below that predicted by the capital asset pricing model (CAPM),
-        given the portfolio's or investment's beta and the average market return.
+        given the portfolio's or investment's beta and the average market return.
         This metric is also commonly referred to as simply alpha.
 
         Reference: https://www.investopedia.com/terms/j/jensensmeasure.asp.
 
         Args:
             asset: The column of the asset.
-            market: The column of the market against which Jensen's alpha is measured.
+            market: The column of the market against which Jensen's alpha is measured.
             riskfree_rate: The return of the zero volatility riskfree asset.
                 Defaults to 0.0.
             dlta_degr_freedms: Variance bias factor taking the value 0 or 1.
                 Defaults to 1.
 
         Returns:
-            Jensen's alpha.
-        """
+            Jensen's alpha.
+        """
         vtypes = self._value_types
         if not (all(vtypes) or not any(vtypes)):
-            msg = "Mix of series types will give inconsistent results"
+            msg = "Mix of series types will give inconsistent results"
             raise MixedValuetypesError(msg)
 
-        asset_rtn = self._extract_column_value(asset, vtypes, param_name="asset")
-        market_rtn = self._extract_column_value(market, vtypes, param_name="market")
+        asset_rtn = self._extract_column_value(asset, vtypes, param_name="asset")
+        market_rtn = self._extract_column_value(market, vtypes, param_name="market")
 
         asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
         market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
@@ -1399,14 +1399,14 @@ 

Source code for openseries.frame

 
 
     def _prepare_returns_for_portfolio(self: Self) -> DataFrame:
-        """Prepare returns DataFrame for portfolio calculation.
+        """Prepare returns DataFrame for portfolio calculation.
 
         Returns:
             Returns DataFrame.
 
         Raises:
             MixedValuetypesError: If series types are mixed.
-        """
+        """
         vtypes = self._value_types
         if not any(vtypes):
             returns = self.tsdf.ffill().pct_change()
@@ -1414,32 +1414,32 @@ 

Source code for openseries.frame

             return returns
         if all(vtypes):
             return self.tsdf
-        msg = "Mix of series types will give inconsistent results"
+        msg = "Mix of series types will give inconsistent results"
         raise MixedValuetypesError(msg)
 
     def _calculate_eq_weights(self: Self) -> list[float]:
-        """Calculate equal weights.
+        """Calculate equal weights.
 
         Returns:
             List of equal weights.
-        """
+        """
         return [1.0 / self.item_count] * self.item_count
 
     def _calculate_inv_vol_weights(self: Self, returns: DataFrame) -> list[float]:
-        """Calculate inverse volatility weights.
+        """Calculate inverse volatility weights.
 
         Args:
             returns: Returns DataFrame.
 
         Returns:
             List of inverse volatility weights.
-        """
+        """
         vol = divide(1.0, std(returns, axis=0, ddof=1))
         vol[isinf(vol)] = nan
         return list(divide(vol, vol.sum()))
 
     def _calculate_max_div_weights(self: Self, returns: DataFrame) -> list[float]:
-        """Calculate maximum diversification weights.
+        """Calculate maximum diversification weights.
 
         Args:
             returns: Returns DataFrame.
@@ -1450,12 +1450,12 @@ 

Source code for openseries.frame

         Raises:
             MaxDiversificationNaNError: If correlation matrix has NaN values.
             MaxDiversificationNegativeWeightsError: If weights are negative.
-        """
+        """
         corr_matrix = corrcoef(returns.T)
         corr_matrix[isinf(corr_matrix)] = nan
         corr_matrix[isnan(corr_matrix)] = nan
 
-        msga = "max_div weight strategy failed: correlation matrix contains NaN values"
+        msga = "max_div weight strategy failed: correlation matrix contains NaN values"
         if isnan(corr_matrix).any():
             raise MaxDiversificationNaNError(msga)
 
@@ -1463,29 +1463,29 @@ 

Source code for openseries.frame

             inv_corr_sum = linalg.inv(corr_matrix).sum(axis=1)
 
             msgb = (
-                "max_div weight strategy failed: "
-                "inverse correlation matrix sum contains NaN values"
+                "max_div weight strategy failed: "
+                "inverse correlation matrix sum contains NaN values"
             )
             if isnan(inv_corr_sum).any():
                 raise MaxDiversificationNaNError(msgb)
 
             weights = list(divide(inv_corr_sum, inv_corr_sum.sum()))
 
-            msgc = "max_div weight strategy failed: final weights contain NaN values"
+            msgc = "max_div weight strategy failed: final weights contain NaN values"
             if any(isnan(weight) for weight in weights):  # pragma: no cover
                 raise MaxDiversificationNaNError(msgc)
 
             msgd = (
-                "max_div weight strategy failed: negative weights detected"
-                f" - weights: {[round(w, 6) for w in weights]}"
+                "max_div weight strategy failed: negative weights detected"
+                f" - weights: {[round(w, 6) for w in weights]}"
             )
             if any(weight < 0 for weight in weights):
                 raise MaxDiversificationNegativeWeightsError(msgd)
 
         except linalg.LinAlgError as e:
             msge = (
-                "max_div weight strategy failed: "
-                f"correlation matrix is singular - {e!s}"
+                "max_div weight strategy failed: "
+                f"correlation matrix is singular - {e!s}"
             )
             raise MaxDiversificationNaNError(msge) from e
         else:
@@ -1495,14 +1495,14 @@ 

Source code for openseries.frame

         self: Self,
         returns: DataFrame,
     ) -> list[float]:
-        """Calculate minimum volatility overweight weights.
+        """Calculate minimum volatility overweight weights.
 
         Args:
             returns: Returns DataFrame.
 
         Returns:
             List of minimum volatility overweight weights.
-        """
+        """
         vols = std(returns, axis=0, ddof=1)
         min_vol_idx = vols.argmin()
         min_vol_weight = 0.6
@@ -1516,7 +1516,7 @@ 

Source code for openseries.frame

         weight_strat: LiteralPortfolioWeightings,
         returns: DataFrame,
     ) -> list[float]:
-        """Calculate weights based on strategy.
+        """Calculate weights based on strategy.
 
         Args:
             weight_strat: Weight calculation strategy.
@@ -1527,17 +1527,17 @@ 

Source code for openseries.frame

 
         Raises:
             NotImplementedError: If strategy is not implemented.
-        """
-        if weight_strat == "eq_weights":
+        """
+        if weight_strat == "eq_weights":
             return self._calculate_eq_weights()
-        if weight_strat == "inv_vol":
+        if weight_strat == "inv_vol":
             return self._calculate_inv_vol_weights(returns)
-        if weight_strat == "max_div":
+        if weight_strat == "max_div":
             return self._calculate_max_div_weights(returns)
-        if weight_strat == "min_vol_overweight":
+        if weight_strat == "min_vol_overweight":
             return self._calculate_min_vol_overweight_weights(returns)
 
-        msg = "Weight strategy not implemented"
+        msg = "Weight strategy not implemented"
         raise NotImplementedError(msg)
 
 
@@ -1547,7 +1547,7 @@

Source code for openseries.frame

         name: str,
         weight_strat: LiteralPortfolioWeightings | None = None,
     ) -> DataFrame:
-        """Calculate a basket timeseries based on the supplied weights.
+        """Calculate a basket timeseries based on the supplied weights.
 
         Args:
             name: Name of the basket timeseries.
@@ -1555,11 +1555,11 @@ 

Source code for openseries.frame

 
         Returns:
             A basket timeseries.
-        """
+        """
         if self.weights is None and weight_strat is None:
             msg = (
-                "OpenFrame weights property must be provided "
-                "to run the make_portfolio method."
+                "OpenFrame weights property must be provided "
+                "to run the make_portfolio method."
             )
             raise NoWeightsError(msg)
 
@@ -1575,7 +1575,7 @@ 

Source code for openseries.frame

             data=(returns @ array(self.weights)).add(1.0).cumprod(),
             index=self.tsdf.index,
             columns=[[name], [ValueType.PRICE]],
-            dtype="float64",
+            dtype="float64",
         )
@@ -1588,7 +1588,7 @@

Source code for openseries.frame

         observations: int = 21,
         periods_in_a_year_fixed: DaysInYearType | None = None,
     ) -> DataFrame:
-        """Calculate rolling Information Ratio.
+        """Calculate rolling Information Ratio.
 
         The Information Ratio equals ( fund return less index return ) divided by
         the Tracking Error. And the Tracking Error is the standard deviation of the
@@ -1606,16 +1606,16 @@ 

Source code for openseries.frame

 
         Returns:
             Rolling Information Ratios.
-        """
+        """
         long_label = cast(
-            "tuple[str, str]",
+            "tuple[str, str]",
             self.tsdf.iloc[:, long_column].name,
         )[0]
         short_label = cast(
-            "tuple[str, str]",
+            "tuple[str, str]",
             self.tsdf.iloc[:, short_column].name,
         )[0]
-        ratio_label = f"{long_label} / {short_label}"
+        ratio_label = f"{long_label} / {short_label}"
         if periods_in_a_year_fixed:
             time_factor = float(periods_in_a_year_fixed)
         else:
@@ -1640,7 +1640,7 @@ 

Source code for openseries.frame

         voldf = voldf.dropna().to_frame()
 
         ratiodf = (retdf.iloc[:, 0] / voldf.iloc[:, 0]).to_frame()
-        ratiodf.columns = [[ratio_label], ["Information Ratio"]]
+        ratiodf.columns = [[ratio_label], ["Information Ratio"]]
 
         return DataFrame(ratiodf)
@@ -1654,7 +1654,7 @@

Source code for openseries.frame

         observations: int = 21,
         dlta_degr_freedms: int = 1,
     ) -> DataFrame:
-        """Calculate rolling Market Beta.
+        """Calculate rolling Market Beta.
 
         Calculates Beta as Co-variance of asset & market divided by Variance
         of the market.
@@ -1671,12 +1671,12 @@ 

Source code for openseries.frame

 
         Returns:
             Rolling Betas.
-        """
-        market_label = cast("tuple[str, str]", self.tsdf.iloc[:, market_column].name)[
+        """
+        market_label = cast("tuple[str, str]", self.tsdf.iloc[:, market_column].name)[
             0
         ]
-        asset_label = cast("tuple[str, str]", self.tsdf.iloc[:, asset_column].name)[0]
-        beta_label = f"{asset_label} / {market_label}"
+        asset_label = cast("tuple[str, str]", self.tsdf.iloc[:, asset_column].name)[0]
+        beta_label = f"{asset_label} / {market_label}"
 
         rolling = (
             self.tsdf.ffill()
@@ -1702,7 +1702,7 @@ 

Source code for openseries.frame

         )
         rollbeta = rollbetaseries.to_frame()
         rollbeta.index = rollbeta.index.get_level_values(0)
-        rollbeta.columns = MultiIndex.from_arrays([[beta_label], ["Beta"]])
+        rollbeta.columns = MultiIndex.from_arrays([[beta_label], ["Beta"]])
 
         return rollbeta
@@ -1715,7 +1715,7 @@

Source code for openseries.frame

         second_column: int = 1,
         observations: int = 21,
     ) -> DataFrame:
-        """Calculate rolling Correlation.
+        """Calculate rolling Correlation.
 
         Calculates correlation between two series. The period with
         at least the given number of observations is the first period calculated.
@@ -1730,11 +1730,11 @@ 

Source code for openseries.frame

 
         Returns:
             Rolling Correlations.
-        """
+        """
         corr_label = (
-            cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0]
-            + "_VS_"
-            + cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0]
+            cast("tuple[str, str]", self.tsdf.iloc[:, first_column].name)[0]
+            + "_VS_"
+            + cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0]
         )
         first_series = (
             self.tsdf.iloc[:, first_column]
@@ -1747,7 +1747,7 @@ 

Source code for openseries.frame

         corrdf.columns = MultiIndex.from_arrays(
             [
                 [corr_label],
-                ["Rolling correlation"],
+                ["Rolling correlation"],
             ],
         )
 
@@ -1760,7 +1760,7 @@ 

Source code for openseries.frame

         self: Self,
         dependent_column: tuple[str, ValueType],
     ) -> tuple[DataFrame, OpenTimeSeries]:
-        """Perform a multi-factor linear regression.
+        """Perform a multi-factor linear regression.
 
         This function treats one specified column in the DataFrame as the dependent
         variable (y) and uses all remaining columns as independent variables (X).
@@ -1780,28 +1780,28 @@ 

Source code for openseries.frame

         Raises:
             KeyError: If the column tuple is not found in the OpenFrame.tsdf.columns.
             ValueError: If not all series are returnseries (ValueType.RTRN).
-        """
+        """
         key_msg = (
-            f"Tuple ({dependent_column[0]}, "
-            f"{dependent_column[1].value}) not found in data."
+            f"Tuple ({dependent_column[0]}, "
+            f"{dependent_column[1].value}) not found in data."
         )
         if dependent_column not in self.tsdf.columns:
             raise KeyError(key_msg)
 
-        vtype_msg = "All series should be of ValueType.RTRN."
+        vtype_msg = "All series should be of ValueType.RTRN."
         if not all(x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)):
             raise MixedValuetypesError(vtype_msg)
 
         dependent = self.tsdf[dependent_column]
         factors = self.tsdf.drop(columns=[dependent_column])
-        indx = ["R-square", "Intercept", *factors.columns.get_level_values(0)]
+        indx = ["R-square", "Intercept", *factors.columns.get_level_values(0)]
 
         model = LinearRegression()
         model.fit(factors, dependent)
 
         predictions = OpenTimeSeries.from_arrays(
-            name=f"Predicted {dependent_column[0]}",
-            dates=[date.strftime("%Y-%m-%d") for date in self.tsdf.index],
+            name=f"Predicted {dependent_column[0]}",
+            dates=[date.strftime("%Y-%m-%d") for date in self.tsdf.index],
             values=list(model.predict(factors)),
             valuetype=ValueType.RTRN,
         )
@@ -1820,7 +1820,7 @@ 

Source code for openseries.frame

         *,
         equal_weights: bool,
     ) -> tuple[list[str], list[float]]:
-        """Validate and prepare inputs for rebalanced portfolio.
+        """Validate and prepare inputs for rebalanced portfolio.
 
         Args:
             items: List of items to include. If None, uses all items.
@@ -1834,30 +1834,30 @@ 

Source code for openseries.frame

             WeightsNotProvidedError: If weights are required but not provided.
             TypeError: If items is not a list.
             PortfolioItemsNotWithinFrameError: If items are invalid.
-        """
+        """
         if bal_weights is None and not equal_weights:
             if self.weights is None:
-                msg = "Weights must be provided."
+                msg = "Weights must be provided."
                 raise WeightsNotProvidedError(msg)
             bal_weights = list(self.weights)
 
         if items is None:
             items = list(self.columns_lvl_zero)
         else:
-            msg = "Items must be passed as list."
+            msg = "Items must be passed as list."
             if not isinstance(items, list):
                 raise TypeError(msg)
             if not items:
-                msg = "Items for portfolio must be within SeriesFrame items."
+                msg = "Items for portfolio must be within SeriesFrame items."
                 raise PortfolioItemsNotWithinFrameError(msg)
             if not set(items) <= set(self.columns_lvl_zero):
-                msg = "Items for portfolio must be within SeriesFrame items."
+                msg = "Items for portfolio must be within SeriesFrame items."
                 raise PortfolioItemsNotWithinFrameError(msg)
 
         if equal_weights:
             bal_weights = [1 / len(items)] * len(items)
 
-        return items, cast("list[float]", bal_weights)
+        return items, cast("list[float]", bal_weights)
 
     def _initialize_rebalance_output(
         self: Self,
@@ -1865,7 +1865,7 @@ 

Source code for openseries.frame

         name: str,
         cash_values: list[float],
     ) -> dict[str, dict[str, list[float]]]:
-        """Initialize output structure for rebalanced portfolio.
+        """Initialize output structure for rebalanced portfolio.
 
         Args:
             items: List of items in portfolio.
@@ -1874,35 +1874,35 @@ 

Source code for openseries.frame

 
         Returns:
             Initialized output dictionary.
-        """
+        """
         output = {
             item: {
                 ValueType.PRICE: [],
-                "buysell_qty": [0.0] * self.length,
-                "position": [0.0] * self.length,
-                "value": [0.0] * self.length,
-                "twr": [0.0] * self.length,
-                "settle": [0.0] * self.length,
+                "buysell_qty": [0.0] * self.length,
+                "position": [0.0] * self.length,
+                "value": [0.0] * self.length,
+                "twr": [0.0] * self.length,
+                "settle": [0.0] * self.length,
             }
             for item in items
         }
         output.update(
             {
-                "cash": {
+                "cash": {
                     ValueType.PRICE: cash_values,
-                    "buysell_qty": [0.0] * self.length,
-                    "position": [0.0] * self.length,
-                    "value": [0.0] * self.length,
-                    "twr": [0.0] * self.length,
-                    "settle": [0.0] * self.length,
+                    "buysell_qty": [0.0] * self.length,
+                    "position": [0.0] * self.length,
+                    "value": [0.0] * self.length,
+                    "twr": [0.0] * self.length,
+                    "settle": [0.0] * self.length,
                 },
                 name: {
                     ValueType.PRICE: [1.0] + [0.0] * (self.length - 1),
-                    "buysell_qty": [-1.0] + [0.0] * (self.length - 1),
-                    "position": [-1.0] + [0.0] * (self.length - 1),
-                    "value": [-1.0] + [0.0] * (self.length - 1),
-                    "twr": [1.0] + [0.0] * (self.length - 1),
-                    "settle": [1.0] + [0.0] * (self.length - 1),
+                    "buysell_qty": [-1.0] + [0.0] * (self.length - 1),
+                    "position": [-1.0] + [0.0] * (self.length - 1),
+                    "value": [-1.0] + [0.0] * (self.length - 1),
+                    "twr": [1.0] + [0.0] * (self.length - 1),
+                    "settle": [1.0] + [0.0] * (self.length - 1),
                 },
             },
         )
@@ -1915,38 +1915,38 @@ 

Source code for openseries.frame

         output: dict[str, dict[str, list[float]]],
         name: str,
     ) -> None:
-        """Initialize positions for the first day.
+        """Initialize positions for the first day.
 
         Args:
             items: List of items in portfolio.
             bal_weights: Weights for each item.
             output: Output dictionary to update.
             name: Name of the portfolio.
-        """
+        """
         for item, weight in zip(items, bal_weights, strict=False):
             output[item][ValueType.PRICE] = cast(
-                "list[float]",
+                "list[float]",
                 self.tsdf[(item, ValueType.PRICE)].to_numpy().tolist(),
             )
-            output[item]["buysell_qty"][0] = (
+            output[item]["buysell_qty"][0] = (
                 weight / self.tsdf[(item, ValueType.PRICE)].iloc[0]
             )
-            output[item]["position"][0] = output[item]["buysell_qty"][0]
-            output[item]["value"][0] = (
-                output[item]["position"][0] * output[item][ValueType.PRICE][0]
+            output[item]["position"][0] = output[item]["buysell_qty"][0]
+            output[item]["value"][0] = (
+                output[item]["position"][0] * output[item][ValueType.PRICE][0]
             )
-            output[item]["settle"][0] = (
-                -output[item]["buysell_qty"][0] * output[item][ValueType.PRICE][0]
+            output[item]["settle"][0] = (
+                -output[item]["buysell_qty"][0] * output[item][ValueType.PRICE][0]
             )
-            output["cash"]["buysell_qty"][0] += output[item]["settle"][0]
-            output[item]["twr"][0] = (
-                output[item]["value"][0] / -output[item]["settle"][0]
+            output["cash"]["buysell_qty"][0] += output[item]["settle"][0]
+            output[item]["twr"][0] = (
+                output[item]["value"][0] / -output[item]["settle"][0]
             )
 
-        output["cash"]["position"][0] = (
-            output["cash"]["buysell_qty"][0] + output[name]["settle"][0]
+        output["cash"]["position"][0] = (
+            output["cash"]["buysell_qty"][0] + output[name]["settle"][0]
         )
-        output["cash"]["settle"][0] = -output["cash"]["position"][0]
+        output["cash"]["settle"][0] = -output["cash"]["position"][0]
 
     def _process_rebalancing_day(
         self: Self,
@@ -1956,7 +1956,7 @@ 

Source code for openseries.frame

         output: dict[str, dict[str, list[float]]],
         name: str,
     ) -> tuple[float, float]:
-        """Process a rebalancing day.
+        """Process a rebalancing day.
 
         Args:
             day: Current day index.
@@ -1967,31 +1967,31 @@ 

Source code for openseries.frame

 
         Returns:
             Tuple of (portfolio_value, settle_value).
-        """
+        """
         portfolio_value = 0.0
         settle_value = 0.0
 
         for item, weight in zip(items, bal_weights, strict=False):
-            output[item]["buysell_qty"][day] = (
+            output[item]["buysell_qty"][day] = (
                 weight
-                - output[item]["value"][day - 1] / -output[name]["value"][day - 1]
+                - output[item]["value"][day - 1] / -output[name]["value"][day - 1]
             ) / output[item][ValueType.PRICE][day]
-            output[item]["position"][day] = (
-                output[item]["position"][day - 1] + output[item]["buysell_qty"][day]
+            output[item]["position"][day] = (
+                output[item]["position"][day - 1] + output[item]["buysell_qty"][day]
             )
-            output[item]["value"][day] = (
-                output[item]["position"][day] * output[item][ValueType.PRICE][day]
+            output[item]["value"][day] = (
+                output[item]["position"][day] * output[item][ValueType.PRICE][day]
             )
-            portfolio_value += output[item]["value"][day]
-            output[item]["twr"][day] = (
-                output[item]["value"][day]
-                / (output[item]["value"][day - 1] - output[item]["settle"][day])
-                * output[item]["twr"][day - 1]
+            portfolio_value += output[item]["value"][day]
+            output[item]["twr"][day] = (
+                output[item]["value"][day]
+                / (output[item]["value"][day - 1] - output[item]["settle"][day])
+                * output[item]["twr"][day - 1]
             )
-            output[item]["settle"][day] = (
-                -output[item]["buysell_qty"][day] * output[item][ValueType.PRICE][day]
+            output[item]["settle"][day] = (
+                -output[item]["buysell_qty"][day] * output[item][ValueType.PRICE][day]
             )
-            settle_value += output[item]["settle"][day]
+            settle_value += output[item]["settle"][day]
 
         return portfolio_value, settle_value
 
@@ -2001,7 +2001,7 @@ 

Source code for openseries.frame

         items: list[str],
         output: dict[str, dict[str, list[float]]],
     ) -> float:
-        """Process a non-rebalancing day.
+        """Process a non-rebalancing day.
 
         Args:
             day: Current day index.
@@ -2010,19 +2010,19 @@ 

Source code for openseries.frame

 
         Returns:
             Portfolio value.
-        """
+        """
         portfolio_value = 0.0
 
         for item in items:
-            output[item]["position"][day] = output[item]["position"][day - 1]
-            output[item]["value"][day] = (
-                output[item]["position"][day] * output[item][ValueType.PRICE][day]
+            output[item]["position"][day] = output[item]["position"][day - 1]
+            output[item]["value"][day] = (
+                output[item]["position"][day] * output[item][ValueType.PRICE][day]
             )
-            portfolio_value += output[item]["value"][day]
-            output[item]["twr"][day] = (
-                output[item]["value"][day]
-                / (output[item]["value"][day - 1] - output[item]["settle"][day])
-                * output[item]["twr"][day - 1]
+            portfolio_value += output[item]["value"][day]
+            output[item]["twr"][day] = (
+                output[item]["value"][day]
+                / (output[item]["value"][day - 1] - output[item]["settle"][day])
+                * output[item]["twr"][day - 1]
             )
 
         return portfolio_value
@@ -2035,7 +2035,7 @@ 

Source code for openseries.frame

         output: dict[str, dict[str, list[float]]],
         name: str,
     ) -> None:
-        """Update cash and portfolio values for a day.
+        """Update cash and portfolio values for a day.
 
         Args:
             day: Current day index.
@@ -2043,22 +2043,22 @@ 

Source code for openseries.frame

             settle_value: Total settle value.
             output: Output dictionary to update.
             name: Name of the portfolio.
-        """
-        output["cash"]["buysell_qty"][day] = settle_value
-        output["cash"]["position"][day] = (
-            output["cash"]["position"][day - 1]
-            * output["cash"][ValueType.PRICE][day]
-            / output["cash"][ValueType.PRICE][day - 1]
-            + output["cash"]["buysell_qty"][day]
+        """
+        output["cash"]["buysell_qty"][day] = settle_value
+        output["cash"]["position"][day] = (
+            output["cash"]["position"][day - 1]
+            * output["cash"][ValueType.PRICE][day]
+            / output["cash"][ValueType.PRICE][day - 1]
+            + output["cash"]["buysell_qty"][day]
         )
-        output["cash"]["value"][day] = output["cash"]["position"][day]
-        total_portfolio_value = portfolio_value + output["cash"]["value"][day]
-        output[name]["position"][day] = output[name]["position"][day - 1]
-        output[name]["value"][day] = -total_portfolio_value
-        output[name]["twr"][day] = (
-            output[name]["value"][day] / output[name]["position"][day]
+        output["cash"]["value"][day] = output["cash"]["position"][day]
+        total_portfolio_value = portfolio_value + output["cash"]["value"][day]
+        output[name]["position"][day] = output[name]["position"][day - 1]
+        output[name]["value"][day] = -total_portfolio_value
+        output[name]["twr"][day] = (
+            output[name]["value"][day] / output[name]["position"][day]
         )
-        output[name][ValueType.PRICE][day] = output[name]["twr"][day]
+        output[name][ValueType.PRICE][day] = output[name]["twr"][day]
 
     def _build_rebalance_result(
         self: Self,
@@ -2066,7 +2066,7 @@ 

Source code for openseries.frame

         instruments: list[str],
         subheaders: list[str | ValueType],
     ) -> DataFrame:
-        """Build result DataFrame from output dictionary.
+        """Build result DataFrame from output dictionary.
 
         Args:
             output: Output dictionary with all calculated values.
@@ -2075,7 +2075,7 @@ 

Source code for openseries.frame

 
         Returns:
             DataFrame with MultiIndex columns.
-        """
+        """
         result = DataFrame()
         for outvalue in output.values():
             result = concat(
@@ -2083,7 +2083,7 @@ 

Source code for openseries.frame

                     result,
                     DataFrame(data=outvalue, index=self.tsdf.index),
                 ],
-                axis="columns",
+                axis="columns",
             )
         lvlone, lvltwo = [], []
         for instr in instruments:
@@ -2105,7 +2105,7 @@ 

Source code for openseries.frame

         equal_weights: bool = False,
         drop_extras: bool = True,
     ) -> OpenFrame:
-        """Create a rebalanced portfolio from the OpenFrame constituents.
+        """Create a rebalanced portfolio from the OpenFrame constituents.
 
         Args:
             name: Name of the portfolio.
@@ -2121,7 +2121,7 @@ 

Source code for openseries.frame

 
         Returns:
             OpenFrame containing the rebalanced portfolio.
-        """
+        """
         items, bal_weights = self._validate_and_prepare_rebalance_inputs(
             items,
             bal_weights,
@@ -2131,7 +2131,7 @@ 

Source code for openseries.frame

         if cash_index:
             cash_index.tsdf = cash_index.tsdf.reindex(self.tsdf.index)
             cash_values: list[float] = cast(
-                "list[float]", cash_index.tsdf.iloc[:, 0].to_numpy().tolist()
+                "list[float]", cash_index.tsdf.iloc[:, 0].to_numpy().tolist()
             )
         else:
             cash_values = [1.0] * self.length
@@ -2141,18 +2141,18 @@ 

Source code for openseries.frame

 
         ccies = list({serie.currency for serie in self.constituents})
         if len(ccies) != 1:
-            msg = "Items for portfolio must be denominated in same currency."
+            msg = "Items for portfolio must be denominated in same currency."
             raise MultipleCurrenciesError(msg)
         currency = ccies[0]
 
-        instruments = [*items, "cash", name]
+        instruments = [*items, "cash", name]
         subheaders = [
             ValueType.PRICE,
-            "buysell_qty",
-            "position",
-            "value",
-            "twr",
-            "settle",
+            "buysell_qty",
+            "position",
+            "value",
+            "twr",
+            "settle",
         ]
 
         output = self._initialize_rebalance_output(
@@ -2209,7 +2209,7 @@ 

Source code for openseries.frame

             series.extend(
                 [
                     OpenTimeSeries.from_df(
-                        dframe=result[(item.label, "twr")],
+                        dframe=result[(item.label, "twr")],
                         valuetype=ValueType.PRICE,
                         baseccy=item.currency,
                         local_ccy=item.local_ccy,
@@ -2219,7 +2219,7 @@ 

Source code for openseries.frame

             )
             series.append(
                 OpenTimeSeries.from_df(
-                    dframe=result[(name, "twr")],
+                    dframe=result[(name, "twr")],
                     valuetype=ValueType.PRICE,
                     baseccy=currency,
                     local_ccy=True,
@@ -2233,7 +2233,7 @@ 

Source code for openseries.frame

                         valuetype=ValueType.PRICE,
                         baseccy=currency,
                         local_ccy=True,
-                    ).set_new_label(f"{col[0]}, {col[1]!s}")
+                    ).set_new_label(f"{col[0]}, {col[1]!s}")
                     for col in result.columns
                 ]
             )
diff --git a/docs/build/html/_modules/openseries/load_plotly.html b/docs/build/html/_modules/openseries/load_plotly.html
index 0b948bf6..ebab9690 100644
--- a/docs/build/html/_modules/openseries/load_plotly.html
+++ b/docs/build/html/_modules/openseries/load_plotly.html
@@ -108,7 +108,7 @@
            

Source code for openseries.load_plotly

-"""Function to load plotly layout and configuration from local json file."""
+"""Function to load plotly layout and configuration from local json file."""
 
 from __future__ import annotations
 
@@ -125,18 +125,18 @@ 

Source code for openseries.load_plotly

 
 logger = getLogger(__name__)
 
-__all__ = ["load_plotly_dict"]
+__all__ = ["load_plotly_dict"]
 
 
 def _check_remote_file_existence(url: str) -> bool:
-    """Check if remote file exists.
+    """Check if remote file exists.
 
     Args:
         url: Path to remote file.
 
     Returns:
         True if url is valid and False otherwise.
-    """
+    """
     ok_code = 200
 
     try:
@@ -154,7 +154,7 @@ 

Source code for openseries.load_plotly

     *,
     responsive: bool = True,
 ) -> tuple[PlotlyLayoutType, CaptorLogoType]:
-    """Load Plotly defaults.
+    """Load Plotly defaults.
 
     Args:
         responsive: Flag whether to load as responsive. Defaults to True.
@@ -164,22 +164,22 @@ 

Source code for openseries.load_plotly

         where config_and_layout is the Plotly config and layout template dict,
         and logo is the Captor logo dict (may be empty if the remote logo is
         unavailable).
-    """
+    """
     package_dir = Path(__file__).parent
-    layoutfile = package_dir / "plotly_layouts.json"
-    logofile = package_dir / "plotly_captor_logo.json"
+    layoutfile = package_dir / "plotly_layouts.json"
+    logofile = package_dir / "plotly_captor_logo.json"
 
-    with layoutfile.open(mode="r", encoding="utf-8") as layout_file:
+    with layoutfile.open(mode="r", encoding="utf-8") as layout_file:
         fig = load(layout_file)
-    with logofile.open(mode="r", encoding="utf-8") as logo_file:
+    with logofile.open(mode="r", encoding="utf-8") as logo_file:
         logo = load(logo_file)
 
-    if not _check_remote_file_existence(url=logo["source"]):
-        msg = f"Failed to add logo image from URL {logo['source']}"
+    if not _check_remote_file_existence(url=logo["source"]):
+        msg = f"Failed to add logo image from URL {logo['source']}"
         logger.warning(msg)
         logo = {}
 
-    fig["config"].update({"responsive": responsive})
+    fig["config"].update({"responsive": responsive})
 
     return fig, logo
diff --git a/docs/build/html/_modules/openseries/owntypes.html b/docs/build/html/_modules/openseries/owntypes.html index b2b0cc33..592963dc 100644 --- a/docs/build/html/_modules/openseries/owntypes.html +++ b/docs/build/html/_modules/openseries/owntypes.html @@ -108,7 +108,7 @@

Source code for openseries.owntypes

-"""Declaring types used throughout the project."""
+"""Declaring types used throughout the project."""
 
 from __future__ import annotations
 
@@ -137,17 +137,17 @@ 

Source code for openseries.owntypes

 else:
     SeriesFloat = Series
 
-__all__ = ["ValueType"]
+__all__ = ["ValueType"]
 
 
-SeriesOrFloat_co = TypeVar("SeriesOrFloat_co", float, SeriesFloat, covariant=True)
+SeriesOrFloat_co = TypeVar("SeriesOrFloat_co", float, SeriesFloat, covariant=True)
 
 
 CountryStringType = Annotated[
     str,
     StringConstraints(
         strip_whitespace=True,
-        pattern=r"^[A-Z]{2}$",
+        pattern=r"^[A-Z]{2}$",
         to_upper=True,
         min_length=2,
         max_length=2,
@@ -161,7 +161,7 @@ 

Source code for openseries.owntypes

 
[docs] class Countries(BaseModel): - """Declare Countries.""" + """Declare Countries.""" countryinput: CountriesType
@@ -170,7 +170,7 @@

Source code for openseries.owntypes

 CurrencyStringType = Annotated[
     str,
     StringConstraints(
-        pattern=r"^[A-Z]{3}$",
+        pattern=r"^[A-Z]{3}$",
         to_upper=True,
         min_length=3,
         max_length=3,
@@ -183,7 +183,7 @@ 

Source code for openseries.owntypes

 
[docs] class Currency(BaseModel): - """Declare Currency.""" + """Declare Currency.""" ccy: CurrencyStringType
@@ -192,7 +192,7 @@

Source code for openseries.owntypes

 DateStringType = Annotated[
     str,
     StringConstraints(
-        pattern=r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$",
+        pattern=r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$",
         strip_whitespace=True,
         strict=True,
         min_length=10,
@@ -220,153 +220,153 @@ 

Source code for openseries.owntypes

 
 CaptorLogoType = dict[str, str | float]
 
-LiteralJsonOutput = Literal["values", "tsdf"]
-LiteralTrunc = Literal["before", "after", "both"]
+LiteralJsonOutput = Literal["values", "tsdf"]
+LiteralTrunc = Literal["before", "after", "both"]
 LiteralLinePlotMode = (
     Literal[
-        "lines",
-        "markers",
-        "lines+markers",
-        "lines+text",
-        "markers+text",
-        "lines+markers+text",
+        "lines",
+        "markers",
+        "lines+markers",
+        "lines+text",
+        "markers+text",
+        "lines+markers+text",
     ]
     | None
 )
-LiteralHowMerge = Literal["outer", "inner"]
-LiteralQuantileInterp = Literal["linear", "lower", "higher", "midpoint", "nearest"]
-LiteralBizDayFreq = Literal["B", "BME", "BQE", "BYE"]
+LiteralHowMerge = Literal["outer", "inner"]
+LiteralQuantileInterp = Literal["linear", "lower", "higher", "midpoint", "nearest"]
+LiteralBizDayFreq = Literal["B", "BME", "BQE", "BYE"]
 LiteralPandasReindexMethod = (
-    Literal["pad", "ffill", "backfill", "bfill", "nearest"] | None
+    Literal["pad", "ffill", "backfill", "bfill", "nearest"] | None
 )
-LiteralNanMethod = Literal["fill", "drop"]
-LiteralCaptureRatio = Literal["up", "down", "both"]
-LiteralBarPlotMode = Literal["stack", "group", "overlay", "relative"]
-LiteralPlotlyOutput = Literal["file", "div"]
-LiteralPlotlyJSlib = Literal[True, False, "cdn"]
-LiteralPlotlyHistogramPlotType = Literal["bars", "lines"]
-LiteralPlotlyHistogramBarMode = Literal["stack", "group", "overlay", "relative"]
-LiteralPlotlyHistogramCurveType = Literal["normal", "kde"]
+LiteralNanMethod = Literal["fill", "drop"]
+LiteralCaptureRatio = Literal["up", "down", "both"]
+LiteralBarPlotMode = Literal["stack", "group", "overlay", "relative"]
+LiteralPlotlyOutput = Literal["file", "div"]
+LiteralPlotlyJSlib = Literal[True, False, "cdn"]
+LiteralPlotlyHistogramPlotType = Literal["bars", "lines"]
+LiteralPlotlyHistogramBarMode = Literal["stack", "group", "overlay", "relative"]
+LiteralPlotlyHistogramCurveType = Literal["normal", "kde"]
 LiteralPlotlyHistogramHistNorm = Literal[
-    "percent",
-    "probability",
-    "density",
-    "probability density",
+    "percent",
+    "probability",
+    "density",
+    "probability density",
 ]
 LiteralPortfolioWeightings = Literal[
-    "eq_weights", "inv_vol", "max_div", "min_vol_overweight"
+    "eq_weights", "inv_vol", "max_div", "min_vol_overweight"
 ]
 LiteralMinimizeMethods = Literal[
-    "SLSQP",
-    "Nelder-Mead",
-    "Powell",
-    "CG",
-    "BFGS",
-    "Newton-CG",
-    "L-BFGS-B",
-    "TNC",
-    "COBYLA",
-    "trust-constr",
-    "dogleg",
-    "trust-ncg",
-    "trust-exact",
-    "trust-krylov",
+    "SLSQP",
+    "Nelder-Mead",
+    "Powell",
+    "CG",
+    "BFGS",
+    "Newton-CG",
+    "L-BFGS-B",
+    "TNC",
+    "COBYLA",
+    "trust-constr",
+    "dogleg",
+    "trust-ncg",
+    "trust-exact",
+    "trust-krylov",
 ]
 
 LiteralSeriesProps = Literal[
-    "value_ret",
-    "geo_ret",
-    "arithmetic_ret",
-    "vol",
-    "downside_deviation",
-    "ret_vol_ratio",
-    "sortino_ratio",
-    "kappa3_ratio",
-    "z_score",
-    "skew",
-    "kurtosis",
-    "positive_share",
-    "var_down",
-    "cvar_down",
-    "vol_from_var",
-    "worst",
-    "worst_month",
-    "max_drawdown_cal_year",
-    "max_drawdown",
-    "max_drawdown_date",
-    "first_idx",
-    "last_idx",
-    "length",
-    "span_of_days",
-    "yearfrac",
-    "periods_in_a_year",
-    "autocorr",
-    "partial_autocorr",
+    "value_ret",
+    "geo_ret",
+    "arithmetic_ret",
+    "vol",
+    "downside_deviation",
+    "ret_vol_ratio",
+    "sortino_ratio",
+    "kappa3_ratio",
+    "z_score",
+    "skew",
+    "kurtosis",
+    "positive_share",
+    "var_down",
+    "cvar_down",
+    "vol_from_var",
+    "worst",
+    "worst_month",
+    "max_drawdown_cal_year",
+    "max_drawdown",
+    "max_drawdown_date",
+    "first_idx",
+    "last_idx",
+    "length",
+    "span_of_days",
+    "yearfrac",
+    "periods_in_a_year",
+    "autocorr",
+    "partial_autocorr",
 ]
 LiteralFrameProps = Literal[
-    "value_ret",
-    "geo_ret",
-    "arithmetic_ret",
-    "autocorr",
-    "vol",
-    "downside_deviation",
-    "ret_vol_ratio",
-    "sortino_ratio",
-    "kappa3_ratio",
-    "z_score",
-    "skew",
-    "kurtosis",
-    "positive_share",
-    "var_down",
-    "cvar_down",
-    "vol_from_var",
-    "worst",
-    "worst_month",
-    "max_drawdown",
-    "max_drawdown_date",
-    "max_drawdown_cal_year",
-    "first_indices",
-    "last_indices",
-    "lengths_of_items",
-    "span_of_days_all",
+    "value_ret",
+    "geo_ret",
+    "arithmetic_ret",
+    "autocorr",
+    "vol",
+    "downside_deviation",
+    "ret_vol_ratio",
+    "sortino_ratio",
+    "kappa3_ratio",
+    "z_score",
+    "skew",
+    "kurtosis",
+    "positive_share",
+    "var_down",
+    "cvar_down",
+    "vol_from_var",
+    "worst",
+    "worst_month",
+    "max_drawdown",
+    "max_drawdown_date",
+    "max_drawdown_cal_year",
+    "first_indices",
+    "last_indices",
+    "lengths_of_items",
+    "span_of_days_all",
 ]
 
 
 
[docs] class PropertiesList(list[str]): - """Base class for allowed property arguments definition.""" + """Base class for allowed property arguments definition.""" allowed_strings: ClassVar[set[str]] = { - "value_ret", - "geo_ret", - "arithmetic_ret", - "vol", - "downside_deviation", - "ret_vol_ratio", - "sortino_ratio", - "kappa3_ratio", - "omega_ratio", - "z_score", - "skew", - "kurtosis", - "positive_share", - "var_down", - "cvar_down", - "vol_from_var", - "worst", - "worst_month", - "max_drawdown", - "max_drawdown_date", - "max_drawdown_cal_year", + "value_ret", + "geo_ret", + "arithmetic_ret", + "vol", + "downside_deviation", + "ret_vol_ratio", + "sortino_ratio", + "kappa3_ratio", + "omega_ratio", + "z_score", + "skew", + "kurtosis", + "positive_share", + "var_down", + "cvar_down", + "vol_from_var", + "worst", + "worst_month", + "max_drawdown", + "max_drawdown_date", + "max_drawdown_cal_year", } def _validate(self: Self) -> None: - """Validate the string input of the all_properties method.""" + """Validate the string input of the all_properties method.""" seen = set() invalids = set() duplicates = set() - msg = "" + msg = "" for item in self: if item not in self.allowed_strings: invalids.add(item) @@ -375,11 +375,11 @@

Source code for openseries.owntypes

             seen.add(item)
         if len(invalids) != 0:
             msg += (
-                f"Invalid string(s): {list(invalids)}.\nAllowed strings are:"
-                f"\n{pformat(self.allowed_strings)}\n"
+                f"Invalid string(s): {list(invalids)}.\nAllowed strings are:"
+                f"\n{pformat(self.allowed_strings)}\n"
             )
         if len(duplicates) != 0:
-            msg += f"Duplicate string(s): {list(duplicates)}."
+            msg += f"Duplicate string(s): {list(duplicates)}."
         if len(msg) != 0:
             raise PropertiesInputValidationError(msg)
@@ -388,17 +388,17 @@

Source code for openseries.owntypes

 
[docs] class OpenTimeSeriesPropertiesList(PropertiesList): - """Allowed property arguments for the OpenTimeSeries class.""" + """Allowed property arguments for the OpenTimeSeries class.""" allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | { - "first_idx", - "last_idx", - "length", - "span_of_days", - "yearfrac", - "periods_in_a_year", - "autocorr", - "partial_autocorr", + "first_idx", + "last_idx", + "length", + "span_of_days", + "yearfrac", + "periods_in_a_year", + "autocorr", + "partial_autocorr", }
@@ -407,7 +407,7 @@

Source code for openseries.owntypes

         self: Self,
         *args: LiteralSeriesProps,
     ) -> None:
-        """Property arguments for the OpenTimeSeries class."""
+        """Property arguments for the OpenTimeSeries class."""
         super().__init__(args)
         self._validate()
@@ -417,20 +417,20 @@

Source code for openseries.owntypes

 
[docs] class OpenFramePropertiesList(PropertiesList): - """Allowed property arguments for the OpenFrame class.""" + """Allowed property arguments for the OpenFrame class.""" allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | { - "autocorr", - "first_indices", - "last_indices", - "lengths_of_items", - "span_of_days_all", + "autocorr", + "first_indices", + "last_indices", + "lengths_of_items", + "span_of_days_all", }
[docs] def __init__(self: Self, *args: LiteralFrameProps) -> None: - """Property arguments for the OpenFrame class.""" + """Property arguments for the OpenFrame class.""" super().__init__(args) self._validate()
@@ -440,153 +440,153 @@

Source code for openseries.owntypes

 
[docs] class ValueType(StrEnum): - """Enum types of OpenTimeSeries to identify the output.""" + """Enum types of OpenTimeSeries to identify the output.""" - EWMA_VOL = "EWMA volatility" - EWMA_VAR = "EWMA VaR" - PRICE = "Price(Close)" - RTRN = "Return(Total)" - RELRTRN = "Relative return" - ROLLBETA = "Beta" - ROLLCORR = "Rolling correlation" - ROLLCVAR = "Rolling CVaR" - ROLLINFORATIO = "Information Ratio" - ROLLRTRN = "Rolling returns" - ROLLVAR = "Rolling VaR" - ROLLVOL = "Rolling volatility"
+ EWMA_VOL = "EWMA volatility" + EWMA_VAR = "EWMA VaR" + PRICE = "Price(Close)" + RTRN = "Return(Total)" + RELRTRN = "Relative return" + ROLLBETA = "Beta" + ROLLCORR = "Rolling correlation" + ROLLCVAR = "Rolling CVaR" + ROLLINFORATIO = "Information Ratio" + ROLLRTRN = "Rolling returns" + ROLLVAR = "Rolling VaR" + ROLLVOL = "Rolling volatility"
[docs] class MixedValuetypesError(Exception): - """Raised when provided timeseries valuetypes are not the same."""
+ """Raised when provided timeseries valuetypes are not the same."""
[docs] class AtLeastOneFrameError(Exception): - """Raised when none of the possible frame inputs is provided."""
+ """Raised when none of the possible frame inputs is provided."""
[docs] class DateAlignmentError(Exception): - """Raised when date input is not aligned with existing range."""
+ """Raised when date input is not aligned with existing range."""
[docs] class NumberOfItemsAndLabelsNotSameError(Exception): - """Raised when number of labels is not matching the number of timeseries."""
+ """Raised when number of labels is not matching the number of timeseries."""
[docs] class InitialValueZeroError(Exception): - """Raised when a calculation cannot be performed due to initial value(s) zero."""
+ """Raised when a calculation cannot be performed due to initial value(s) zero."""
[docs] class CountriesNotStringNorListStrError(Exception): - """Raised when countries argument is not provided in correct format."""
+ """Raised when countries argument is not provided in correct format."""
[docs] class MarketsNotStringNorListStrError(Exception): - """Raised when markets argument is not provided in correct format."""
+ """Raised when markets argument is not provided in correct format."""
[docs] class TradingDaysNotAboveZeroError(Exception): - """Raised when trading days argument is not above zero."""
+ """Raised when trading days argument is not above zero."""
[docs] class BothStartAndEndError(Exception): - """Raised when both start and end dates are provided."""
+ """Raised when both start and end dates are provided."""
[docs] class NoWeightsError(Exception): - """Raised when no weights are provided to function where necessary."""
+ """Raised when no weights are provided to function where necessary."""
[docs] class LabelsNotUniqueError(Exception): - """Raised when provided label names are not unique."""
+ """Raised when provided label names are not unique."""
[docs] class RatioInputError(Exception): - """Raised when ratio keyword not provided correctly."""
+ """Raised when ratio keyword not provided correctly."""
[docs] class MergingResultedInEmptyError(Exception): - """Raised when a merge resulted in an empty DataFrame."""
+ """Raised when a merge resulted in an empty DataFrame."""
[docs] class IncorrectArgumentComboError(Exception): - """Raised when correct combination of arguments is not provided."""
+ """Raised when correct combination of arguments is not provided."""
[docs] class PropertiesInputValidationError(Exception): - """Raised when duplicate strings are provided."""
+ """Raised when duplicate strings are provided."""
[docs] class ResampleDataLossError(Exception): - """Raised when user attempts to run resample_to_business_period_ends on returns."""
+ """Raised when user attempts to run resample_to_business_period_ends on returns."""
class WeightsNotProvidedError(Exception): - """Raised when weights are not provided.""" + """Raised when weights are not provided.""" class MultipleCurrenciesError(Exception): - """Raised when multiple currencies are provided.""" + """Raised when multiple currencies are provided.""" class PortfolioItemsNotWithinFrameError(Exception): - """Raised when portfolio items are not within frame.""" + """Raised when portfolio items are not within frame.""" class MaxDiversificationNaNError(Exception): - """Raised when max_div weight strategy produces NaN values.""" + """Raised when max_div weight strategy produces NaN values.""" class MaxDiversificationNegativeWeightsError(Exception): - """Raised when max_div weight strategy produces negative weights.""" + """Raised when max_div weight strategy produces negative weights."""
diff --git a/docs/build/html/_modules/openseries/portfoliotools.html b/docs/build/html/_modules/openseries/portfoliotools.html index 35896198..7ef4f58a 100644 --- a/docs/build/html/_modules/openseries/portfoliotools.html +++ b/docs/build/html/_modules/openseries/portfoliotools.html @@ -108,7 +108,7 @@

Source code for openseries.portfoliotools

-"""Defining the portfolio tools for the OpenFrame class."""
+"""Defining the portfolio tools for the OpenFrame class."""
 
 from __future__ import annotations
 
@@ -162,11 +162,11 @@ 

Source code for openseries.portfoliotools

     from .frame import OpenFrame
 
 __all__ = [
-    "constrain_optimized_portfolios",
-    "efficient_frontier",
-    "prepare_plot_data",
-    "sharpeplot",
-    "simulate_portfolios",
+    "constrain_optimized_portfolios",
+    "efficient_frontier",
+    "prepare_plot_data",
+    "sharpeplot",
+    "simulate_portfolios",
 ]
 
 
@@ -177,7 +177,7 @@ 

Source code for openseries.portfoliotools

     num_ports: int,
     seed: int,
 ) -> DataFrame:
-    """Generate random weights for simulated portfolios.
+    """Generate random weights for simulated portfolios.
 
     Args:
         simframe: Return data for portfolio constituents.
@@ -186,7 +186,7 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         The resulting data.
-    """
+    """
     copi = simframe.from_deepcopy()
 
     vtypes = [x == ValueType.RTRN for x in copi.tsdf.columns.get_level_values(1)]
@@ -196,7 +196,7 @@ 

Source code for openseries.portfoliotools

     elif all(vtypes):
         log_ret = copi.tsdf.copy()
     else:
-        msg = "Mix of series types will give inconsistent results"
+        msg = "Mix of series types will give inconsistent results"
         raise MixedValuetypesError(msg)
 
     log_ret.columns = log_ret.columns.get_level_values(0)
@@ -209,15 +209,15 @@ 

Source code for openseries.portfoliotools

     all_weights = all_weights / all_weights.sum(axis=1, keepdims=True)
 
     ret_arr = all_weights @ mean_returns
-    vol_arr = sqrt(einsum("ij,jk,ik->i", all_weights, cov_matrix, all_weights))
+    vol_arr = sqrt(einsum("ij,jk,ik->i", all_weights, cov_matrix, all_weights))
     sharpe_arr = ret_arr / vol_arr
 
     simdf = concat(
         [
-            DataFrame({"stdev": vol_arr, "ret": ret_arr, "sharpe": sharpe_arr}),
+            DataFrame({"stdev": vol_arr, "ret": ret_arr, "sharpe": sharpe_arr}),
             DataFrame(all_weights, columns=simframe.columns_lvl_zero),
         ],
-        axis="columns",
+        axis="columns",
     )
     simdf = simdf.replace([inf, -inf], nan)
     return simdf.dropna()
@@ -225,7 +225,7 @@

Source code for openseries.portfoliotools

 
 
 def _prepare_returns_for_frontier(eframe: OpenFrame) -> tuple[DataFrame, OpenFrame]:
-    """Prepare returns DataFrame for frontier calculation.
+    """Prepare returns DataFrame for frontier calculation.
 
     Args:
         eframe: Portfolio data.
@@ -235,7 +235,7 @@ 

Source code for openseries.portfoliotools

 
     Raises:
         MixedValuetypesError: If series types are mixed.
-    """
+    """
     if eframe.weights is None:
         eframe.weights = [1.0 / eframe.item_count] * eframe.item_count
 
@@ -248,7 +248,7 @@ 

Source code for openseries.portfoliotools

     elif all(vtypes):
         log_ret = copi.tsdf.copy()
     else:
-        msg = "Mix of series types will give inconsistent results"
+        msg = "Mix of series types will give inconsistent results"
         raise MixedValuetypesError(msg)
 
     log_ret.columns = log_ret.columns.get_level_values(0)
@@ -260,7 +260,7 @@ 

Source code for openseries.portfoliotools

     log_ret: DataFrame,
     periods_in_a_year: float,
 ) -> tuple[float, float]:
-    """Calculate frontier return bounds.
+    """Calculate frontier return bounds.
 
     Args:
         simulated: Simulated portfolios DataFrame.
@@ -269,9 +269,9 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         Tuple of (min_return, max_return).
-    """
-    min_stdev_idx = simulated["stdev"].idxmin()
-    frontier_min = cast("float", simulated.loc[min_stdev_idx, "ret"])
+    """
+    min_stdev_idx = simulated["stdev"].idxmin()
+    frontier_min = cast("float", simulated.loc[min_stdev_idx, "ret"])
 
     arithmetic_means = array(log_ret.mean() * periods_in_a_year)
     cleaned_arithmetic_means = arithmetic_means[~isnan(arithmetic_means)]
@@ -290,7 +290,7 @@ 

Source code for openseries.portfoliotools

     bounds: tuple[tuple[float, float], ...],
     minimize_method: LiteralMinimizeMethods,
 ) -> tuple[list[float], list[NDArray[float64]]]:
-    """Build frontier line points.
+    """Build frontier line points.
 
     Args:
         log_ret: Returns DataFrame.
@@ -304,10 +304,10 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         Tuple of (frontier_x, frontier_weights).
-    """
+    """
 
     def _check_sum(weights: NDArray[float64]) -> float:
-        return cast("float", npsum(weights) - 1)
+        return cast("float", npsum(weights) - 1)
 
     def _get_ret_vol_sr(
         lg_ret: DataFrame,
@@ -317,7 +317,7 @@ 

Source code for openseries.portfoliotools

         ret = npsum(lg_ret.mean() * weights) * per_in_yr
         volatility = sqrt(weights.T @ (lg_ret.cov() * per_in_yr @ weights))
         sr = ret / volatility
-        return cast("NDArray[float64]", array([ret, volatility, sr]))
+        return cast("NDArray[float64]", array([ret, volatility, sr]))
 
     def _diff_return(
         lg_ret: DataFrame,
@@ -326,7 +326,7 @@ 

Source code for openseries.portfoliotools

         poss_return: float,
     ) -> float64:
         return cast(
-            "float64",
+            "float64",
             _get_ret_vol_sr(lg_ret=lg_ret, weights=weights, per_in_yr=per_in_yr)[0]
             - poss_return,
         )
@@ -335,7 +335,7 @@ 

Source code for openseries.portfoliotools

         weights: NDArray[float64],
     ) -> float64:
         return cast(
-            "float64",
+            "float64",
             _get_ret_vol_sr(
                 lg_ret=log_ret,
                 weights=weights,
@@ -349,12 +349,12 @@ 

Source code for openseries.portfoliotools

 
     for possible_return in frontier_y:
         cons = cast(
-            "Any",
+            "Any",
             [
-                {"type": "eq", "fun": _check_sum},
+                {"type": "eq", "fun": _check_sum},
                 {
-                    "type": "eq",
-                    "fun": lambda w, poss_return=possible_return: _diff_return(
+                    "type": "eq",
+                    "fun": lambda w, poss_return=possible_return: _diff_return(
                         lg_ret=log_ret,
                         weights=w,
                         per_in_yr=periods_in_a_year,
@@ -372,8 +372,8 @@ 

Source code for openseries.portfoliotools

             constraints=cons,
         )
 
-        frontier_x.append(result["fun"])
-        frontier_weights.append(result["x"])
+        frontier_x.append(result["fun"])
+        frontier_weights.append(result["x"])
 
     return frontier_x, frontier_weights
 
@@ -384,7 +384,7 @@ 

Source code for openseries.portfoliotools

     frontier_weights: list[NDArray[float64]],
     columns_lvl_zero: list[str],
 ) -> DataFrame:
-    """Build frontier DataFrame.
+    """Build frontier DataFrame.
 
     Args:
         frontier_x: Frontier volatility values.
@@ -394,25 +394,25 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         Frontier DataFrame.
-    """
+    """
     line_df = concat(
         [
             DataFrame(data=frontier_weights, columns=columns_lvl_zero),
-            DataFrame({"stdev": frontier_x, "ret": frontier_y}),
+            DataFrame({"stdev": frontier_x, "ret": frontier_y}),
         ],
-        axis="columns",
+        axis="columns",
     )
-    line_df["sharpe"] = line_df.ret / line_df.stdev
+    line_df["sharpe"] = line_df.ret / line_df.stdev
 
     limit_small = 0.0001
     line_df = line_df.mask(line_df.abs() < limit_small, 0.0)
 
     weight_cols = columns_lvl_zero
-    weight_header = "<br><br>Weights:<br>"
-    line_df["text"] = line_df[weight_cols].apply(
+    weight_header = "<br><br>Weights:<br>"
+    line_df["text"] = line_df[weight_cols].apply(
         lambda row: (
             weight_header
-            + "<br>".join([f"{row[col]:.1%}  {col}" for col in weight_cols])
+            + "<br>".join([f"{row[col]:.1%}  {col}" for col in weight_cols])
         ),
         axis=1,
     )
@@ -421,18 +421,18 @@ 

Source code for openseries.portfoliotools

 
 
 def _apply_tweak(line_df: DataFrame) -> DataFrame:
-    """Apply tweak to frontier DataFrame.
+    """Apply tweak to frontier DataFrame.
 
     Args:
         line_df: Frontier DataFrame.
 
     Returns:
         Tweaked DataFrame.
-    """
+    """
     limit_tweak = 0.001
-    line_df["stdev_diff"] = line_df.stdev.ffill().pct_change()
+    line_df["stdev_diff"] = line_df.stdev.ffill().pct_change()
     line_df = line_df.loc[line_df.stdev_diff.abs() > limit_tweak]
-    return line_df.drop(columns="stdev_diff")
+    return line_df.drop(columns="stdev_diff")
 
 
 def _create_optimization_functions(
@@ -443,7 +443,7 @@ 

Source code for openseries.portfoliotools

     Callable[[NDArray[float64]], NDArray[float64]],
     Callable[[NDArray[float64]], float64],
 ]:
-    """Create optimization helper functions.
+    """Create optimization helper functions.
 
     Args:
         log_ret: Returns DataFrame.
@@ -451,19 +451,19 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         Tuple of (_check_sum, _get_ret_vol_sr, _neg_sharpe) functions.
-    """
+    """
 
     def _check_sum(weights: NDArray[float64]) -> float:
-        return cast("float", npsum(weights) - 1)
+        return cast("float", npsum(weights) - 1)
 
     def _get_ret_vol_sr(weights: NDArray[float64]) -> NDArray[float64]:
         ret = npsum(log_ret.mean() * weights) * periods_in_a_year
         volatility = sqrt(weights.T @ (log_ret.cov() * periods_in_a_year @ weights))
         sr = ret / volatility
-        return cast("NDArray[float64]", array([ret, volatility, sr]))
+        return cast("NDArray[float64]", array([ret, volatility, sr]))
 
     def _neg_sharpe(weights: NDArray[float64]) -> float64:
-        return cast("float64", _get_ret_vol_sr(weights)[2] * -1)
+        return cast("float64", _get_ret_vol_sr(weights)[2] * -1)
 
     return _check_sum, _get_ret_vol_sr, _neg_sharpe
 
@@ -476,7 +476,7 @@ 

Source code for openseries.portfoliotools

     _get_ret_vol_sr: Callable[[NDArray[float64]], NDArray[float64]],
     _neg_sharpe: Callable[[NDArray[float64]], float64],
 ) -> tuple[NDArray[float64], NDArray[float64]]:
-    """Optimize maximum Sharpe ratio portfolio.
+    """Optimize maximum Sharpe ratio portfolio.
 
     Args:
         init_guess: Initial guess for optimization.
@@ -488,8 +488,8 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         Tuple of (optimal metrics, optimal weights).
-    """
-    constraints = cast("Any", [{"type": "eq", "fun": _check_sum}])
+    """
+    constraints = cast("Any", [{"type": "eq", "fun": _check_sum}])
     opt_results = minimize(
         fun=_neg_sharpe,
         x0=init_guess,
@@ -511,11 +511,11 @@ 

Source code for openseries.portfoliotools

     seed: int = 71,
     bounds: tuple[tuple[float, float], ...] | None = None,
     frontier_points: int = 200,
-    minimize_method: LiteralMinimizeMethods = "SLSQP",
+    minimize_method: LiteralMinimizeMethods = "SLSQP",
     *,
     tweak: bool = True,
 ) -> tuple[DataFrame, DataFrame, NDArray[float64]]:
-    """Identify an efficient frontier.
+    """Identify an efficient frontier.
 
     Args:
         eframe: Portfolio data.
@@ -531,7 +531,7 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         The efficient frontier data, simulation data and optimal portfolio.
-    """
+    """
     log_ret, copi = _prepare_returns_for_frontier(eframe)
 
     simulated = simulate_portfolios(simframe=copi, num_ports=num_ports, seed=seed)
@@ -591,18 +591,18 @@ 

Source code for openseries.portfoliotools

 def constrain_optimized_portfolios(
     data: OpenFrame,
     serie: OpenTimeSeries,
-    portfolioname: str = "Current Portfolio",
+    portfolioname: str = "Current Portfolio",
     simulations: int = 10000,
     curve_points: int = 200,
     bounds: tuple[tuple[float, float], ...] | None = None,
-    minimize_method: LiteralMinimizeMethods = "SLSQP",
+    minimize_method: LiteralMinimizeMethods = "SLSQP",
 ) -> tuple[OpenFrame, OpenTimeSeries, OpenFrame, OpenTimeSeries]:
-    """Constrain optimized portfolios to those that improve on the current one.
+    """Constrain optimized portfolios to those that improve on the current one.
 
     Args:
         data: Portfolio data.
         serie: A timeseries representing the current portfolio.
-        portfolioname: Name of the portfolio. Defaults to "Current Portfolio".
+        portfolioname: Name of the portfolio. Defaults to "Current Portfolio".
         simulations: Number of possible portfolios to simulate. Defaults to 10000.
         curve_points: Number of optimal portfolios on the efficient frontier.
             Defaults to 200.
@@ -613,7 +613,7 @@ 

Source code for openseries.portfoliotools

     Returns:
         The constrained optimal portfolio data.
 
-    """
+    """
     lr_frame = data.from_deepcopy()
     mv_frame = data.from_deepcopy()
 
@@ -629,9 +629,9 @@ 

Source code for openseries.portfoliotools

     )
 
     condition_least_ret = front_frame.ret > serie.arithmetic_ret
-    least_ret_frame = front_frame[condition_least_ret].sort_values(by="stdev")
+    least_ret_frame = front_frame[condition_least_ret].sort_values(by="stdev")
     least_ret_port: Series[float] = least_ret_frame.iloc[0]
-    least_ret_port_name = f"Minimize vol & target return of {portfolioname}"
+    least_ret_port_name = f"Minimize vol & target return of {portfolioname}"
     least_ret_weights: list[float] = [
         least_ret_port.loc[c] for c in lr_frame.columns_lvl_zero
     ]
@@ -640,11 +640,11 @@ 

Source code for openseries.portfoliotools

 
     condition_most_vol = front_frame.stdev < serie.vol
     most_vol_frame = front_frame[condition_most_vol].sort_values(
-        by="ret",
+        by="ret",
         ascending=False,
     )
     most_vol_port: Series[float] = most_vol_frame.iloc[0]
-    most_vol_port_name = f"Maximize return & target risk of {portfolioname}"
+    most_vol_port_name = f"Maximize return & target risk of {portfolioname}"
     most_vol_weights: list[float] = [
         most_vol_port.loc[c] for c in mv_frame.columns_lvl_zero
     ]
@@ -662,7 +662,7 @@ 

Source code for openseries.portfoliotools

     current: OpenTimeSeries,
     optimized: NDArray[float64],
 ) -> DataFrame:
-    """Prepare data to be used as point_frame in the sharpeplot function.
+    """Prepare data to be used as point_frame in the sharpeplot function.
 
     Args:
         assets: Portfolio data with individual assets and a weighted portfolio.
@@ -671,12 +671,12 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         The data prepared with mean returns, volatility and weights.
-    """
-    txt = "<br><br>Weights:<br>" + "<br>".join(
+    """
+    txt = "<br><br>Weights:<br>" + "<br>".join(
         [
-            f"{wgt:.1%}  {nm}"
+            f"{wgt:.1%}  {nm}"
             for wgt, nm in zip(
-                cast("list[float]", assets.weights),
+                cast("list[float]", assets.weights),
                 assets.columns_lvl_zero,
                 strict=True,
             )
@@ -684,23 +684,23 @@ 

Source code for openseries.portfoliotools

     )
 
     opt_text_list = [
-        f"{wgt:.1%}  {nm}"
+        f"{wgt:.1%}  {nm}"
         for wgt, nm in zip(optimized[3:], assets.columns_lvl_zero, strict=True)
     ]
-    opt_text = "<br><br>Weights:<br>" + "<br>".join(opt_text_list)
+    opt_text = "<br><br>Weights:<br>" + "<br>".join(opt_text_list)
     plotframe = DataFrame(
         data=[
             assets.arithmetic_ret,
             assets.vol,
             Series(
-                data=[""] * assets.item_count,
+                data=[""] * assets.item_count,
                 index=assets.vol.index,
             ),
         ],
-        index=["ret", "stdev", "text"],
+        index=["ret", "stdev", "text"],
     )
     plotframe.columns = plotframe.columns.get_level_values(0)
-    plotframe["Max Sharpe Portfolio"] = Series(
+    plotframe["Max Sharpe Portfolio"] = Series(
         data=[optimized[0], optimized[1], opt_text],
         index=plotframe.index,
         dtype=object,
@@ -718,18 +718,18 @@ 

Source code for openseries.portfoliotools

 
 
 def _determine_output_directory(directory: DirectoryPath | None) -> Path:
-    """Determine output directory for plot file.
+    """Determine output directory for plot file.
 
     Args:
         directory: Optional directory path.
 
     Returns:
         Path to output directory.
-    """
+    """
     if directory:
         return Path(directory).resolve()
-    if Path.home().joinpath("Documents").exists():
-        return Path.home().joinpath("Documents")
+    if Path.home().joinpath("Documents").exists():
+        return Path.home().joinpath("Documents")
     return Path(stack()[2].filename).parent
 
 
@@ -739,30 +739,30 @@ 

Source code for openseries.portfoliotools

     returns: list[float],
     risk: list[float],
 ) -> None:
-    """Add simulated portfolios trace to figure.
+    """Add simulated portfolios trace to figure.
 
     Args:
         figure: Plotly figure.
         sim_frame: Simulated portfolios DataFrame.
         returns: List to extend with returns.
         risk: List to extend with risk values.
-    """
-    returns.extend(list(sim_frame.loc[:, "ret"]))
-    risk.extend(list(sim_frame.loc[:, "stdev"]))
+    """
+    returns.extend(list(sim_frame.loc[:, "ret"]))
+    risk.extend(list(sim_frame.loc[:, "stdev"]))
     figure.add_scatter(
-        x=sim_frame.loc[:, "stdev"],
-        y=sim_frame.loc[:, "ret"],
-        hoverinfo="skip",
+        x=sim_frame.loc[:, "stdev"],
+        y=sim_frame.loc[:, "ret"],
+        hoverinfo="skip",
         marker={
-            "size": 10,
-            "opacity": 0.5,
-            "color": sim_frame.loc[:, "sharpe"],
-            "colorscale": "Jet",
-            "reversescale": True,
-            "colorbar": {"thickness": 20, "title": "Ratio<br>ret / vol"},
+            "size": 10,
+            "opacity": 0.5,
+            "color": sim_frame.loc[:, "sharpe"],
+            "colorscale": "Jet",
+            "reversescale": True,
+            "colorbar": {"thickness": 20, "title": "Ratio<br>ret / vol"},
         },
-        mode="markers",
-        name="simulated portfolios",
+        mode="markers",
+        name="simulated portfolios",
     )
 
 
@@ -772,27 +772,27 @@ 

Source code for openseries.portfoliotools

     returns: list[float],
     risk: list[float],
 ) -> None:
-    """Add efficient frontier trace to figure.
+    """Add efficient frontier trace to figure.
 
     Args:
         figure: Plotly figure.
         line_frame: Efficient frontier DataFrame.
         returns: List to extend with returns.
         risk: List to extend with risk values.
-    """
-    returns.extend(list(line_frame.loc[:, "ret"]))
-    risk.extend(list(line_frame.loc[:, "stdev"]))
+    """
+    returns.extend(list(line_frame.loc[:, "ret"]))
+    risk.extend(list(line_frame.loc[:, "stdev"]))
     figure.add_scatter(
-        x=line_frame.loc[:, "stdev"],
-        y=line_frame.loc[:, "ret"],
-        text=line_frame.loc[:, "text"],
-        xhoverformat=".2%",
-        yhoverformat=".2%",
-        hovertemplate="Return %{y}<br>Vol %{x}%{text}",
-        hoverlabel_align="right",
-        line={"width": 2.5, "dash": "solid"},
-        mode="lines",
-        name="Efficient frontier",
+        x=line_frame.loc[:, "stdev"],
+        y=line_frame.loc[:, "ret"],
+        text=line_frame.loc[:, "text"],
+        xhoverformat=".2%",
+        yhoverformat=".2%",
+        hovertemplate="Return %{y}<br>Vol %{x}%{text}",
+        hoverlabel_align="right",
+        line={"width": 2.5, "dash": "solid"},
+        mode="lines",
+        name="Efficient frontier",
     )
 
 
@@ -804,7 +804,7 @@ 

Source code for openseries.portfoliotools

     returns: list[float],
     risk: list[float],
 ) -> None:
-    """Add point frame traces to figure.
+    """Add point frame traces to figure.
 
     Args:
         figure: Plotly figure.
@@ -813,12 +813,12 @@ 

Source code for openseries.portfoliotools

         fig: Plotly figure dictionary.
         returns: List to extend with returns.
         risk: List to extend with risk values.
-    """
+    """
     layout_dict = cast(
-        "dict[str, str | int | float | bool | list[str]]",
-        fig["layout"],
+        "dict[str, str | int | float | bool | list[str]]",
+        fig["layout"],
     )
-    base_colorway = cast("list[str]", layout_dict.get("colorway", []))
+    base_colorway = cast("list[str]", layout_dict.get("colorway", []))
     if len(base_colorway) < len(point_frame.columns) and base_colorway:
         repeats = (len(point_frame.columns) + len(base_colorway) - 1) // len(
             base_colorway
@@ -827,22 +827,22 @@ 

Source code for openseries.portfoliotools

     else:
         colorway = base_colorway[: len(point_frame.columns)]
     for col, clr in zip(point_frame.columns, colorway, strict=True):
-        returns.extend([cast("float", point_frame.loc["ret", col])])
-        risk.extend([cast("float", point_frame.loc["stdev", col])])
+        returns.extend([cast("float", point_frame.loc["ret", col])])
+        risk.extend([cast("float", point_frame.loc["stdev", col])])
         figure.add_scatter(
-            x=[point_frame.loc["stdev", col]],
-            y=[point_frame.loc["ret", col]],
-            xhoverformat=".2%",
-            yhoverformat=".2%",
-            hovertext=[point_frame.loc["text", col]],
-            hovertemplate="Return %{y}<br>Vol %{x}%{hovertext}",
-            hoverlabel_align="right",
-            marker={"size": 20, "color": clr},
+            x=[point_frame.loc["stdev", col]],
+            y=[point_frame.loc["ret", col]],
+            xhoverformat=".2%",
+            yhoverformat=".2%",
+            hovertext=[point_frame.loc["text", col]],
+            hovertemplate="Return %{y}<br>Vol %{x}%{hovertext}",
+            hoverlabel_align="right",
+            marker={"size": 20, "color": clr},
             mode=point_frame_mode,
             name=col,
             text=col,
-            textfont={"size": 14},
-            textposition="bottom center",
+            textfont={"size": 14},
+            textposition="bottom center",
         )
 
 
@@ -854,7 +854,7 @@ 

Source code for openseries.portfoliotools

     title: bool = True,
     add_logo: bool = True,
 ) -> None:
-    """Configure figure layout.
+    """Configure figure layout.
 
     Args:
         figure: Plotly figure.
@@ -862,22 +862,22 @@ 

Source code for openseries.portfoliotools

         titletext: Optional title text.
         add_logo: Whether to add logo.
         logo: Logo dictionary.
-    """
+    """
     figure.update_layout(
-        xaxis={"tickformat": ".1%"},
-        xaxis_title="volatility",
+        xaxis={"tickformat": ".1%"},
+        xaxis_title="volatility",
         yaxis={
-            "tickformat": ".1%",
-            "scaleanchor": "x",
-            "scaleratio": 1,
+            "tickformat": ".1%",
+            "scaleanchor": "x",
+            "scaleratio": 1,
         },
-        yaxis_title="annual return",
+        yaxis_title="annual return",
         showlegend=False,
     )
     if title:
         if titletext is None:
-            titletext = "<b>Risk and Return</b><br>"
-        figure.update_layout(title={"text": titletext, "font": {"size": 36}})
+            titletext = "<b>Risk and Return</b><br>"
+        figure.update_layout(title={"text": titletext, "font": {"size": 36}})
 
     if add_logo:
         figure.add_layout_image(logo)
@@ -893,7 +893,7 @@ 

Source code for openseries.portfoliotools

     *,
     auto_open: bool = True,
 ) -> str:
-    """Generate output for sharpeplot.
+    """Generate output for sharpeplot.
 
     Args:
         figure: Plotly figure.
@@ -906,26 +906,26 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         Output string.
-    """
-    if output_type == "file":
+    """
+    if output_type == "file":
         plot(
             figure_or_data=figure,
             filename=str(plotfile),
             auto_open=auto_open,
             auto_play=False,
-            link_text="",
+            link_text="",
             include_plotlyjs=include_plotlyjs,
-            config=fig["config"],
+            config=fig["config"],
             output_type=output_type,
         )
         return str(plotfile)
 
-    div_id = filename.split(maxsplit=1, sep=".")[0]
+    div_id = filename.split(maxsplit=1, sep=".")[0]
     return cast(
-        "str",
+        "str",
         to_html(
             fig=figure,
-            config=fig["config"],
+            config=fig["config"],
             auto_play=False,
             include_plotlyjs=include_plotlyjs,
             full_html=False,
@@ -940,18 +940,18 @@ 

Source code for openseries.portfoliotools

     sim_frame: DataFrame | None = None,
     line_frame: DataFrame | None = None,
     point_frame: DataFrame | None = None,
-    point_frame_mode: LiteralLinePlotMode = "markers",
+    point_frame_mode: LiteralLinePlotMode = "markers",
     filename: str | None = None,
     directory: DirectoryPath | None = None,
     titletext: str | None = None,
-    output_type: LiteralPlotlyOutput = "file",
-    include_plotlyjs: LiteralPlotlyJSlib = "cdn",
+    output_type: LiteralPlotlyOutput = "file",
+    include_plotlyjs: LiteralPlotlyJSlib = "cdn",
     *,
     title: bool = True,
     add_logo: bool = True,
     auto_open: bool = True,
 ) -> tuple[Figure, str]:
-    """Create scatter plot coloured by Sharpe Ratio.
+    """Create scatter plot coloured by Sharpe Ratio.
 
     Args:
         sim_frame: Data from the simulate_portfolios method.
@@ -961,10 +961,10 @@ 

Source code for openseries.portfoliotools

         filename: Name of the Plotly html file.
         directory: Directory where Plotly html file is saved.
         titletext: Text for the plot title.
-        output_type: Determines output type. Defaults to "file".
+        output_type: Determines output type. Defaults to "file".
         include_plotlyjs: Determines how the plotly.js library is included
             in the output.
-            Defaults to "cdn".
+            Defaults to "cdn".
         title: Whether to add standard plot title. Defaults to True.
         add_logo: Whether to add Captor logo. Defaults to True.
         auto_open: Determines whether to open a browser window with the plot.
@@ -972,9 +972,9 @@ 

Source code for openseries.portfoliotools

 
     Returns:
         The scatter plot with simulated and optimized results.
-    """
+    """
     if sim_frame is None and line_frame is None and point_frame is None:
-        msg = "One of sim_frame, line_frame or point_frame must be provided."
+        msg = "One of sim_frame, line_frame or point_frame must be provided."
         raise AtLeastOneFrameError(msg)
 
     returns: list[float] = []
@@ -982,7 +982,7 @@ 

Source code for openseries.portfoliotools

 
     dirpath = _determine_output_directory(directory)
     if not filename:
-        filename = "sharpeplot.html"
+        filename = "sharpeplot.html"
     plotfile = dirpath.joinpath(filename)
 
     fig, logo = load_plotly_dict()
diff --git a/docs/build/html/_modules/openseries/report.html b/docs/build/html/_modules/openseries/report.html
index 5a58c3ea..44cd0985 100644
--- a/docs/build/html/_modules/openseries/report.html
+++ b/docs/build/html/_modules/openseries/report.html
@@ -108,7 +108,7 @@
            

Source code for openseries.report

-"""Functions related to HTML reports."""
+"""Functions related to HTML reports."""
 
 from __future__ import annotations
 
@@ -143,29 +143,29 @@ 

Source code for openseries.report

 
 logger = getLogger(__name__)
 
-__all__ = ["report_html"]
+__all__ = ["report_html"]
 
 
 def calendar_period_returns(
     data: OpenFrame,
-    freq: LiteralBizDayFreq = "BYE",
+    freq: LiteralBizDayFreq = "BYE",
     *,
     relabel: bool = True,
 ) -> DataFrame:
-    """Generate a table of returns with appropriate table labels."""
+    """Generate a table of returns with appropriate table labels."""
     copied = data.from_deepcopy()
     copied.resample_to_business_period_ends(freq=freq)
     copied.value_to_ret()
     cldr = copied.tsdf.iloc[1:].copy()
     if relabel:
-        if freq.upper() == "BYE":
+        if freq.upper() == "BYE":
             cldr.index = Index([d.year for d in cldr.index])
-        elif freq.upper() == "BQE":
+        elif freq.upper() == "BQE":
             cldr.index = Index(
-                [Timestamp(d).to_period("Q").strftime("Q%q %Y") for d in cldr.index],
+                [Timestamp(d).to_period("Q").strftime("Q%q %Y") for d in cldr.index],
             )
         else:
-            cldr.index = Index([d.strftime("%b %y") for d in cldr.index])
+            cldr.index = Index([d.strftime("%b %y") for d in cldr.index])
     return cldr
 
 
@@ -174,100 +174,100 @@ 

Source code for openseries.report

 
 
 def _fmt_dates(idx: Index) -> list[str]:
-    return [Timestamp(d).strftime("%Y-%m-%d") for d in idx]
+    return [Timestamp(d).strftime("%Y-%m-%d") for d in idx]
 
 
 def _metrics_table_html(df: DataFrame) -> str:
-    return df.to_html(index=False, escape=False, classes=["metrics"], border=0)
+    return df.to_html(index=False, escape=False, classes=["metrics"], border=0)
 
 
 def _get_report_properties_and_labels(
     yearfrac: float,
 ) -> tuple[list[str], list[str], list[str]]:
-    """Get properties and labels based on yearfrac."""
+    """Get properties and labels based on yearfrac."""
     if yearfrac > 1.0:
         properties = [
-            "geo_ret",
-            "vol",
-            "ret_vol_ratio",
-            "sortino_ratio",
-            "worst_month",
-            "first_indices",
-            "last_indices",
+            "geo_ret",
+            "vol",
+            "ret_vol_ratio",
+            "sortino_ratio",
+            "worst_month",
+            "first_indices",
+            "last_indices",
         ]
         labels_init = [
-            "Return (CAGR)",
-            "Volatility",
-            "Sharpe Ratio",
-            "Sortino Ratio",
-            "Worst Month",
-            "Comparison Start",
-            "Comparison End",
-            "Jensen's Alpha",
-            "Information Ratio",
-            "Tracking Error (weekly)",
-            "Capture Ratio (monthly)",
-            "Index Beta (weekly)",
+            "Return (CAGR)",
+            "Volatility",
+            "Sharpe Ratio",
+            "Sortino Ratio",
+            "Worst Month",
+            "Comparison Start",
+            "Comparison End",
+            "Jensen's Alpha",
+            "Information Ratio",
+            "Tracking Error (weekly)",
+            "Capture Ratio (monthly)",
+            "Index Beta (weekly)",
         ]
         labels_final = [
-            "Return (CAGR)",
-            "Year-to-Date",
-            "Month-to-Date",
-            "Volatility",
-            "Sharpe Ratio",
-            "Sortino Ratio",
-            "Jensen's Alpha",
-            "Information Ratio",
-            "Tracking Error (weekly)",
-            "Index Beta (weekly)",
-            "Capture Ratio (monthly)",
-            "Worst Month",
-            "Comparison Start",
-            "Comparison End",
+            "Return (CAGR)",
+            "Year-to-Date",
+            "Month-to-Date",
+            "Volatility",
+            "Sharpe Ratio",
+            "Sortino Ratio",
+            "Jensen's Alpha",
+            "Information Ratio",
+            "Tracking Error (weekly)",
+            "Index Beta (weekly)",
+            "Capture Ratio (monthly)",
+            "Worst Month",
+            "Comparison Start",
+            "Comparison End",
         ]
     else:
         properties = [
-            "value_ret",
-            "vol",
-            "ret_vol_ratio",
-            "sortino_ratio",
-            "worst",
-            "first_indices",
-            "last_indices",
+            "value_ret",
+            "vol",
+            "ret_vol_ratio",
+            "sortino_ratio",
+            "worst",
+            "first_indices",
+            "last_indices",
         ]
         labels_init = [
-            "Return (simple)",
-            "Volatility",
-            "Sharpe Ratio",
-            "Sortino Ratio",
-            "Worst Day",
-            "Comparison Start",
-            "Comparison End",
-            "Jensen's Alpha",
-            "Information Ratio",
-            "Tracking Error (weekly)",
-            "Index Beta (weekly)",
+            "Return (simple)",
+            "Volatility",
+            "Sharpe Ratio",
+            "Sortino Ratio",
+            "Worst Day",
+            "Comparison Start",
+            "Comparison End",
+            "Jensen's Alpha",
+            "Information Ratio",
+            "Tracking Error (weekly)",
+            "Index Beta (weekly)",
         ]
         labels_final = [
-            "Return (simple)",
-            "Year-to-Date",
-            "Month-to-Date",
-            "Volatility",
-            "Sharpe Ratio",
-            "Sortino Ratio",
-            "Jensen's Alpha",
-            "Information Ratio",
-            "Tracking Error (weekly)",
-            "Index Beta (weekly)",
-            "Worst Day",
-            "Comparison Start",
-            "Comparison End",
+            "Return (simple)",
+            "Year-to-Date",
+            "Month-to-Date",
+            "Volatility",
+            "Sharpe Ratio",
+            "Sortino Ratio",
+            "Jensen's Alpha",
+            "Information Ratio",
+            "Tracking Error (weekly)",
+            "Index Beta (weekly)",
+            "Worst Day",
+            "Comparison Start",
+            "Comparison End",
         ]
     return properties, labels_init, labels_final
 
 
 def _create_line_traces(data: OpenFrame) -> list[Scatter]:
-    """Create line traces for the plot."""
+    """Create line traces for the plot."""
     x_line = _fmt_dates(data.tsdf.index)
     line_traces: list[Scatter] = []
     for item, lbl in enumerate(data.columns_lvl_zero):
@@ -275,9 +275,9 @@ 

Source code for openseries.report

             Scatter(
                 x=x_line,
                 y=data.tsdf.iloc[:, item].tolist(),
-                hovertemplate=f"{lbl}<br>%{{y:.2%}}<br>%{{x}}<extra></extra>",
-                line={"width": 2.5, "dash": "solid"},
-                mode="lines",
+                hovertemplate=f"{lbl}<br>%{{y:.2%}}<br>%{{x}}<extra></extra>",
+                line={"width": 2.5, "dash": "solid"},
+                mode="lines",
                 name=lbl,
                 showlegend=True,
             ),
@@ -289,7 +289,7 @@ 

Source code for openseries.report

     data: OpenFrame,
     bar_freq: LiteralBizDayFreq,
 ) -> list[Bar]:
-    """Create bar traces for the plot."""
+    """Create bar traces for the plot."""
     quarter_of_year = 0.25
     if data.yearfrac < quarter_of_year:
         tmp = data.from_deepcopy()
@@ -300,12 +300,12 @@ 

Source code for openseries.report

     x_bar = [str(x) for x in bdf.index]
     bar_traces: list[Bar] = []
     for item in range(data.item_count):
-        col_name = cast("tuple[str, ValueType]", bdf.iloc[:, item].name)
+        col_name = cast("tuple[str, ValueType]", bdf.iloc[:, item].name)
         bar_traces.append(
             Bar(
                 x=x_bar,
                 y=bdf.iloc[:, item].tolist(),
-                hovertemplate=f"{col_name[0]}<br>%{{y:.2%}}<br>%{{x}}<extra></extra>",
+                hovertemplate=f"{col_name[0]}<br>%{{y:.2%}}<br>%{{x}}<extra></extra>",
                 name=col_name[0],
                 showlegend=False,
             ),
@@ -317,11 +317,11 @@ 

Source code for openseries.report

     rpt_df: DataFrame,
     data: OpenFrame,
 ) -> DataFrame:
-    """Add Jensen's Alpha to the report dataframe."""
+    """Add Jensen's Alpha to the report dataframe."""
     alpha_frame = data.from_deepcopy()
     alpha_frame.to_cumret()
     with catch_warnings():
-        simplefilter("ignore")
+        simplefilter("ignore")
         alphas: list[str | float] = [
             alpha_frame.jensen_alpha(
                 asset=(aname, ValueType.PRICE),
@@ -330,11 +330,11 @@ 

Source code for openseries.report

             )
             for aname in alpha_frame.columns_lvl_zero[:-1]
         ]
-    alphas.append("")
+    alphas.append("")
     ar = DataFrame(
         data=alphas,
         index=data.tsdf.columns,
-        columns=["Jensen's Alpha"],
+        columns=["Jensen's Alpha"],
     ).T
     return concat([rpt_df, ar])
 
@@ -343,9 +343,9 @@ 

Source code for openseries.report

     rpt_df: DataFrame,
     data: OpenFrame,
 ) -> DataFrame:
-    """Add Information Ratio to the report dataframe."""
+    """Add Information Ratio to the report dataframe."""
     ir = data.info_ratio_func()
-    ir.name = "Information Ratio"
+    ir.name = "Information Ratio"
     ir.iloc[-1] = None
     ir_df = ir.to_frame().T
     return concat([rpt_df, ir_df])
@@ -355,21 +355,21 @@ 

Source code for openseries.report

     rpt_df: DataFrame,
     data: OpenFrame,
 ) -> DataFrame:
-    """Add Tracking Error to the report dataframe."""
+    """Add Tracking Error to the report dataframe."""
     te_frame = data.from_deepcopy()
-    te_frame.resample("7D")
+    te_frame.resample("7D")
     with catch_warnings():
-        simplefilter("ignore")
+        simplefilter("ignore")
         te: Series[float] | Series[str] = te_frame.tracking_error_func()
     if te.hasnans:
         te = Series(
-            data=[""] * te_frame.item_count,
+            data=[""] * te_frame.item_count,
             index=te_frame.tsdf.columns,
-            name="Tracking Error (weekly)",
+            name="Tracking Error (weekly)",
         )
     else:
         te.iloc[-1] = None
-        te.name = "Tracking Error (weekly)"
+        te.name = "Tracking Error (weekly)"
     te_df = te.to_frame().T
     return concat([rpt_df, te_df])
 
@@ -379,27 +379,27 @@ 

Source code for openseries.report

     data: OpenFrame,
     formats: list[str],
 ) -> tuple[DataFrame, list[str]]:
-    """Add Capture Ratio to the report dataframe."""
+    """Add Capture Ratio to the report dataframe."""
     crm = data.from_deepcopy()
-    crm.resample("ME")
+    crm.resample("ME")
     cru_save = Series(
-        data=[""] * crm.item_count,
+        data=[""] * crm.item_count,
         index=crm.tsdf.columns,
-        name="Capture Ratio (monthly)",
+        name="Capture Ratio (monthly)",
     )
     with catch_warnings():
-        simplefilter("ignore")
+        simplefilter("ignore")
         try:
-            cru: Series[float] | Series[str] = crm.capture_ratio_func(ratio="both")
+            cru: Series[float] | Series[str] = crm.capture_ratio_func(ratio="both")
         except ZeroDivisionError as exc:  # pragma: no cover
-            msg = f"Capture ratio calculation error: {exc!s}"  # pragma: no cover
+            msg = f"Capture ratio calculation error: {exc!s}"  # pragma: no cover
             logger.warning(msg)  # pragma: no cover
             cru = cru_save  # pragma: no cover
     if cru.hasnans:
         cru = cru_save
     else:
         cru.iloc[-1] = None
-        cru.name = "Capture Ratio (monthly)"
+        cru.name = "Capture Ratio (monthly)"
     cru_df = cru.to_frame().T
     return concat([rpt_df, cru_df]), formats
 
@@ -408,9 +408,9 @@ 

Source code for openseries.report

     rpt_df: DataFrame,
     data: OpenFrame,
 ) -> DataFrame:
-    """Add Index Beta to the report dataframe."""
+    """Add Index Beta to the report dataframe."""
     beta_frame = data.from_deepcopy()
-    beta_frame.resample("7D").value_nan_handle("drop")
+    beta_frame.resample("7D").value_nan_handle("drop")
     beta_frame.to_cumret()
     betas: list[str | float] = [
         beta_frame.beta(
@@ -419,11 +419,11 @@ 

Source code for openseries.report

         )
         for bname in beta_frame.columns_lvl_zero[:-1]
     ]
-    betas.append("")
+    betas.append("")
     br = DataFrame(
         data=betas,
         index=data.tsdf.columns,
-        columns=["Index Beta (weekly)"],
+        columns=["Index Beta (weekly)"],
     ).T
     return concat([rpt_df, br])
 
@@ -432,26 +432,26 @@ 

Source code for openseries.report

     rpt_df: DataFrame,
     data: OpenFrame,
 ) -> DataFrame:
-    """Add Year-to-Date and Month-to-Date to the report dataframe."""
+    """Add Year-to-Date and Month-to-Date to the report dataframe."""
     this_year = data.last_idx.year
     this_month = data.last_idx.month
-    ytd = data.value_ret_calendar_period(year=this_year).map("{:.2%}".format)
-    ytd.name = "Year-to-Date"
+    ytd = data.value_ret_calendar_period(year=this_year).map("{:.2%}".format)
+    ytd.name = "Year-to-Date"
     mtd = data.value_ret_calendar_period(year=this_year, month=this_month).map(
-        "{:.2%}".format,
+        "{:.2%}".format,
     )
-    mtd.name = "Month-to-Date"
+    mtd.name = "Month-to-Date"
     ytd_df = ytd.to_frame().T
     mtd_df = mtd.to_frame().T
     return concat([rpt_df, ytd_df, mtd_df])
 
 
 def _get_output_directory(directory: Path | None) -> Path:
-    """Determine the output directory."""
+    """Determine the output directory."""
     if directory:
         return Path(directory).resolve()
-    if Path.home().joinpath("Documents").exists():
-        return Path.home() / "Documents"
+    if Path.home().joinpath("Documents").exists():
+        return Path.home() / "Documents"
     return Path(stack()[1].filename).parent
 
 
@@ -460,26 +460,26 @@ 

Source code for openseries.report

     colorway: list[str],
     item_count: int,
 ) -> tuple[dict[str, Any], dict[str, Any]]:
-    """Get line and bar layouts for plotly."""
+    """Get line and bar layouts for plotly."""
     line_layout = dict(layout_theme)
     line_layout.update(
         {
-            "colorway": colorway[:item_count] if colorway else None,
-            "margin": {"l": 50, "r": 20, "t": 20, "b": 40},
-            "xaxis": {"gridcolor": "#EEEEEE", "automargin": True, "tickangle": -45},
-            "yaxis": {"tickformat": ".2%", "gridcolor": "#EEEEEE", "automargin": True},
-            "showlegend": False,
+            "colorway": colorway[:item_count] if colorway else None,
+            "margin": {"l": 50, "r": 20, "t": 20, "b": 40},
+            "xaxis": {"gridcolor": "#EEEEEE", "automargin": True, "tickangle": -45},
+            "yaxis": {"tickformat": ".2%", "gridcolor": "#EEEEEE", "automargin": True},
+            "showlegend": False,
         },
     )
 
     bar_layout = dict(layout_theme)
     bar_layout.update(
         {
-            "barmode": "group",
-            "margin": {"l": 50, "r": 20, "t": 10, "b": 80},
-            "xaxis": {"gridcolor": "#EEEEEE", "automargin": True, "tickangle": -45},
-            "yaxis": {"tickformat": ".2%", "gridcolor": "#EEEEEE", "automargin": True},
-            "showlegend": False,
+            "barmode": "group",
+            "margin": {"l": 50, "r": 20, "t": 10, "b": 80},
+            "xaxis": {"gridcolor": "#EEEEEE", "automargin": True, "tickangle": -45},
+            "yaxis": {"tickformat": ".2%", "gridcolor": "#EEEEEE", "automargin": True},
+            "showlegend": False,
         },
     )
 
@@ -487,47 +487,47 @@ 

Source code for openseries.report

 
 
 def _get_logo_html(logo: CaptorLogoType, *, add_logo: bool) -> str:
-    """Get logo HTML."""
+    """Get logo HTML."""
     if not add_logo:
-        return ""
+        return ""
     try:
-        src = cast("dict[str, Any]", logo).get("source", "")
+        src = cast("dict[str, Any]", logo).get("source", "")
     except (KeyError, AttributeError, TypeError):
-        src = ""
+        src = ""
     if src:
-        return f'<img src="{src}" alt="Captor" style="height:68px;" />'
-    return "CAPTOR"
+        return f'<img src="{src}" alt="Captor" style="height:68px;" />'
+    return "CAPTOR"
 
 
 def _get_legend_html(line_traces: list[Scatter], colorway: list[str]) -> str:
-    """Generate HTML for the legend at the bottom of the page."""
+    """Generate HTML for the legend at the bottom of the page."""
     legend_items = []
-    color_cycle = cycle(colorway or ["#66725B"])
+    color_cycle = cycle(colorway or ["#66725B"])
     for trace in line_traces:
-        name = trace.name or ""
+        name = trace.name or ""
         color = next(color_cycle)
         legend_items.append(
-            f'<div class="legend-item">'
-            f'<div class="legend-color" style="background-color:{color};"></div>'
-            f"<span>{name}</span>"
-            f"</div>",
+            f'<div class="legend-item">'
+            f'<div class="legend-color" style="background-color:{color};"></div>'
+            f"<span>{name}</span>"
+            f"</div>",
         )
     if legend_items:
-        return f'<div class="legend-container">{"".join(legend_items)}</div>'
-    return ""
+        return f'<div class="legend-container">{"".join(legend_items)}</div>'
+    return ""
 
 
 def _get_css() -> str:
-    """Get CSS styles for the HTML report."""
+    """Get CSS styles for the HTML report."""
     base_css = _get_base_css()
     return (
         base_css
-        + """
+        + """
     .header{display:grid;grid-template-columns:140px 1fr 140px;gap:12px;
     align-items:start;}
     h1{margin:0;text-align:center;font-size:45px;font-weight:800;}
     .layout{display:grid;grid-template-columns:1.2fr .9fr;
-    grid-template-areas:"charts table";gap:22px;align-items:start;margin-top:12px;}
+    grid-template-areas:"charts table";gap:22px;align-items:start;margin-top:12px;}
     .charts{grid-area:charts;display:grid;grid-template-rows:auto auto;gap:18px;}
     .table{grid-area:table;}
     .plot{width:100%;height:380px;}
@@ -536,7 +536,7 @@ 

Source code for openseries.report

       .page{padding:24px;padding-bottom:24px;}
       .header{grid-template-columns:120px 1fr;}
       h1{font-size:36px;}
-      .layout{grid-template-columns:1fr;grid-template-areas:"table" "charts";gap:16px;}
+      .layout{grid-template-columns:1fr;grid-template-areas:"table" "charts";gap:16px;}
       .plot{height:380px;}
       .plot.bar{height:300px;}
       table.metrics{table-layout:fixed;width:auto;}
@@ -563,7 +563,7 @@ 

Source code for openseries.report

     @media (min-width:981px){
       html,body{overflow-y:auto;}
     }
-    """
+    """
     )
 
 
@@ -573,14 +573,14 @@ 

Source code for openseries.report

     *,
     auto_open: bool,
 ) -> str:
-    """Write HTML file and optionally open it."""
+    """Write HTML file and optionally open it."""
     plotfile.parent.mkdir(parents=True, exist_ok=True)
-    plotfile.write_text(html, encoding="utf-8")
+    plotfile.write_text(html, encoding="utf-8")
     if auto_open:
         try:
             webbrowser_open(plotfile.as_uri())
         except OSError as exc:
-            logger.warning("Failed to open browser: %s", exc)
+            logger.warning("Failed to open browser: %s", exc)
     return str(plotfile)
 
 
@@ -594,63 +594,63 @@ 

Source code for openseries.report

     bar_payload: dict[str, Any],
     legend_html: str,
 ) -> str:
-    """Generate the HTML string."""
-    return f"""<!doctype html>
-<html lang="sv">
+    """Generate the HTML string."""
+    return f"""<!doctype html>
+<html lang="sv">
 <head>
-<meta charset="utf-8" />
-<meta name="viewport" content="width=device-width,initial-scale=1" />
-<title>{title or ""}</title>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width,initial-scale=1" />
+<title>{title or ""}</title>
 <style>{css}</style>
 {plotly_script}
 </head>
 <body>
-<div class="page">
-  <div class="header">
+<div class="page">
+  <div class="header">
     <div>{logo_html}</div>
-    <div><h1>{title or ""}</h1></div>
+    <div><h1>{title or ""}</h1></div>
     <div></div>
   </div>
-  <div class="layout">
-    <div class="charts">
-      <div id="lineplot" class="plot"></div>
-      <div id="barplot" class="plot bar"></div>
+  <div class="layout">
+    <div class="charts">
+      <div id="lineplot" class="plot"></div>
+      <div id="barplot" class="plot bar"></div>
     </div>
-    <div class="table">{table_html}</div>
+    <div class="table">{table_html}</div>
   </div>
   {legend_html}
 </div>
 <script>
 const line = {_dumps_plotly(line_payload)};
 const bar = {_dumps_plotly(bar_payload)};
-Plotly.newPlot("lineplot", line.data, line.layout, line.config);
-Plotly.newPlot("barplot", bar.data, bar.layout, bar.config);
-window.addEventListener("resize", () => {{
-  Plotly.Plots.resize("lineplot");
-  Plotly.Plots.resize("barplot");
+Plotly.newPlot("lineplot", line.data, line.layout, line.config);
+Plotly.newPlot("barplot", bar.data, bar.layout, bar.config);
+window.addEventListener("resize", () => {{
+  Plotly.Plots.resize("lineplot");
+  Plotly.Plots.resize("barplot");
 }});
 </script>
 </body>
 </html>
-"""
+"""
 
 
 
[docs] def report_html( data: OpenFrame, - bar_freq: LiteralBizDayFreq = "BYE", + bar_freq: LiteralBizDayFreq = "BYE", filename: str | None = None, title: str | None = None, directory: Path | None = None, - output_type: LiteralPlotlyOutput = "file", - include_plotlyjs: LiteralPlotlyJSlib = "cdn", + output_type: LiteralPlotlyOutput = "file", + include_plotlyjs: LiteralPlotlyJSlib = "cdn", *, auto_open: bool = False, add_logo: bool = True, vertical_legend: bool = True, ) -> tuple[Figure, str]: - """Generate a responsive HTML report page with line and bar plots and a table.""" + """Generate a responsive HTML report page with line and bar plots and a table.""" copied = data.from_deepcopy() copied.trunc_frame().value_nan_handle().to_cumret() @@ -662,7 +662,7 @@

Source code for openseries.report

     bar_traces = _create_bar_traces(copied, bar_freq)
 
     rpt_df = copied.all_properties(
-        properties=cast("list[LiteralFrameProps]", properties),
+        properties=cast("list[LiteralFrameProps]", properties),
     )
     rpt_df = _add_jensen_alpha(rpt_df, copied)
     rpt_df = _add_information_ratio(rpt_df, copied)
@@ -677,64 +677,64 @@ 

Source code for openseries.report

     rpt_df = rpt_df.reindex(labels_final)
 
     format_map = {
-        "Return (CAGR)": "{:.2%}",
-        "Return (simple)": "{:.2%}",
-        "Year-to-Date": "{:.2%}",
-        "Month-to-Date": "{:.2%}",
-        "Volatility": "{:.2%}",
-        "Sharpe Ratio": "{:.2f}",
-        "Sortino Ratio": "{:.2f}",
-        "Jensen's Alpha": "{:.2%}",
-        "Information Ratio": "{:.2f}",
-        "Tracking Error (weekly)": "{:.2%}",
-        "Index Beta (weekly)": "{:.2f}",
-        "Capture Ratio (monthly)": "{:.2f}",
-        "Worst Month": "{:.2%}",
-        "Worst Day": "{:.2%}",
-        "Comparison Start": "{:%Y-%m-%d}",
-        "Comparison End": "{:%Y-%m-%d}",
+        "Return (CAGR)": "{:.2%}",
+        "Return (simple)": "{:.2%}",
+        "Year-to-Date": "{:.2%}",
+        "Month-to-Date": "{:.2%}",
+        "Volatility": "{:.2%}",
+        "Sharpe Ratio": "{:.2f}",
+        "Sortino Ratio": "{:.2f}",
+        "Jensen's Alpha": "{:.2%}",
+        "Information Ratio": "{:.2f}",
+        "Tracking Error (weekly)": "{:.2%}",
+        "Index Beta (weekly)": "{:.2f}",
+        "Capture Ratio (monthly)": "{:.2f}",
+        "Worst Month": "{:.2%}",
+        "Worst Day": "{:.2%}",
+        "Comparison Start": "{:%Y-%m-%d}",
+        "Comparison End": "{:%Y-%m-%d}",
     }
-    formats = [format_map.get(label, "{:.2f}") for label in labels_final]
+    formats = [format_map.get(label, "{:.2f}") for label in labels_final]
 
     for item, f in zip(rpt_df.index, formats, strict=False):
         rpt_df.loc[item] = rpt_df.loc[item].apply(
             lambda x, fmt=f: (
-                ""
+                ""
                 if (
                     x is None
                     or (not isinstance(x, str) and isna(x))
-                    or (isinstance(x, str) and x.lower() in ("nan", "nan%", ""))
+                    or (isinstance(x, str) and x.lower() in ("nan", "nan%", ""))
                 )
                 else (
                     str(x)
                     if isinstance(x, str)
                     else (
-                        Timestamp(x).strftime("%Y-%m-%d")
-                        if "%Y-%m-%d" in fmt and not isinstance(x, str)
+                        Timestamp(x).strftime("%Y-%m-%d")
+                        if "%Y-%m-%d" in fmt and not isinstance(x, str)
                         else fmt.format(x)
                     )
                 )
             ),
         )
 
-    rpt_df.index = Index([f"<b>{x}</b>" for x in rpt_df.index])
+    rpt_df.index = Index([f"<b>{x}</b>" for x in rpt_df.index])
     rpt_df = rpt_df.reset_index()
 
-    colmns = ["", *copied.columns_lvl_zero]
+    colmns = ["", *copied.columns_lvl_zero]
     rpt_df.columns = colmns
     table_html = _metrics_table_html(rpt_df)
 
     dirpath = _get_output_directory(directory=directory)
 
     if not filename:
-        filename = "".join(choice(ascii_letters) for _ in range(6)) + ".html"
+        filename = "".join(choice(ascii_letters) for _ in range(6)) + ".html"
 
     plotfile = dirpath / filename
 
     fig_theme, logo = load_plotly_dict()
-    layout_theme = cast("dict[str, Any]", fig_theme.get("layout", {}))
-    colorway: list[str] = cast("dict[str, list[str]]", layout_theme).get(
-        "colorway", []
+    layout_theme = cast("dict[str, Any]", fig_theme.get("layout", {}))
+    colorway: list[str] = cast("dict[str, list[str]]", layout_theme).get(
+        "colorway", []
     )
 
     line_layout, bar_layout = _get_plotly_layouts(
@@ -743,26 +743,26 @@ 

Source code for openseries.report

         item_count=copied.item_count,
     )
 
-    config = cast("dict[str, Any]", fig_theme.get("config", {})) or {}
-    config = {**config, "responsive": True, "displayModeBar": False}
+    config = cast("dict[str, Any]", fig_theme.get("config", {})) or {}
+    config = {**config, "responsive": True, "displayModeBar": False}
 
     plotly_script = _get_plotly_script(include_plotlyjs=include_plotlyjs)
     logo_html = _get_logo_html(logo=logo, add_logo=add_logo)
     css = _get_css()
 
     line_payload = {
-        "data": [t.to_plotly_json() for t in line_traces],
-        "layout": line_layout,
-        "config": config,
+        "data": [t.to_plotly_json() for t in line_traces],
+        "layout": line_layout,
+        "config": config,
     }
     bar_payload = {
-        "data": [t.to_plotly_json() for t in bar_traces],
-        "layout": bar_layout,
-        "config": config,
+        "data": [t.to_plotly_json() for t in bar_traces],
+        "layout": bar_layout,
+        "config": config,
     }
 
     if not vertical_legend:
-        logger.debug("Horizontal legend layout requested.")
+        logger.debug("Horizontal legend layout requested.")
     legend_html = _get_legend_html(line_traces=line_traces, colorway=colorway)
 
     html = _generate_html(
@@ -776,7 +776,7 @@ 

Source code for openseries.report

         legend_html=legend_html,
     )
 
-    if output_type == "file":
+    if output_type == "file":
         output = _write_html_file(plotfile=plotfile, html=html, auto_open=auto_open)
     else:
         output = html
diff --git a/docs/build/html/_modules/openseries/series.html b/docs/build/html/_modules/openseries/series.html
index dffa6883..6b070b91 100644
--- a/docs/build/html/_modules/openseries/series.html
+++ b/docs/build/html/_modules/openseries/series.html
@@ -108,7 +108,7 @@
            

Source code for openseries.series

-"""The OpenTimeSeries class."""
+"""The OpenTimeSeries class."""
 
 from __future__ import annotations
 
@@ -172,15 +172,15 @@ 

Source code for openseries.series

 
 logger = getLogger(__name__)
 
-__all__ = ["OpenTimeSeries", "timeseries_chain"]
+__all__ = ["OpenTimeSeries", "timeseries_chain"]
 
-TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
+TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
 
 
 
[docs] class OpenTimeSeries(_CommonModel[float]): - """OpenTimeSeries objects are at the core of the openseries package. + """OpenTimeSeries objects are at the core of the openseries package. The intended use is to allow analyses of financial timeseries. It is only intended for daily or less frequent data samples. @@ -199,15 +199,15 @@

Source code for openseries.series

         tsdf: Pandas object holding dates and values that can be altered via
             methods.
         currency: ISO 4217 currency code of the timeseries.
-        domestic: ISO 4217 currency code of the user's home currency.
-            Defaults to "SEK".
+        domestic: ISO 4217 currency code of the user's home currency.
+            Defaults to "SEK".
         countries: (List of) country code(s) according to ISO 3166-1 alpha-2.
-            Defaults to "SE".
+            Defaults to "SE".
         markets: (List of) markets code(s) supported by exchange_calendars.
             Optional.
         isin: ISO 6166 identifier code of the associated instrument. Optional.
         label: Placeholder for a name of the timeseries. Optional.
-    """
+    """
 
     timeseries_id: str
     instrument_id: str
@@ -218,70 +218,70 @@ 

Source code for openseries.series

     local_ccy: bool
     tsdf: DataFrame
     currency: CurrencyStringType
-    domestic: CurrencyStringType = "SEK"
-    countries: CountriesType = "SE"
+    domestic: CurrencyStringType = "SEK"
+    countries: CountriesType = "SE"
     isin: str | None = None
     label: str | None = None
 
-    @field_validator("domestic", mode="before")
+    @field_validator("domestic", mode="before")
     @classmethod
     def _validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType:
-        """Pydantic validator to ensure domestic field is validated."""
+        """Pydantic validator to ensure domestic field is validated."""
         Currency(ccy=value)
         return value
 
-    @field_validator("countries", mode="before")
+    @field_validator("countries", mode="before")
     @classmethod
     def _validate_countries(cls, value: CountriesType) -> CountriesType:
-        """Pydantic validator to ensure countries field is validated."""
+        """Pydantic validator to ensure countries field is validated."""
         Countries(countryinput=value)
         return value
 
-    @field_validator("markets", mode="before")
+    @field_validator("markets", mode="before")
     @classmethod
     def _validate_markets(
         cls,
         value: list[str] | str | None,
     ) -> list[str] | str | None:
-        """Pydantic validator to ensure markets field is validated.
+        """Pydantic validator to ensure markets field is validated.
 
         Raises:
             MarketsNotStringNorListStrError: If ``markets`` is neither a string
                 nor a non-empty list of strings.
-        """
+        """
         msg = (
-            "'markets' must be a string or list of strings, "
-            f"got {type(value).__name__!r}"
+            "'markets' must be a string or list of strings, "
+            f"got {type(value).__name__!r}"
         )
         if value is None or isinstance(value, str):
             return value
         if isinstance(value, list):
             if all(isinstance(item, str) for item in value) and len(value) != 0:
                 return value
-            item_msg = "All items in 'markets' must be strings."
+            item_msg = "All items in 'markets' must be strings."
             raise MarketsNotStringNorListStrError(item_msg)
         raise MarketsNotStringNorListStrError(msg)
 
-    @model_validator(mode="after")
+    @model_validator(mode="after")
     def _dates_and_values_validate(self: Self) -> Self:
-        """Pydantic validator to ensure dates and values are validated.
+        """Pydantic validator to ensure dates and values are validated.
 
         Raises:
             ValueError: If dates are not unique or if numbers of dates and values
                 do not match the shape of ``tsdf``.
-        """
+        """
         values_list_length = len(self.values)
         dates_list_length = len(self.dates)
         dates_set_length = len(set(self.dates))
         if dates_list_length != dates_set_length:
-            msg = "Dates are not unique"
+            msg = "Dates are not unique"
             raise ValueError(msg)
         if (
             (dates_list_length != values_list_length)
             or (len(self.tsdf.index) != self.tsdf.shape[0])
             or (self.tsdf.shape[1] != 1)
         ):
-            msg = "Number of dates and values passed do not match"
+            msg = "Number of dates and values passed do not match"
             raise ValueError(msg)
         return self
 
@@ -302,14 +302,14 @@ 

Source code for openseries.series

         dates: DateListType,
         values: ValueListType,
         valuetype: ValueType = ValueType.PRICE,
-        timeseries_id: str = "",
-        instrument_id: str = "",
+        timeseries_id: str = "",
+        instrument_id: str = "",
         isin: str | None = None,
-        baseccy: CurrencyStringType = "SEK",
+        baseccy: CurrencyStringType = "SEK",
         *,
         local_ccy: bool = True,
     ) -> Self:
-        """Create series from a list of dates and a list of values.
+        """Create series from a list of dates and a list of values.
 
         Args:
             name: String identifier of the timeseries and/or instrument.
@@ -321,13 +321,13 @@ 

Source code for openseries.series

             instrument_id: Database identifier of the instrument associated
                 with the timeseries. Optional.
             isin: ISO 6166 identifier code of the associated instrument. Optional.
-            baseccy: ISO 4217 currency code of the timeseries. Defaults to "SEK".
+            baseccy: ISO 4217 currency code of the timeseries. Defaults to "SEK".
             local_ccy: Boolean flag indicating if timeseries is in local currency.
                 Defaults to True.
 
         Returns:
             An OpenTimeSeries object.
-        """
+        """
         return cls(
             name=name,
             label=name,
@@ -343,7 +343,7 @@ 

Source code for openseries.series

                 data=values,
                 index=[deyt.date() for deyt in DatetimeIndex(dates)],
                 columns=[[name], [valuetype]],
-                dtype="float64",
+                dtype="float64",
             ),
         )
@@ -356,18 +356,18 @@

Source code for openseries.series

         dframe: Series | DataFrame | object,
         column_nmbr: int = 0,
         valuetype: ValueType = ValueType.PRICE,
-        baseccy: CurrencyStringType = "SEK",
+        baseccy: CurrencyStringType = "SEK",
         *,
         local_ccy: bool = True,
     ) -> Self:
-        """Create series from a Pandas DataFrame or Series.
+        """Create series from a Pandas DataFrame or Series.
 
         Args:
             dframe: Pandas DataFrame or Series.
             column_nmbr: Using iloc[:, column_nmbr] to pick column. Defaults to 0.
             valuetype: Identifies if the series is a series of values or returns.
                 Defaults to ValueType.PRICE.
-            baseccy: ISO 4217 currency code of the timeseries. Defaults to "SEK".
+            baseccy: ISO 4217 currency code of the timeseries. Defaults to "SEK".
             local_ccy: Boolean flag indicating if timeseries is in local currency.
                 Defaults to True.
 
@@ -377,8 +377,8 @@ 

Source code for openseries.series

         Raises:
             TypeError: If ``dframe`` is not a ``pandas.Series`` or a
                 ``pandas.DataFrame``.
-        """
-        msg = "Argument dframe must be pandas Series or DataFrame."
+        """
+        msg = "Argument dframe must be pandas Series or DataFrame."
         values: list[float]
         pandas_obj: Series | DataFrame
         if isinstance(dframe, Series):
@@ -395,8 +395,8 @@ 

Source code for openseries.series

                 if _check_if_none(
                     dframe.columns.get_level_values(0).to_numpy()[column_nmbr],
                 ):
-                    label = "Series"
-                    msg = f"Label missing. Adding: {label}"
+                    label = "Series"
+                    msg = f"Label missing. Adding: {label}"
                     logger.warning(msg)
                 else:
                     label = dframe.columns.get_level_values(0).to_numpy()[column_nmbr]
@@ -404,7 +404,7 @@ 

Source code for openseries.series

                     dframe.columns.get_level_values(1).to_numpy()[column_nmbr],
                 ):
                     valuetype = ValueType.PRICE
-                    msg = f"valuetype missing. Adding: {valuetype.value}"
+                    msg = f"valuetype missing. Adding: {valuetype.value}"
                     logger.warning(msg)
                 else:
                     valuetype = dframe.columns.get_level_values(1).to_numpy()[
@@ -415,11 +415,11 @@ 

Source code for openseries.series

         else:
             raise TypeError(msg)
 
-        dates = [date_fix(d).strftime("%Y-%m-%d") for d in pandas_obj.index]
+        dates = [date_fix(d).strftime("%Y-%m-%d") for d in pandas_obj.index]
 
         return cls(
-            timeseries_id="",
-            instrument_id="",
+            timeseries_id="",
+            instrument_id="",
             currency=baseccy,
             dates=dates,
             name=label,
@@ -431,7 +431,7 @@ 

Source code for openseries.series

                 data=values,
                 index=[deyt.date() for deyt in DatetimeIndex(dates)],
                 columns=[[label], [valuetype]],
-                dtype="float64",
+                dtype="float64",
             ),
         )
@@ -445,13 +445,13 @@

Source code for openseries.series

         d_range: DatetimeIndex | None = None,
         days: int | None = None,
         end_dt: dt.date | None = None,
-        label: str = "Series",
+        label: str = "Series",
         valuetype: ValueType = ValueType.PRICE,
-        baseccy: CurrencyStringType = "SEK",
+        baseccy: CurrencyStringType = "SEK",
         *,
         local_ccy: bool = True,
     ) -> Self:
-        """Create series from values accruing with a given fixed rate return.
+        """Create series from values accruing with a given fixed rate return.
 
         Providing a date_range of type Pandas DatetimeIndex takes priority over
         providing a combination of days and an end date.
@@ -466,7 +466,7 @@ 

Source code for openseries.series

             label: Placeholder for a name of the timeseries.
             valuetype: Identifies if the series is a series of values or returns.
                 Defaults to ValueType.PRICE.
-            baseccy: The currency of the timeseries. Defaults to "SEK".
+            baseccy: The currency of the timeseries. Defaults to "SEK".
             local_ccy: Boolean flag indicating if timeseries is in local currency.
                 Defaults to True.
 
@@ -476,22 +476,22 @@ 

Source code for openseries.series

         Raises:
             IncorrectArgumentComboError: If ``d_range`` is not provided and the
                 combination of ``days`` and ``end_dt`` is incomplete.
-        """
+        """
         if d_range is None:
             if days is not None and end_dt is not None:
                 d_range = DatetimeIndex(
-                    [d.date() for d in date_range(periods=days, end=end_dt, freq="D")],
+                    [d.date() for d in date_range(periods=days, end=end_dt, freq="D")],
                 )
             else:
-                msg = "If d_range is not provided both days and end_dt must be."
+                msg = "If d_range is not provided both days and end_dt must be."
                 raise IncorrectArgumentComboError(msg)
         deltas = array([i.days for i in d_range[1:] - d_range[:-1]])
         arr: list[float] = list(cumprod(insert(1 + deltas * rate / 365, 0, 1.0)))
-        dates = [d.strftime("%Y-%m-%d") for d in d_range]
+        dates = [d.strftime("%Y-%m-%d") for d in d_range]
 
         return cls(
-            timeseries_id="",
-            instrument_id="",
+            timeseries_id="",
+            instrument_id="",
             currency=baseccy,
             dates=dates,
             name=label,
@@ -503,7 +503,7 @@ 

Source code for openseries.series

                 data=arr,
                 index=[d.date() for d in DatetimeIndex(dates)],
                 columns=[[label], [valuetype]],
-                dtype="float64",
+                dtype="float64",
             ),
         )
@@ -511,27 +511,27 @@

Source code for openseries.series

 
[docs] def from_deepcopy(self: Self) -> Self: - """Create copy of OpenTimeSeries object. + """Create copy of OpenTimeSeries object. Returns: An OpenTimeSeries object. - """ + """ return deepcopy(self)
[docs] def pandas_df(self: Self) -> Self: - """Populate .tsdf Pandas DataFrame from the .dates and .values lists. + """Populate .tsdf Pandas DataFrame from the .dates and .values lists. Returns: An OpenTimeSeries object. - """ + """ dframe = DataFrame( data=self.values, index=[d.date() for d in DatetimeIndex(self.dates)], columns=[[self.label], [self.valuetype]], - dtype="float64", + dtype="float64", ) self.tsdf = dframe @@ -544,7 +544,7 @@

Source code for openseries.series

         self: Self,
         properties: list[LiteralSeriesProps] | None = None,
     ) -> DataFrame:
-        """Calculate chosen properties.
+        """Calculate chosen properties.
 
         Args:
             properties: The properties to calculate. Defaults to calculating all
@@ -552,10 +552,10 @@ 

Source code for openseries.series

 
         Returns:
             Properties of the OpenTimeSeries.
-        """
+        """
         if not properties:
             properties = cast(
-                "list[LiteralSeriesProps]",
+                "list[LiteralSeriesProps]",
                 OpenTimeSeriesPropertiesList.allowed_strings,
             )
 
@@ -564,13 +564,13 @@ 

Source code for openseries.series

         def _prop_value(name: str) -> float | int | dt.date | Series[float]:
             attr = getattr(self, name)
             return cast(
-                "float | int | dt.date | Series[float]",
+                "float | int | dt.date | Series[float]",
                 attr() if callable(attr) else attr,
             )
 
         pdf = DataFrame.from_dict(
             {x: _prop_value(x) for x in props},
-            orient="index",
+            orient="index",
         )
         pdf.columns = self.tsdf.columns
         return pdf
@@ -579,15 +579,15 @@

Source code for openseries.series

 
[docs] def value_to_ret(self: Self) -> Self: - """Convert series of values into series of returns. + """Convert series of values into series of returns. Returns: The returns of the values in the series. - """ + """ returns = self.tsdf.ffill().pct_change() returns.iloc[0] = 0 self.valuetype = ValueType.RTRN - arrays = cast("Any", [[self.label], [self.valuetype]]) + arrays = cast("Any", [[self.label], [self.valuetype]]) returns.columns = MultiIndex.from_arrays(arrays) self.tsdf = returns.copy() return self
@@ -596,7 +596,7 @@

Source code for openseries.series

 
[docs] def value_to_diff(self: Self, periods: int = 1) -> Self: - """Convert series of values to series of their period differences. + """Convert series of values to series of their period differences. Args: periods: The number of periods between observations over which difference @@ -604,7 +604,7 @@

Source code for openseries.series

 
         Returns:
             An OpenTimeSeries object.
-        """
+        """
         self.tsdf = self.tsdf.diff(periods=periods)
         self.tsdf.iloc[0] = 0
         self.valuetype = ValueType.RTRN
@@ -620,11 +620,11 @@ 

Source code for openseries.series

 
[docs] def to_cumret(self: Self) -> Self: - """Convert series of returns into cumulative series of values. + """Convert series of returns into cumulative series of values. Returns: An OpenTimeSeries object. - """ + """ if self.valuetype == ValueType.PRICE: self.value_to_ret() @@ -648,7 +648,7 @@

Source code for openseries.series

         days_in_year: int = 365,
         divider: float = 1.0,
     ) -> Self:
-        """Convert series of 1-day rates into series of cumulative values.
+        """Convert series of 1-day rates into series of cumulative values.
 
         Args:
             days_in_year: Calendar days per year used as divisor. Defaults to 365.
@@ -657,12 +657,12 @@ 

Source code for openseries.series

 
         Returns:
             An OpenTimeSeries object.
-        """
+        """
         arr: NDArray[float64] = array(self.values) / divider
 
         deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]])
         arr = cast(
-            "NDArray[float64]",
+            "NDArray[float64]",
             cumprod(
                 a=insert(
                     arr=1.0 + deltas * arr[:-1] / days_in_year, obj=0, values=1.0
@@ -670,14 +670,14 @@ 

Source code for openseries.series

             ),
         )
 
-        self.dates = [d.strftime("%Y-%m-%d") for d in self.tsdf.index]
+        self.dates = [d.strftime("%Y-%m-%d") for d in self.tsdf.index]
         self.values = list(arr)
         self.valuetype = ValueType.PRICE
         self.tsdf = DataFrame(
             data=self.values,
             index=[d.date() for d in DatetimeIndex(self.dates)],
             columns=[[self.label], [self.valuetype]],
-            dtype="float64",
+            dtype="float64",
         )
 
         return self
@@ -687,17 +687,17 @@

Source code for openseries.series

 [docs]
     def resample(
         self: Self,
-        freq: LiteralBizDayFreq | str = "BME",
+        freq: LiteralBizDayFreq | str = "BME",
     ) -> Self:
-        """Resamples the timeseries frequency.
+        """Resamples the timeseries frequency.
 
         Args:
             freq: The date offset string that sets the resampled frequency.
-                Defaults to "BME".
+                Defaults to "BME".
 
         Returns:
             An OpenTimeSeries object.
-        """
+        """
         self.tsdf.index = DatetimeIndex(self.tsdf.index)
         if self.valuetype == ValueType.RTRN:
             self.tsdf = self.tsdf.resample(freq).sum()
@@ -711,10 +711,10 @@ 

Source code for openseries.series

 [docs]
     def resample_to_business_period_ends(
         self: Self,
-        freq: LiteralBizDayFreq = "BME",
-        method: LiteralPandasReindexMethod = "nearest",
+        freq: LiteralBizDayFreq = "BME",
+        method: LiteralPandasReindexMethod = "nearest",
     ) -> Self:
-        """Resamples timeseries frequency to the business calendar month end dates.
+        """Resamples timeseries frequency to the business calendar month end dates.
 
         Stubs left in place. Stubs will be aligned to the shortest stub.
 
@@ -731,12 +731,12 @@ 

Source code for openseries.series

             ResampleDataLossError: If called on a return series (``valuetype`` is
                 ``ValueType.RTRN``), since summation across sparser frequency would
                 be required to avoid data loss.
-        """
+        """
         if self.valuetype == ValueType.RTRN:
             msg = (
-                "Do not run resample_to_business_period_ends on return series. "
-                "The operation will pick the last data point in the sparser series. "
-                "It will not sum returns and therefore data will be lost."
+                "Do not run resample_to_business_period_ends on return series. "
+                "The operation will pick the last data point in the sparser series. "
+                "It will not sum returns and therefore data will be lost."
             )
             raise ResampleDataLossError(msg)
 
@@ -762,7 +762,7 @@ 

Source code for openseries.series

         to_date: dt.date | None = None,
         periods_in_a_year_fixed: DaysInYearType | None = None,
     ) -> Series[float]:
-        """Exponentially Weighted Moving Average Model for Volatility.
+        """Exponentially Weighted Moving Average Model for Volatility.
 
         Reference: https://www.investopedia.com/articles/07/ewma.asp.
 
@@ -781,7 +781,7 @@ 

Source code for openseries.series

 
         Returns:
             Series EWMA volatility.
-        """
+        """
         earlier, later = self.calc_range(
             months_offset=months_from_last,
             from_dt=from_date,
@@ -789,7 +789,7 @@ 

Source code for openseries.series

         )
         time_factor = _calculate_time_factor(
             data=self.tsdf.loc[
-                cast("Timestamp", earlier) : cast("Timestamp", later)
+                cast("Timestamp", earlier) : cast("Timestamp", later)
             ].iloc[:, 0],
             earlier=earlier,
             later=later,
@@ -797,7 +797,7 @@ 

Source code for openseries.series

         )
 
         data = self.tsdf.loc[
-            cast("Timestamp", earlier) : cast("Timestamp", later)
+            cast("Timestamp", earlier) : cast("Timestamp", later)
         ].copy()
 
         data.loc[:, (self.label, ValueType.RTRN)] = log(
@@ -823,7 +823,7 @@ 

Source code for openseries.series

             data=rawdata,
             index=data.index,
             name=(self.label, ValueType.EWMA_VOL),
-            dtype="float64",
+            dtype="float64",
         )
@@ -840,7 +840,7 @@

Source code for openseries.series

         to_date: dt.date | None = None,
         periods_in_a_year_fixed: DaysInYearType | None = None,
     ) -> Series[float]:
-        """Exponentially Weighted Moving Average Model for Value At Risk (VaR).
+        """Exponentially Weighted Moving Average Model for Value At Risk (VaR).
 
         Reference: https://www.investopedia.com/articles/07/ewma.asp.
 
@@ -860,7 +860,7 @@ 

Source code for openseries.series

 
         Returns:
             Series EWMA VaR.
-        """
+        """
         earlier, later = self.calc_range(
             months_offset=months_from_last,
             from_dt=from_date,
@@ -868,7 +868,7 @@ 

Source code for openseries.series

         )
         time_factor = _calculate_time_factor(
             data=self.tsdf.loc[
-                cast("Timestamp", earlier) : cast("Timestamp", later)
+                cast("Timestamp", earlier) : cast("Timestamp", later)
             ].iloc[:, 0],
             earlier=earlier,
             later=later,
@@ -876,7 +876,7 @@ 

Source code for openseries.series

         )
 
         data = self.tsdf.loc[
-            cast("Timestamp", earlier) : cast("Timestamp", later)
+            cast("Timestamp", earlier) : cast("Timestamp", later)
         ].copy()
 
         data.loc[:, (self.label, ValueType.RTRN)] = log(
@@ -902,7 +902,7 @@ 

Source code for openseries.series

             data=array(rawdata) * norm.ppf(1 - level),
             index=data.index,
             name=(self.label, ValueType.EWMA_VAR),
-            dtype="float64",
+            dtype="float64",
         )
@@ -913,7 +913,7 @@

Source code for openseries.series

         adjustment: float,
         days_in_year: int = 365,
     ) -> Self:
-        """Add or subtract a fee from the timeseries return.
+        """Add or subtract a fee from the timeseries return.
 
         Args:
             adjustment: Fee to add or subtract.
@@ -922,13 +922,13 @@ 

Source code for openseries.series

 
         Returns:
             An OpenTimeSeries object.
-        """
+        """
         if self.valuetype == ValueType.RTRN:
             ra_df = self.tsdf.copy()
             initial_value = 1.0
             returns_input = True
         else:
-            initial_value = cast("float", self.tsdf.iloc[0, 0])
+            initial_value = cast("float", self.tsdf.iloc[0, 0])
             ra_df = self.tsdf.ffill().pct_change()
             returns_input = False
         ra_df = ra_df.dropna()
@@ -938,15 +938,15 @@ 

Source code for openseries.series

 
         dates_np = array(
             [dt.datetime.combine(d, dt.time()) for d in dates_list],
-            dtype="datetime64[D]",
+            dtype="datetime64[D]",
         )
         date_diffs = cast(
-            "NDArray[float64]",
-            diff(dates_np).astype("timedelta64[D]").astype(float64),
+            "NDArray[float64]",
+            diff(dates_np).astype("timedelta64[D]").astype(float64),
         )
 
         returns_array = cast(
-            "NDArray[float64]",
+            "NDArray[float64]",
             ra_df.iloc[:, 0].to_numpy(),
         )
 
@@ -980,7 +980,7 @@ 

Source code for openseries.series

         *,
         delete_lvl_one: bool = False,
     ) -> Self:
-        """Set the column labels of the .tsdf Pandas Dataframe.
+        """Set the column labels of the .tsdf Pandas Dataframe.
 
         Args:
             lvl_zero: New level zero label. Optional.
@@ -989,7 +989,7 @@ 

Source code for openseries.series

 
         Returns:
             An OpenTimeSeries object.
-        """
+        """
         if lvl_zero is None and lvl_one is None:
             self.tsdf.columns = MultiIndex.from_arrays(
                 [[self.label], [self.valuetype]],
@@ -1002,14 +1002,14 @@ 

Source code for openseries.series

             self.valuetype = lvl_one
         else:
             self.tsdf.columns = MultiIndex.from_arrays([[lvl_zero], [lvl_one]])
-            self.label, self.valuetype = lvl_zero, cast("ValueType", lvl_one)
+            self.label, self.valuetype = lvl_zero, cast("ValueType", lvl_one)
         if delete_lvl_one:
             self.tsdf.columns = self.tsdf.columns.get_level_values(0)
         return self
def _returns_series(self: Self, *, squared: bool = False) -> Series[float]: - """Return demeaned return series for autocorrelation analysis.""" + """Return demeaned return series for autocorrelation analysis.""" data: Series[float] = self.tsdf.iloc[:, 0] return _demeaned_returns_for_autocorr( series=data, valuetype=self.valuetype, squared=squared @@ -1023,7 +1023,7 @@

Source code for openseries.series

         *,
         squared: bool = False,
     ) -> Series[float]:
-        """Calculate autocorrelation function for specified lags.
+        """Calculate autocorrelation function for specified lags.
 
         Args:
             lags: If int, compute ACF from lag 0 to this value (inclusive).
@@ -1032,7 +1032,7 @@ 

Source code for openseries.series

 
         Returns:
             Series of autocorrelations indexed by lag.
-        """
+        """
         rets = self._returns_series(squared=squared)
         if isinstance(lags, int):
             lag_list = list(range(lags + 1))
@@ -1047,15 +1047,15 @@ 

Source code for openseries.series

         return Series(
             data=values,
             index=lag_list,
-            name="ACF",
-            dtype="float64",
+            name="ACF",
+            dtype="float64",
         )
[docs] def partial_autocorr(self: Self, lag: int = 1, *, squared: bool = False) -> float: - """Calculate partial autocorrelation at a given lag. + """Calculate partial autocorrelation at a given lag. Args: lag: The lag at which to compute partial autocorrelation. Defaults to 1. @@ -1064,7 +1064,7 @@

Source code for openseries.series

 
         Returns:
             Partial autocorrelation at the specified lag.
-        """
+        """
         pacf_series = self.pacf(lags=lag, squared=squared)
         return float(pacf_series.loc[lag])
@@ -1077,7 +1077,7 @@

Source code for openseries.series

         *,
         squared: bool = False,
     ) -> Series[float]:
-        """Calculate partial autocorrelation function for specified lags.
+        """Calculate partial autocorrelation function for specified lags.
 
         Args:
             lags: If int, compute PACF from lag 0 to this value (inclusive).
@@ -1086,7 +1086,7 @@ 

Source code for openseries.series

 
         Returns:
             Series of partial autocorrelations indexed by lag.
-        """
+        """
         if isinstance(lags, int):
             lag_list = list(range(lags + 1))
         else:
@@ -1116,8 +1116,8 @@ 

Source code for openseries.series

         return Series(
             data=[result[lag] for lag in lag_list],
             index=lag_list,
-            name="PACF",
-            dtype="float64",
+            name="PACF",
+            dtype="float64",
         )
@@ -1129,7 +1129,7 @@

Source code for openseries.series

         *,
         squared: bool = False,
     ) -> tuple[float, float, list[int]]:
-        """Compute Ljung-Box test for autocorrelation.
+        """Compute Ljung-Box test for autocorrelation.
 
         Args:
             lags: If int, use lags 1 through this value. If list, use the given
@@ -1141,7 +1141,7 @@ 

Source code for openseries.series

             Tuple of (statistic, pvalue, lags) where statistic is the Ljung-Box
             Q statistic, pvalue is the chi-squared p-value, and lags is the
             list of lags used.
-        """
+        """
         rets = self._returns_series(squared=squared)
         n = len(rets)
         if isinstance(lags, int):
@@ -1170,7 +1170,7 @@ 

Source code for openseries.series

     back: TypeOpenTimeSeries,
     old_fee: float = 0.0,
 ) -> TypeOpenTimeSeries:
-    """Chain two timeseries together.
+    """Chain two timeseries together.
 
     The function assumes that the two series have at least one date in common.
 
@@ -1181,7 +1181,7 @@ 

Source code for openseries.series

 
     Returns:
         An OpenTimeSeries object or a subclass thereof.
-    """
+    """
     old = front.from_deepcopy()
     old.running_adjustment(old_fee)
     new = back.from_deepcopy()
@@ -1189,17 +1189,17 @@ 

Source code for openseries.series

     first = new.tsdf.index[idx]
 
     if old.last_idx < first:
-        msg = "Timeseries dates must overlap to allow them to be chained."
+        msg = "Timeseries dates must overlap to allow them to be chained."
         raise DateAlignmentError(msg)
 
     while first not in old.tsdf.index:
         idx += 1
         first = new.tsdf.index[idx]
         if first > old.tsdf.index[-1]:
-            msg = "Failed to find a matching date between series"
+            msg = "Failed to find a matching date between series"
             raise DateAlignmentError(msg)
 
-    dates: list[str] = [x.strftime("%Y-%m-%d") for x in old.tsdf.index if x < first]
+    dates: list[str] = [x.strftime("%Y-%m-%d") for x in old.tsdf.index if x < first]
 
     old_values = Series(old.tsdf.iloc[: len(dates), 0])
     old_values = old_values.mul(
@@ -1208,7 +1208,7 @@ 

Source code for openseries.series

     )
     values = append(old_values, new.tsdf.iloc[:, 0])
 
-    dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
+    dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
 
     return back.__class__(
         timeseries_id=new.timeseries_id,
@@ -1224,26 +1224,26 @@ 

Source code for openseries.series

             data=values,
             index=[d.date() for d in DatetimeIndex(dates)],
             columns=[[new.label], [new.valuetype]],
-            dtype="float64",
+            dtype="float64",
         ),
     )
def _check_if_none(item: object) -> bool: - """Check if a variable is None or equivalent. + """Check if a variable is None or equivalent. Args: item: Variable to be checked. Returns: Answer to whether the variable is None or equivalent. - """ + """ if item is None: return True try: - return cast("bool", isnan(cast("float", item))) + return cast("bool", isnan(cast("float", item))) except (TypeError, ValueError): return len(str(item)) == 0
diff --git a/docs/build/html/_modules/openseries/simulation.html b/docs/build/html/_modules/openseries/simulation.html index c5dfa0a2..6fb00d83 100644 --- a/docs/build/html/_modules/openseries/simulation.html +++ b/docs/build/html/_modules/openseries/simulation.html @@ -108,7 +108,7 @@

Source code for openseries.simulation

-"""The ReturnSimulation class."""
+"""The ReturnSimulation class."""
 
 from __future__ import annotations
 
@@ -146,11 +146,11 @@ 

Source code for openseries.simulation

     ValueType,
 )
 
-__all__ = ["ReturnSimulation"]
+__all__ = ["ReturnSimulation"]
 
 
 class _JumpParams(TypedDict, total=False):
-    """TypedDict for jump diffusion parameters."""
+    """TypedDict for jump diffusion parameters."""
 
     jumps_lamda: NonNegativeFloat
     jumps_sigma: NonNegativeFloat
@@ -158,14 +158,14 @@ 

Source code for openseries.simulation

 
 
 def _validate_ar1_coef(ar1_coef: float) -> None:
-    """Validate ar1_coef is in (-1, 1) for stationarity."""
+    """Validate ar1_coef is in (-1, 1) for stationarity."""
     if not -1.0 < ar1_coef < 1.0:
-        msg = f"ar1_coef must be in (-1, 1) for stationarity, got {ar1_coef}"
+        msg = f"ar1_coef must be in (-1, 1) for stationarity, got {ar1_coef}"
         raise ValueError(msg)
 
 
 def _apply_ar1_filter(returns: DataFrame, ar1_coef: float) -> DataFrame:
-    """Apply AR(1) filter to returns to introduce lag-1 autocorrelation.
+    """Apply AR(1) filter to returns to introduce lag-1 autocorrelation.
 
     r_t = ar1_coef * r_{t-1} + sqrt(1 - ar1_coef**2) * innovation_t
     Preserves mean and variance of the base process.
@@ -176,27 +176,27 @@ 

Source code for openseries.simulation

 
     Returns:
         Filtered returns.
-    """
+    """
     if ar1_coef == 0.0:
         return returns
     arr = returns.to_numpy(copy=True)
     scale = sqrt(1.0 - ar1_coef * ar1_coef)
     for t in range(1, arr.shape[1]):
         arr[:, t] = ar1_coef * arr[:, t - 1] + scale * arr[:, t]
-    return DataFrame(data=arr, dtype="float64")
+    return DataFrame(data=arr, dtype="float64")
 
 
 def _random_generator(seed: int | None) -> Generator:
-    """Make a Numpy Random Generator object.
+    """Make a Numpy Random Generator object.
 
     Args:
         seed: Random seed.
 
     Returns:
         Numpy random process generator.
-    """
+    """
     ss = SeedSequence(entropy=seed)
-    bg = PCG64(seed=cast("int | None", ss))
+    bg = PCG64(seed=cast("int | None", ss))
     return Generator(bit_generator=bg)
 
 
@@ -211,7 +211,7 @@ 

Source code for openseries.simulation

     seed: int | None = None,
     **kwargs: Unpack[_JumpParams],
 ) -> ReturnSimulation:
-    """Common logic for creating simulations.
+    """Common logic for creating simulations.
 
     Args:
         cls: The ReturnSimulation class.
@@ -226,7 +226,7 @@ 

Source code for openseries.simulation

 
     Returns:
         A ReturnSimulation instance.
-    """
+    """
     return cls(
         number_of_sims=number_of_sims,
         trading_days=trading_days,
@@ -242,7 +242,7 @@ 

Source code for openseries.simulation

 
[docs] class ReturnSimulation(BaseModel): - """The class ReturnSimulation allows for simulating financial timeseries. + """The class ReturnSimulation allows for simulating financial timeseries. Args: number_of_sims: Number of simulations to generate. @@ -257,7 +257,7 @@

Source code for openseries.simulation

         jumps_mu: This is the average jump size. Defaults to 0.0.
         seed: Seed for random process initiation.
 
-    """
+    """
 
     number_of_sims: PositiveInt
     trading_days: PositiveInt
@@ -273,30 +273,30 @@ 

Source code for openseries.simulation

     model_config = ConfigDict(
         arbitrary_types_allowed=True,
         validate_assignment=True,
-        revalidate_instances="always",
+        revalidate_instances="always",
     )
 
 
[docs] @cached_property def results(self: Self) -> DataFrame: - """Simulation data. + """Simulation data. Returns: Simulation data. - """ - return self.dframe.add(1.0).cumprod(axis="columns").T
+ """ + return self.dframe.add(1.0).cumprod(axis="columns").T
@property def realized_mean_return(self: Self) -> float: - """Annualized arithmetic mean of returns. + """Annualized arithmetic mean of returns. Returns: Annualized arithmetic mean of returns. - """ + """ return cast( - "float", + "float", ( self.results.ffill().pct_change().mean() * self.trading_days_in_year ).iloc[0], @@ -304,13 +304,13 @@

Source code for openseries.simulation

 
     @property
     def realized_vol(self: Self) -> float:
-        """Annualized volatility.
+        """Annualized volatility.
 
         Returns:
             Annualized volatility.
-        """
+        """
         return cast(
-            "float",
+            "float",
             (
                 self.results.ffill().pct_change().std()
                 * sqrt(self.trading_days_in_year)
@@ -331,7 +331,7 @@ 

Source code for openseries.simulation

         randomizer: Generator | None = None,
         ar1_coef: float = 0.0,
     ) -> ReturnSimulation:
-        """Create a Normal distribution simulation.
+        """Create a Normal distribution simulation.
 
         Args:
             number_of_sims: Number of simulations to generate.
@@ -347,7 +347,7 @@ 

Source code for openseries.simulation

 
         Returns:
             Normal distribution simulation.
-        """
+        """
         _validate_ar1_coef(ar1_coef)
         if not randomizer:
             randomizer = _random_generator(seed=seed)
@@ -358,7 +358,7 @@ 

Source code for openseries.simulation

                 scale=mean_annual_vol / sqrt(trading_days_in_year),
                 size=(number_of_sims, trading_days),
             ),
-            dtype="float64",
+            dtype="float64",
         )
         returns = _apply_ar1_filter(returns_df, ar1_coef)
 
@@ -388,7 +388,7 @@ 

Source code for openseries.simulation

         randomizer: Generator | None = None,
         ar1_coef: float = 0.0,
     ) -> ReturnSimulation:
-        """Create a Lognormal distribution simulation.
+        """Create a Lognormal distribution simulation.
 
         Args:
             number_of_sims: Number of simulations to generate.
@@ -404,7 +404,7 @@ 

Source code for openseries.simulation

 
         Returns:
             Lognormal distribution simulation.
-        """
+        """
         _validate_ar1_coef(ar1_coef)
         if not randomizer:
             randomizer = _random_generator(seed=seed)
@@ -418,7 +418,7 @@ 

Source code for openseries.simulation

                 )
                 - 1
             ),
-            dtype="float64",
+            dtype="float64",
         )
         returns = _apply_ar1_filter(returns_df, ar1_coef)
 
@@ -448,7 +448,7 @@ 

Source code for openseries.simulation

         randomizer: Generator | None = None,
         ar1_coef: float = 0.0,
     ) -> ReturnSimulation:
-        """Create a Geometric Brownian Motion simulation.
+        """Create a Geometric Brownian Motion simulation.
 
         Args:
             number_of_sims: Number of simulations to generate.
@@ -464,7 +464,7 @@ 

Source code for openseries.simulation

 
         Returns:
             Geometric Brownian Motion simulation.
-        """
+        """
         _validate_ar1_coef(ar1_coef)
         if not randomizer:
             randomizer = _random_generator(seed=seed)
@@ -480,7 +480,7 @@ 

Source code for openseries.simulation

             size=(number_of_sims, trading_days),
         )
 
-        returns_df = DataFrame(data=drift + wiener, dtype="float64")
+        returns_df = DataFrame(data=drift + wiener, dtype="float64")
         returns = _apply_ar1_filter(returns_df, ar1_coef)
 
         return _create_base_simulation(
@@ -512,7 +512,7 @@ 

Source code for openseries.simulation

         randomizer: Generator | None = None,
         ar1_coef: float = 0.0,
     ) -> ReturnSimulation:
-        """Create a Merton Jump-Diffusion model simulation.
+        """Create a Merton Jump-Diffusion model simulation.
 
         Args:
             number_of_sims: Number of simulations to generate.
@@ -532,7 +532,7 @@ 

Source code for openseries.simulation

 
         Returns:
             Merton Jump-Diffusion model simulation.
-        """
+        """
         _validate_ar1_coef(ar1_coef)
         if not randomizer:
             randomizer = _random_generator(seed=seed)
@@ -565,7 +565,7 @@ 

Source code for openseries.simulation

         raw_returns = poisson_jumps + drift + wiener
         raw_returns[:, 0] = 0.0
 
-        returns_df = DataFrame(data=raw_returns, dtype="float64")
+        returns_df = DataFrame(data=raw_returns, dtype="float64")
         returns = _apply_ar1_filter(returns_df, ar1_coef)
 
         return _create_base_simulation(
@@ -590,22 +590,22 @@ 

Source code for openseries.simulation

         name: str,
         start: dt.date | None = None,
         end: dt.date | None = None,
-        countries: CountriesType = "SE",
+        countries: CountriesType = "SE",
         markets: list[str] | str | None = None,
     ) -> DataFrame:
-        """Create a pandas.DataFrame from simulation(s).
+        """Create a pandas.DataFrame from simulation(s).
 
         Args:
             name: Name label of the serie(s).
             start: Date when the simulation starts.
             end: Date when the simulation ends.
             countries: (List of) country code(s) according to ISO 3166-1 alpha-2.
-                Defaults to "SE".
+                Defaults to "SE".
             markets: (List of) markets code(s) supported by exchange_calendars.
 
         Returns:
             The simulation(s) data.
-        """
+        """
         d_range = generate_calendar_date_range(
             trading_days=self.trading_days,
             start=start,
@@ -631,14 +631,14 @@ 

Source code for openseries.simulation

                 index=Index(d_range),
                 columns=MultiIndex.from_arrays(
                     [
-                        [f"{name}_{item}"],
+                        [f"{name}_{item}"],
                         [ValueType.RTRN],
                     ],
                 ),
             )
             for item in range(self.number_of_sims)
         ]
-        return concat(df_list, axis="columns", sort=True)
+ return concat(df_list, axis="columns", sort=True)
diff --git a/docs/build/html/_sources/user_guide/installation.rst.txt b/docs/build/html/_sources/user_guide/installation.rst.txt index 9561e284..3ae5fc99 100644 --- a/docs/build/html/_sources/user_guide/installation.rst.txt +++ b/docs/build/html/_sources/user_guide/installation.rst.txt @@ -52,7 +52,7 @@ Core Dependencies - **pandas** (>=2.1.2,<3.0.0) - Data manipulation and analysis - **numpy** (>=1.23.2,!=2.3.0,<3.0.0) - Numerical computing - **pydantic** (>=2.5.2,<3.0.0) - Data validation and settings management -- **plotly** (>=5.18.0,<7.0.0) - Interactive plotting +- **plotly** (>=5.18.0) - Interactive plotting - **scipy** (>=1.11.4,<2.0.0) - Scientific computing - **scikit-learn** (>=1.4.0,<2.0.0) - Machine learning utilities diff --git a/docs/build/html/api/frame.html b/docs/build/html/api/frame.html index 66beb084..746bffd1 100644 --- a/docs/build/html/api/frame.html +++ b/docs/build/html/api/frame.html @@ -221,7 +221,7 @@

OpenFrame

Properties of the constituent OpenTimeSeries.

Return type:
-

DataFrame

+

DataFrame

@@ -441,7 +441,7 @@

OpenFrame
-property correl_matrix: DataFrame
+property correl_matrix: DataFrame

Correlation matrix.

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

@@ -730,7 +730,7 @@

OpenFrame

A basket timeseries.

Return type:
-

DataFrame

+

DataFrame

@@ -760,7 +760,7 @@

OpenFrame

Rolling Information Ratios.

Return type:
-

DataFrame

+

DataFrame

@@ -788,7 +788,7 @@

OpenFrame

Rolling Betas.

Return type:
-

DataFrame

+

DataFrame

@@ -815,7 +815,7 @@

OpenFrame

Rolling Correlations.

Return type:
-

DataFrame

+

DataFrame

@@ -1115,7 +1115,7 @@

Financial MetricsReturn type: -

DataFrame

+

DataFrame

@@ -1573,7 +1573,7 @@

Portfolio Analysis

A basket timeseries.

Return type:
-

DataFrame

+

DataFrame

@@ -1853,7 +1853,7 @@

Rolling Analysis

Rolling Information Ratios.

Return type:
-

DataFrame

+

DataFrame

@@ -1881,7 +1881,7 @@

Rolling Analysis

Rolling Betas.

Return type:
-

DataFrame

+

DataFrame

@@ -1908,7 +1908,7 @@

Rolling Analysis

Rolling Correlations.

Return type:
-

DataFrame

+

DataFrame

@@ -1929,7 +1929,7 @@

Rolling Analysis

DataFrame with rolling returns.

Return type:
-

DataFrame

+

DataFrame

@@ -1998,7 +1998,7 @@

Rolling Analysis

DataFrame with rolling annualized downside CVaR.

Return type:
-

DataFrame

+

DataFrame

diff --git a/docs/build/html/api/generated/openseries.OpenFrame.html b/docs/build/html/api/generated/openseries.OpenFrame.html index 2c2909cf..d6cb48a8 100644 --- a/docs/build/html/api/generated/openseries.OpenFrame.html +++ b/docs/build/html/api/generated/openseries.OpenFrame.html @@ -631,7 +631,7 @@

openseries.OpenFrame

Properties of the constituent OpenTimeSeries.

Return type:
-

DataFrame

+

DataFrame

@@ -851,7 +851,7 @@

openseries.OpenFrame
-property correl_matrix: DataFrame
+property correl_matrix: DataFrame

Correlation matrix.

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

@@ -1140,7 +1140,7 @@

openseries.OpenFrame

A basket timeseries.

Return type:
-

DataFrame

+

DataFrame

@@ -1170,7 +1170,7 @@

openseries.OpenFrame

Rolling Information Ratios.

Return type:
-

DataFrame

+

DataFrame

@@ -1198,7 +1198,7 @@

openseries.OpenFrame

Rolling Betas.

Return type:
-

DataFrame

+

DataFrame

@@ -1225,7 +1225,7 @@

openseries.OpenFrame

Rolling Correlations.

Return type:
-

DataFrame

+

DataFrame

diff --git a/docs/build/html/api/generated/openseries.OpenTimeSeries.html b/docs/build/html/api/generated/openseries.OpenTimeSeries.html index e300bd60..266052e2 100644 --- a/docs/build/html/api/generated/openseries.OpenTimeSeries.html +++ b/docs/build/html/api/generated/openseries.OpenTimeSeries.html @@ -146,7 +146,7 @@

openseries.OpenTimeSeriesAnnotated[list[float], MinLen(min_length=1)]) – The value or return values of the timeseries items. These values will not be altered by methods.

  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency.

  • -
  • tsdf (DataFrame) – Pandas object holding dates and values that can be altered via +

  • tsdf (DataFrame) – Pandas object holding dates and values that can be altered via methods.

  • currency (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries.

  • domestic (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the user’s home currency. @@ -678,7 +678,7 @@

    openseries.OpenTimeSeries
    Parameters:
    diff --git a/docs/build/html/api/generated/openseries.ReturnSimulation.html b/docs/build/html/api/generated/openseries.ReturnSimulation.html index 6e8da5dd..45006040 100644 --- a/docs/build/html/api/generated/openseries.ReturnSimulation.html +++ b/docs/build/html/api/generated/openseries.ReturnSimulation.html @@ -138,7 +138,7 @@

    openseries.ReturnSimulationAnnotated[int, Strict(strict=True), Ge(ge=1), Le(le=366)]) – Number of trading days used to annualize.

  • mean_annual_return (float) – Mean annual return of the distribution.

  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean annual standard deviation of the distribution.

  • -
  • dframe (DataFrame) – Pandas DataFrame object holding the resulting values.

  • +
  • dframe (DataFrame) – Pandas DataFrame object holding the resulting values.

  • jumps_lamda (Annotated[float, Ge(ge=0)]) – This is the probability of a jump happening at each point in time. Defaults to 0.0.

  • jumps_sigma (Annotated[float, Ge(ge=0)]) – This is the volatility of the jump size. Defaults to 0.0.

  • @@ -373,7 +373,7 @@

    openseries.ReturnSimulation
    -property results: DataFrame[source]
    +property results: DataFrame[source]

    Simulation data.

    Returns:
    @@ -418,7 +418,7 @@

    openseries.ReturnSimulationAnnotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -446,7 +446,7 @@

    openseries.ReturnSimulationAnnotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -474,7 +474,7 @@

    openseries.ReturnSimulationAnnotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -506,7 +506,7 @@

    openseries.ReturnSimulationAnnotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • diff --git a/docs/build/html/api/series.html b/docs/build/html/api/series.html index 34ac24c9..f5f08f88 100644 --- a/docs/build/html/api/series.html +++ b/docs/build/html/api/series.html @@ -151,7 +151,7 @@

    OpenTimeSeriesAnnotated[list[float], MinLen(min_length=1)]) – The value or return values of the timeseries items. These values will not be altered by methods.

  • local_ccy (bool) – Boolean flag indicating if timeseries is in local currency.

  • -
  • tsdf (DataFrame) – Pandas object holding dates and values that can be altered via +

  • tsdf (DataFrame) – Pandas object holding dates and values that can be altered via methods.

  • currency (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the timeseries.

  • domestic (Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, to_lower=None, strict=True, min_length=3, max_length=3, pattern=^[A-Z]{3}$, ascii_only=None)]) – ISO 4217 currency code of the user’s home currency. @@ -269,7 +269,7 @@

    OpenTimeSeries
    Parameters:

  • @@ -762,7 +762,7 @@

    Class Methods for Construction
    Parameters:
      -
    • dframe (Series | DataFrame | object) – Pandas DataFrame or Series.

    • +
    • dframe (Series | DataFrame | object) – Pandas DataFrame or Series.

    • column_nmbr (int) – Using iloc[:, column_nmbr] to pick column. Defaults to 0.

    • valuetype (ValueType) – Identifies if the series is a series of values or returns. Defaults to ValueType.PRICE.

    • @@ -794,7 +794,7 @@

      Class Methods for ConstructionParameters:
      • rate (float) – The accrual rate.

      • -
      • d_range (DatetimeIndex | None) – A given range of dates. Optional.

      • +
      • d_range (DatetimeIndex | None) – A given range of dates. Optional.

      • days (int | None) – Number of days to generate when date_range not provided. Must be combined with end_dt. Optional.

      • end_dt (date | None) – End date of date range to generate when date_range not provided. @@ -1009,7 +1009,7 @@

        Financial MetricsReturn type: -

        DataFrame

        +

        DataFrame

      @@ -1832,7 +1832,7 @@

      Analysis Methods

      DataFrame with rolling returns.

    Return type:
    -

    DataFrame

    +

    DataFrame

    @@ -1901,7 +1901,7 @@

    Analysis Methods

    DataFrame with rolling annualized downside CVaR.

    Return type:
    -

    DataFrame

    +

    DataFrame

    diff --git a/docs/build/html/api/simulation.html b/docs/build/html/api/simulation.html index 2e0cb61d..13f94e91 100644 --- a/docs/build/html/api/simulation.html +++ b/docs/build/html/api/simulation.html @@ -129,7 +129,7 @@

    SimulationAnnotated[int, Strict(strict=True), Ge(ge=1), Le(le=366)]) – Number of trading days used to annualize.

  • mean_annual_return (float) – Mean annual return of the distribution.

  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean annual standard deviation of the distribution.

  • -
  • dframe (DataFrame) – Pandas DataFrame object holding the resulting values.

  • +
  • dframe (DataFrame) – Pandas DataFrame object holding the resulting values.

  • jumps_lamda (Annotated[float, Ge(ge=0)]) – This is the probability of a jump happening at each point in time. Defaults to 0.0.

  • jumps_sigma (Annotated[float, Ge(ge=0)]) – This is the volatility of the jump size. Defaults to 0.0.

  • @@ -196,7 +196,7 @@

    Simulation
    -property results: DataFrame[source]
    +property results: DataFrame[source]

    Simulation data.

    Returns:
    @@ -241,7 +241,7 @@

    SimulationAnnotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -269,7 +269,7 @@

    SimulationAnnotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -297,7 +297,7 @@

    SimulationAnnotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -329,7 +329,7 @@

    SimulationAnnotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -385,7 +385,7 @@

    ReturnSimulation Class

    trading_days_in_year (Annotated[int, Strict(strict=True), Ge(ge=1), Le(le=366)]) – Number of trading days used to annualize.

  • mean_annual_return (float) – Mean annual return of the distribution.

  • mean_annual_vol (Annotated[float, Gt(gt=0)]) – Mean annual standard deviation of the distribution.

  • -
  • dframe (DataFrame) – Pandas DataFrame object holding the resulting values.

  • +
  • dframe (DataFrame) – Pandas DataFrame object holding the resulting values.

  • jumps_lamda (Annotated[float, Ge(ge=0)]) – This is the probability of a jump happening at each point in time. Defaults to 0.0.

  • jumps_sigma (Annotated[float, Ge(ge=0)]) – This is the volatility of the jump size. Defaults to 0.0.

  • @@ -452,7 +452,7 @@

    ReturnSimulation Class
    -property results: DataFrame[source]
    +property results: DataFrame[source]

    Simulation data.

    Returns:
    @@ -497,7 +497,7 @@

    ReturnSimulation Class

    trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -525,7 +525,7 @@

    ReturnSimulation Class

    trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -553,7 +553,7 @@

    ReturnSimulation Class

    trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • @@ -585,7 +585,7 @@

    ReturnSimulation Class

    trading_days_in_year (Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Strict(strict=True), Ge(ge=1), Le(le=366)])]) – Number of trading days used to annualize. Defaults to 252.

  • seed (int | None) – Seed for random process initiation.

  • -
  • randomizer (Generator | None) – Random process generator.

  • +
  • randomizer (Generator | None) – Random process generator.

  • ar1_coef (float) – Lag-1 autoregressive coefficient in (-1, 1) to induce autocorrelation. Defaults to 0.0 (i.i.d. returns).

  • diff --git a/docs/build/html/api/types.html b/docs/build/html/api/types.html index e28cf820..b1603da1 100644 --- a/docs/build/html/api/types.html +++ b/docs/build/html/api/types.html @@ -290,10 +290,10 @@

    Type Aliases
      @@ -217,7 +217,7 @@

      Development Workflow

      Commit your changes:

    git add .
    -git commit -m "Add your descriptive commit message"
    +git commit -m "Add your descriptive commit message"
     
      @@ -247,7 +247,7 @@

      Code Style

      All new code should include proper type hints:

      def calculate_returns(prices: list[float]) -> list[float]:
      -     """Calculate simple returns from prices."""
      +     """Calculate simple returns from prices."""
            returns = []
            for i in range(1, len(prices)):
                 ret = (prices[i] / prices[i-1]) - 1
      @@ -260,7 +260,7 @@ 

      Type Hints

      Use Google-style docstrings for all public functions and classes:

      def calculate_sharpe_ratio(returns: list[float], risk_free_rate: float = 0.0) -> float:
      -     """Calculate the Sharpe ratio.
      +     """Calculate the Sharpe ratio.
       
            Args:
                 returns: List of periodic returns.
      @@ -275,10 +275,10 @@ 

      Docstrings Example: >>> returns = [0.01, 0.02, -0.01, 0.03] >>> sharpe = calculate_sharpe_ratio(returns) - >>> print(f"Sharpe ratio: {sharpe:.3f}") - """ + >>> print(f"Sharpe ratio: {sharpe:.3f}") + """ if not returns: - raise ValueError("Returns list cannot be empty") + raise ValueError("Returns list cannot be empty") mean_return = sum(returns) / len(returns) std_dev = (sum((r - mean_return) ** 2 for r in returns) / len(returns)) ** 0.5 @@ -314,57 +314,57 @@

      Writing Testsfrom openseries import OpenTimeSeries class TestOpenTimeSeries: - """Test cases for OpenTimeSeries class.""" + """Test cases for OpenTimeSeries class.""" def test_from_arrays_basic(self): - """Test basic creation from arrays.""" - dates = ['2023-01-01', '2023-01-02', '2023-01-03'] + """Test basic creation from arrays.""" + dates = ['2023-01-01', '2023-01-02', '2023-01-03'] values = [100.0, 102.0, 99.0] - series = OpenTimeSeries.from_arrays(dates=dates, values=values, name="Test") + series = OpenTimeSeries.from_arrays(dates=dates, values=values, name="Test") - if series.label != "Test": - msg = f"Expected name 'Test', got '{series.label}'" + if series.label != "Test": + msg = f"Expected name 'Test', got '{series.label}'" raise ValueError(msg) if series.length != 3: - msg = f"Expected length 3, got {series.length}" + msg = f"Expected length 3, got {series.length}" raise ValueError(msg) - if series.first_idx != pd.Timestamp('2023-01-01').date(): - msg = f"Expected first_idx 2023-01-01, got {series.first_idx}" + if series.first_idx != pd.Timestamp('2023-01-01').date(): + msg = f"Expected first_idx 2023-01-01, got {series.first_idx}" raise ValueError(msg) - if series.last_idx != pd.Timestamp('2023-01-03').date(): - msg = f"Expected last_idx 2023-01-03, got {series.last_idx}" + if series.last_idx != pd.Timestamp('2023-01-03').date(): + msg = f"Expected last_idx 2023-01-03, got {series.last_idx}" raise ValueError(msg) def test_from_arrays_invalid_dates(self): - """Test that invalid dates raise appropriate errors.""" + """Test that invalid dates raise appropriate errors.""" with pytest.raises(ValueError): OpenTimeSeries.from_arrays( - dates=['invalid-date'], + dates=['invalid-date'], values=[100.0], - name="Test" + name="Test" ) def test_calculate_returns(self): - """Test return calculation.""" - dates = ['2023-01-01', '2023-01-02', '2023-01-03'] + """Test return calculation.""" + dates = ['2023-01-01', '2023-01-02', '2023-01-03'] values = [100.0, 102.0, 99.0] - series = OpenTimeSeries.from_arrays(dates=dates, values=values, name="Test") + series = OpenTimeSeries.from_arrays(dates=dates, values=values, name="Test") series.value_to_ret() # Modifies original expected_returns = [0.02, -0.0294117647] # Approximate actual_returns = series.values if len(actual_returns) != 2: - msg = f"Expected 2 returns, got {len(actual_returns)}" + msg = f"Expected 2 returns, got {len(actual_returns)}" raise ValueError(msg) # Use tolerance-based comparison if abs(actual_returns[0] - expected_returns[0]) >= 1e-6: - msg = f"First return mismatch: {actual_returns[0]} vs {expected_returns[0]}" + msg = f"First return mismatch: {actual_returns[0]} vs {expected_returns[0]}" raise ValueError(msg) if abs(actual_returns[1] - expected_returns[1]) >= 1e-6: - msg = f"Second return mismatch: {actual_returns[1]} vs {expected_returns[1]}" + msg = f"Second return mismatch: {actual_returns[1]} vs {expected_returns[1]}" raise ValueError(msg)

      @@ -511,11 +511,11 @@

      IDE Setup.vscode/settings.json:

      {
      -    "python.defaultInterpreterPath": ".venv/bin/python",
      -    "python.linting.enabled": true,
      -    "python.linting.ruffEnabled": true,
      -    "python.formatting.provider": "ruff",
      -    "python.typeChecking": "strict"
      +    "python.defaultInterpreterPath": ".venv/bin/python",
      +    "python.linting.enabled": true,
      +    "python.linting.ruffEnabled": true,
      +    "python.formatting.provider": "ruff",
      +    "python.typeChecking": "strict"
       }
       
      @@ -528,22 +528,22 @@

      Debugging.vscode/launch.json:

      {
      -    "version": "0.2.0",
      -    "configurations": [
      +    "version": "0.2.0",
      +    "configurations": [
               {
      -            "name": "Python: Current File",
      -            "type": "python",
      -            "request": "launch",
      -            "program": "${file}",
      -            "console": "integratedTerminal"
      +            "name": "Python: Current File",
      +            "type": "python",
      +            "request": "launch",
      +            "program": "${file}",
      +            "console": "integratedTerminal"
               },
               {
      -            "name": "Python: Pytest",
      -            "type": "python",
      -            "request": "launch",
      -            "module": "pytest",
      -            "args": ["${workspaceFolder}/tests"],
      -            "console": "integratedTerminal"
      +            "name": "Python: Pytest",
      +            "type": "python",
      +            "request": "launch",
      +            "module": "pytest",
      +            "args": ["${workspaceFolder}/tests"],
      +            "console": "integratedTerminal"
               }
           ]
       }
      diff --git a/docs/build/html/examples/custom_reports.html b/docs/build/html/examples/custom_reports.html
      index ed8710c0..e4bb496a 100644
      --- a/docs/build/html/examples/custom_reports.html
      +++ b/docs/build/html/examples/custom_reports.html
      @@ -124,13 +124,13 @@ 

      Using the Built-in HTML Reportimport pandas as pd # Load sample data for comparison -tickers = ["AAPL", "MSFT", "GOOGL", "SPY"] -names = ["Apple", "Microsoft", "Google", "S&P 500"] +tickers = ["AAPL", "MSFT", "GOOGL", "SPY"] +names = ["Apple", "Microsoft", "Google", "S&P 500"] series_list = [] for ticker, name in zip(tickers, names): - data = yf.Ticker(ticker).history(period="3y") - series = OpenTimeSeries.from_df(dframe=data['Close']) + data = yf.Ticker(ticker).history(period="3y") + series = OpenTimeSeries.from_df(dframe=data['Close']) series.set_new_label(lvl_zero=name) series_list.append(series) @@ -141,12 +141,12 @@

      Using the Built-in HTML Report# The last asset in the frame is used as the benchmark figure, filepath = report_html( data=comparison_frame, - output_type="file", - filename="stock_comparison_report.html" + output_type="file", + filename="stock_comparison_report.html" ) # filepath contains the path to the saved HTML file -print(f"Report saved to: {filepath}") +print(f"Report saved to: {filepath}") # The figure object can be used for further customization if needed # figure.show() # Display the figure interactively @@ -159,7 +159,7 @@

      Embedding Reports in Existing HTML Pages

      diff --git a/docs/build/html/examples/multi_asset.html b/docs/build/html/examples/multi_asset.html index a1d6a64b..6c574967 100644 --- a/docs/build/html/examples/multi_asset.html +++ b/docs/build/html/examples/multi_asset.html @@ -133,32 +133,32 @@

      Setting Up Multi-Asset Analysis# Define asset universe assets = { - "AAPL": "Apple Inc.", - "GOOGL": "Alphabet Inc.", - "MSFT": "Microsoft Corp.", - "AMZN": "Amazon.com Inc.", - "TSLA": "Tesla Inc.", - "NVDA": "NVIDIA Corp.", - "META": "Meta Platforms Inc.", - "NFLX": "Netflix Inc." + "AAPL": "Apple Inc.", + "GOOGL": "Alphabet Inc.", + "MSFT": "Microsoft Corp.", + "AMZN": "Amazon.com Inc.", + "TSLA": "Tesla Inc.", + "NVDA": "NVIDIA Corp.", + "META": "Meta Platforms Inc.", + "NFLX": "Netflix Inc." } # Download data for all assets series_list = [] for ticker, name in assets.items(): # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="3y") + data = yf.Ticker(ticker).history(period="3y") series = OpenTimeSeries.from_df( - dframe=data['Close'] + dframe=data['Close'] ) series.set_new_label(lvl_zero=name) series_list.append(series) - print(f"Loaded {name}: {series.length} observations") + print(f"Loaded {name}: {series.length} observations") # Create OpenFrame tech_stocks = OpenFrame(constituents=series_list) -print(f"\nCreated frame with {tech_stocks.item_count} assets") -print(f"Common period: {tech_stocks.first_idx} to {tech_stocks.last_idx}") +print(f"\nCreated frame with {tech_stocks.item_count} assets") +print(f"Common period: {tech_stocks.first_idx} to {tech_stocks.last_idx}")

      @@ -166,18 +166,18 @@

      Setting Up Multi-Asset Analysis

      # Get metrics for all assets
       all_metrics = tech_stocks.all_properties()
      -print("=== COMPARATIVE METRICS ===")
      +print("=== COMPARATIVE METRICS ===")
       print(all_metrics)
       
       # Focus on key metrics
      -key_metrics = all_metrics.loc[['Geometric return', 'Volatility', 'Return vol ratio', 'Max drawdown']]
      -key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown']
      +key_metrics = all_metrics.loc[['Geometric return', 'Volatility', 'Return vol ratio', 'Max drawdown']]
      +key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown']
       
       # Convert to percentages for better readability
       percentage_metrics = key_metrics.copy()
      -percentage_metrics.loc[['Annual Return', 'Volatility', 'Max Drawdown']] *= 100
      +percentage_metrics.loc[['Annual Return', 'Volatility', 'Max Drawdown']] *= 100
       
      -print("\n=== KEY METRICS COMPARISON ===")
      +print("\n=== KEY METRICS COMPARISON ===")
       print(percentage_metrics.round(2))
       
      @@ -186,27 +186,27 @@

      Comparative AnalysisRanking Analysis

      # Rank assets by different criteria using openseries metrics
       # Get key metrics for ranking
      -returns = all_metrics.loc['Geometric return']
      -volatilities = all_metrics.loc['Volatility']
      -sharpe_ratios = all_metrics.loc['Return vol ratio']
      -drawdowns = all_metrics.loc['Max drawdown']
      +returns = all_metrics.loc['Geometric return']
      +volatilities = all_metrics.loc['Volatility']
      +sharpe_ratios = all_metrics.loc['Return vol ratio']
      +drawdowns = all_metrics.loc['Max drawdown']
       
      -print("\n=== ASSET RANKINGS ===")
      -print("Ranked by Return (highest first):")
      +print("\n=== ASSET RANKINGS ===")
      +print("Ranked by Return (highest first):")
       for i, (asset, ret) in enumerate(returns.sort_values(ascending=False).items(), 1):
      -    print(f"  {i}. {asset}: {ret:.2%}")
      +    print(f"  {i}. {asset}: {ret:.2%}")
       
      -print("\nRanked by Volatility (lowest first):")
      +print("\nRanked by Volatility (lowest first):")
       for i, (asset, vol) in enumerate(volatilities.sort_values(ascending=True).items(), 1):
      -    print(f"  {i}. {asset}: {vol:.2%}")
      +    print(f"  {i}. {asset}: {vol:.2%}")
       
      -print("\nRanked by Sharpe Ratio (highest first):")
      +print("\nRanked by Sharpe Ratio (highest first):")
       for i, (asset, sharpe) in enumerate(sharpe_ratios.sort_values(ascending=False).items(), 1):
      -    print(f"  {i}. {asset}: {sharpe:.2f}")
      +    print(f"  {i}. {asset}: {sharpe:.2f}")
       
      -print("\nRanked by Max Drawdown (least negative first):")
      +print("\nRanked by Max Drawdown (least negative first):")
       for i, (asset, dd) in enumerate(drawdowns.sort_values(ascending=False).items(), 1):
      -    print(f"  {i}. {asset}: {dd:.2%}")
      +    print(f"  {i}. {asset}: {dd:.2%}")
       
      @@ -214,7 +214,7 @@

      Ranking Analysis

      Risk-Return Analysis

      # Analyze risk-return using openseries metrics
      -returns = all_metrics.loc['Geometric return']
      -volatilities = all_metrics.loc['Volatility']
      -sharpe_ratios = all_metrics.loc['Return vol ratio']
      +returns = all_metrics.loc['Geometric return']
      +volatilities = all_metrics.loc['Volatility']
      +sharpe_ratios = all_metrics.loc['Return vol ratio']
       
      -print("\n=== RISK-RETURN ANALYSIS ===")
      +print("\n=== RISK-RETURN ANALYSIS ===")
       for asset in returns.index:
           ret_pct = returns[asset] * 100
           vol_pct = volatilities[asset] * 100
           sharpe = sharpe_ratios[asset]
      -    print(f"{asset}: Return={ret_pct:.2f}%, Volatility={vol_pct:.2f}%, Sharpe={sharpe:.2f}")
      +    print(f"{asset}: Return={ret_pct:.2f}%, Volatility={vol_pct:.2f}%, Sharpe={sharpe:.2f}")
       
       # Identify efficient assets (high return per unit risk)
       # Calculate 75th percentile threshold manually
      @@ -259,10 +259,10 @@ 

      Risk-Return Analysisthreshold_idx = int(len(sorted_sharpes) * 0.25) efficient_threshold = sorted_sharpes[threshold_idx] if threshold_idx < len(sorted_sharpes) else sorted_sharpes[-1] -print(f"\n=== MOST EFFICIENT ASSETS (Sharpe >= {efficient_threshold:.2f}) ===") +print(f"\n=== MOST EFFICIENT ASSETS (Sharpe >= {efficient_threshold:.2f}) ===") for asset, sharpe in sharpe_ratios.items(): if sharpe >= efficient_threshold: - print(f"{asset}: {sharpe:.2f}") + print(f"{asset}: {sharpe:.2f}")

      @@ -270,12 +270,12 @@

      Risk-Return AnalysisSector/Style Analysis

      # Group assets by characteristics (example grouping)
       asset_groups = {
      -     'Mega Cap': ['Apple Inc.', 'Microsoft Corp.', 'Alphabet Inc.', 'Amazon.com Inc.'],
      -     'Growth': ['Tesla Inc.', 'NVIDIA Corp.', 'Netflix Inc.'],
      -     'Social Media': ['Meta Platforms Inc.']
      +     'Mega Cap': ['Apple Inc.', 'Microsoft Corp.', 'Alphabet Inc.', 'Amazon.com Inc.'],
      +     'Growth': ['Tesla Inc.', 'NVIDIA Corp.', 'Netflix Inc.'],
      +     'Social Media': ['Meta Platforms Inc.']
       }
       
      -print("\n=== GROUP ANALYSIS ===")
      +print("\n=== GROUP ANALYSIS ===")
       for group_name, group_assets in asset_groups.items():
            # Filter assets that exist in our data
            group_series = [s for s in tech_stocks.constituents if s.label in group_assets]
      @@ -284,14 +284,14 @@ 

      Sector/Style Analysisgroup_frame = OpenFrame(constituents=group_series) group_metrics = group_frame.all_properties() - avg_return = group_metrics.loc['Geometric return'].mean() - avg_vol = group_metrics.loc['Volatility'].mean() - avg_sharpe = group_metrics.loc['Return vol ratio'].mean() + avg_return = group_metrics.loc['Geometric return'].mean() + avg_vol = group_metrics.loc['Volatility'].mean() + avg_sharpe = group_metrics.loc['Return vol ratio'].mean() - print(f"\n{group_name} ({len(group_series)} assets):") - print(f" Average Return: {avg_return:.2%}") - print(f" Average Volatility: {avg_vol:.2%}") - print(f" Average Sharpe: {avg_sharpe:.2f}") + print(f"\n{group_name} ({len(group_series)} assets):") + print(f" Average Return: {avg_return:.2%}") + print(f" Average Volatility: {avg_vol:.2%}") + print(f" Average Sharpe: {avg_sharpe:.2f}")

      @@ -299,45 +299,45 @@

      Sector/Style AnalysisTime Series Analysis

      # Rolling correlation analysis
       # Pick two assets for detailed analysis
      -apple = next(s for s in tech_stocks.constituents if "Apple" in s.label)
      -microsoft = next(s for s in tech_stocks.constituents if "Microsoft" in s.label)
      +apple = next(s for s in tech_stocks.constituents if "Apple" in s.label)
      +microsoft = next(s for s in tech_stocks.constituents if "Microsoft" in s.label)
       
       pair_frame = OpenFrame(constituents=[apple, microsoft])
       rolling_corr = pair_frame.rolling_corr(observations=252)  # 1-year rolling
       
      -print(f"\n=== ROLLING CORRELATION: {apple.label} vs {microsoft.label} ===")
      -print(f"Current correlation: {rolling_corr.iloc[-1, 0]:.3f}")
      -print(f"Average correlation: {rolling_corr.mean().iloc[0]:.3f}")
      -print(f"Correlation range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}")
      +print(f"\n=== ROLLING CORRELATION: {apple.label} vs {microsoft.label} ===")
      +print(f"Current correlation: {rolling_corr.iloc[-1, 0]:.3f}")
      +print(f"Average correlation: {rolling_corr.mean().iloc[0]:.3f}")
      +print(f"Correlation range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}")
       

      Performance Attribution

      # Create equal-weighted portfolio for attribution
      -portfolio_df = tech_stocks.make_portfolio(name="Tech Portfolio", weight_strat="eq_weights")
      +portfolio_df = tech_stocks.make_portfolio(name="Tech Portfolio", weight_strat="eq_weights")
       portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
       
      -print(f"\n=== PORTFOLIO vs INDIVIDUAL ASSETS ===")
      -print(f"Portfolio Return: {portfolio.geo_ret:.2%}")
      -print(f"Portfolio Volatility: {portfolio.vol:.2%}")
      -print(f"Portfolio Sharpe: {portfolio.ret_vol_ratio:.2f}")
      +print(f"\n=== PORTFOLIO vs INDIVIDUAL ASSETS ===")
      +print(f"Portfolio Return: {portfolio.geo_ret:.2%}")
      +print(f"Portfolio Volatility: {portfolio.vol:.2%}")
      +print(f"Portfolio Sharpe: {portfolio.ret_vol_ratio:.2f}")
       
       # Compare with individual assets using OpenFrame
       asset_metrics = tech_stocks.all_properties()
      -individual_returns = asset_metrics.loc['Geometric return'].values
      -individual_vols = asset_metrics.loc['Volatility'].values
      +individual_returns = asset_metrics.loc['Geometric return'].values
      +individual_vols = asset_metrics.loc['Volatility'].values
       
      -print(f"\nDiversification benefit:")
      +print(f"\nDiversification benefit:")
       equal_weights = [1/tech_stocks.item_count] * tech_stocks.item_count
       # Calculate weighted average manually
       weighted_avg_return = sum(ret * w for ret, w in zip(individual_returns, equal_weights))
       weighted_avg_vol = sum(vol * w for vol, w in zip(individual_vols, equal_weights))
      -print(f"  Weighted avg return: {weighted_avg_return:.2%}")
      -print(f"  Portfolio return: {portfolio.geo_ret:.2%}")
      -print(f"  Weighted avg volatility: {weighted_avg_vol:.2%}")
      -print(f"  Portfolio volatility: {portfolio.vol:.2%}")
      -print(f"  Volatility reduction: {(weighted_avg_vol - portfolio.vol):.2%}")
      +print(f"  Weighted avg return: {weighted_avg_return:.2%}")
      +print(f"  Portfolio return: {portfolio.geo_ret:.2%}")
      +print(f"  Weighted avg volatility: {weighted_avg_vol:.2%}")
      +print(f"  Portfolio volatility: {portfolio.vol:.2%}")
      +print(f"  Volatility reduction: {(weighted_avg_vol - portfolio.vol):.2%}")
       
      @@ -351,12 +351,12 @@

      Stress Testingworst_threshold = market_data.quantile(0.05) worst_days = market_data[market_data <= worst_threshold] -print(f"\n=== STRESS TEST ANALYSIS ===") -print(f"Market stress threshold: {worst_threshold:.2%}") -print(f"Number of stress days: {len(worst_days)}") +print(f"\n=== STRESS TEST ANALYSIS ===") +print(f"Market stress threshold: {worst_threshold:.2%}") +print(f"Number of stress days: {len(worst_days)}") -# Analyze each asset's performance during stress -print("\nAsset performance during market stress:") +# Analyze each asset's performance during stress +print("\nAsset performance during market stress:") for series in tech_stocks.constituents: series.value_to_ret() # Modifies original asset_data = series.tsdf @@ -364,7 +364,7 @@

      Stress Testingstress_returns = asset_data.loc[worst_days.index] avg_stress_return = stress_returns.mean() - print(f" {series.label}: {avg_stress_return:.2%}") + print(f" {series.label}: {avg_stress_return:.2%}")

    @@ -372,12 +372,12 @@

    Stress Testing

    # Export using openseries native methods
     # Export frame data
    -tech_stocks.to_xlsx('multi_asset_analysis.xlsx')
    +tech_stocks.to_xlsx('multi_asset_analysis.xlsx')
     
     # Note: For comprehensive Excel export with multiple sheets,
     # you can use the DataFrame returned by all_properties() and correl_matrix
     # which are pandas DataFrames and support to_excel() method
    -print("\nMulti-asset analysis exported to 'multi_asset_analysis.xlsx'")
    +print("\nMulti-asset analysis exported to 'multi_asset_analysis.xlsx'")
     
    @@ -385,49 +385,49 @@

    Export Multi-Asset Results

    Here’s how to perform a complete multi-asset analysis using openseries methods directly:

    # Example: Analyze tech stocks using openseries methods
    -tech_tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"]
    +tech_tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"]
     
     # Load data using openseries methods
     series_list = []
     for ticker in tech_tickers:
          # This may fail if the ticker is invalid or data unavailable
    -     data = yf.Ticker(ticker).history(period="3y")
    -     series = OpenTimeSeries.from_df(dframe=data['Close'])
    +     data = yf.Ticker(ticker).history(period="3y")
    +     series = OpenTimeSeries.from_df(dframe=data['Close'])
          series.set_new_label(lvl_zero=ticker)
          series_list.append(series)
     
     if not series_list:
    -     print("No data loaded")
    +     print("No data loaded")
     else:
          # Create frame using openseries
          frame = OpenFrame(constituents=series_list)
     
          # Analysis using openseries properties and methods
    -     print(f"=== MULTI-ASSET ANALYSIS ===")
    -     print(f"Assets: {frame.item_count}")
    -     print(f"Period: {frame.first_idx} to {frame.last_idx}")
    +     print(f"=== MULTI-ASSET ANALYSIS ===")
    +     print(f"Assets: {frame.item_count}")
    +     print(f"Period: {frame.first_idx} to {frame.last_idx}")
     
          # Key metrics using openseries all_properties method
          key_metrics = frame.all_properties(
    -          properties=['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']
    +          properties=['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']
          )
     
    -     print("\nKey Metrics:")
    +     print("\nKey Metrics:")
          print((key_metrics * 100).round(2))  # Convert to percentages
     
          # Correlations using openseries correl_matrix property
          correlations = frame.correl_matrix
          avg_correlation = correlations.mean().mean()
    -     print(f"\nAverage correlation: {avg_correlation:.3f}")
    +     print(f"\nAverage correlation: {avg_correlation:.3f}")
     
          # Create portfolio using openseries make_portfolio method
    -     portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
    +     portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
          portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
     
    -     print(f"\nEqual-weight portfolio:")
    -     print(f"  Return: {portfolio.geo_ret:.2%}")
    -     print(f"  Volatility: {portfolio.vol:.2%}")
    -     print(f"  Sharpe: {portfolio.ret_vol_ratio:.2f}")
    +     print(f"\nEqual-weight portfolio:")
    +     print(f"  Return: {portfolio.geo_ret:.2%}")
    +     print(f"  Volatility: {portfolio.vol:.2%}")
    +     print(f"  Sharpe: {portfolio.ret_vol_ratio:.2f}")
     
    diff --git a/docs/build/html/examples/portfolio_optimization.html b/docs/build/html/examples/portfolio_optimization.html index e53ab84d..1a66577b 100644 --- a/docs/build/html/examples/portfolio_optimization.html +++ b/docs/build/html/examples/portfolio_optimization.html @@ -146,30 +146,30 @@

    Basic Portfolio Optimization Setup# Define investment universe universe = { - "VTI": "Total Stock Market", - "VEA": "Developed Markets", - "VWO": "Emerging Markets", - "BND": "Total Bond Market", - "VNQ": "Real Estate", - "VDE": "Energy", - "VGT": "Technology", - "VHT": "Healthcare" + "VTI": "Total Stock Market", + "VEA": "Developed Markets", + "VWO": "Emerging Markets", + "BND": "Total Bond Market", + "VNQ": "Real Estate", + "VDE": "Energy", + "VGT": "Technology", + "VHT": "Healthcare" } # Load data assets = [] for ticker, name in universe.items(): # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="5y") - series = OpenTimeSeries.from_df(dframe=data['Close']) + data = yf.Ticker(ticker).history(period="5y") + series = OpenTimeSeries.from_df(dframe=data['Close']) series.set_new_label(lvl_zero=name) assets.append(series) - print(f"Loaded {name}") + print(f"Loaded {name}") # Create investment universe frame investment_universe = OpenFrame(constituents=assets) -print(f"\nInvestment universe: {investment_universe.item_count} assets") -print(f"Period: {investment_universe.first_idx} to {investment_universe.last_idx}") +print(f"\nInvestment universe: {investment_universe.item_count} assets") +print(f"Period: {investment_universe.first_idx} to {investment_universe.last_idx}")

    @@ -183,44 +183,44 @@

    Mean-Variance Optimizationseed=42 ) -print("=== EFFICIENT FRONTIER RESULTS ===") -print(f"Generated {len(frontier_df)} efficient portfolios") -print(f"Simulated {len(simulated_df)} random portfolios") +print("=== EFFICIENT FRONTIER RESULTS ===") +print(f"Generated {len(frontier_df)} efficient portfolios") +print(f"Simulated {len(simulated_df)} random portfolios") # Find key portfolios -returns = frontier_df['ret'] -volatilities = frontier_df['stdev'] +returns = frontier_df['ret'] +volatilities = frontier_df['stdev'] sharpe_ratios = returns / volatilities # Maximum Sharpe ratio portfolio max_sharpe_idx = sharpe_ratios.idxmax() max_sharpe_weights = optimal_portfolio[-len(investment_universe.constituents):] -print(f"\n=== MAXIMUM SHARPE RATIO PORTFOLIO ===") -print(f"Expected Return: {frontier_df.iloc[max_sharpe_idx]['ret']:.2%}") -print(f"Volatility: {frontier_df.iloc[max_sharpe_idx]['stdev']:.2%}") -print(f"Sharpe Ratio: {sharpe_ratios.iloc[max_sharpe_idx]:.2f}") +print(f"\n=== MAXIMUM SHARPE RATIO PORTFOLIO ===") +print(f"Expected Return: {frontier_df.iloc[max_sharpe_idx]['ret']:.2%}") +print(f"Volatility: {frontier_df.iloc[max_sharpe_idx]['stdev']:.2%}") +print(f"Sharpe Ratio: {sharpe_ratios.iloc[max_sharpe_idx]:.2f}") -print("\nOptimal Weights:") +print("\nOptimal Weights:") for i, weight in enumerate(max_sharpe_weights): asset_name = investment_universe.constituents[i].label if weight > 0.01: # Only show weights > 1% - print(f" {asset_name}: {weight:.1%}") + print(f" {asset_name}: {weight:.1%}") # Minimum volatility portfolio min_vol_idx = volatilities.idxmin() min_vol_weights = frontier_df.iloc[min_vol_idx][investment_universe.columns_lvl_zero].values -print(f"\n=== MINIMUM VOLATILITY PORTFOLIO ===") -print(f"Expected Return: {min_vol_row['ret']:.2%}") -print(f"Volatility: {min_vol_row['stdev']:.2%}") -print(f"Sharpe Ratio: {sharpe_ratios.iloc[min_vol_idx]:.2f}") +print(f"\n=== MINIMUM VOLATILITY PORTFOLIO ===") +print(f"Expected Return: {min_vol_row['ret']:.2%}") +print(f"Volatility: {min_vol_row['stdev']:.2%}") +print(f"Sharpe Ratio: {sharpe_ratios.iloc[min_vol_idx]:.2f}") -print("\nMinimum Volatility Weights:") +print("\nMinimum Volatility Weights:") for col in investment_universe.columns_lvl_zero: weight = min_vol_row[col] if weight > 0.01: - print(f" {col}: {weight:.1%}") + print(f" {col}: {weight:.1%}")

    @@ -234,36 +234,36 @@

    Monte Carlo Portfolio Simulationseed=42 ) -print(f"\n=== MONTE CARLO SIMULATION ===") -print(f"Simulated {len(simulation_results)} random portfolios") +print(f"\n=== MONTE CARLO SIMULATION ===") +print(f"Simulated {len(simulation_results)} random portfolios") -sim_returns = simulation_results['ret'].values -sim_volatilities = simulation_results['stdev'].values +sim_returns = simulation_results['ret'].values +sim_volatilities = simulation_results['stdev'].values sim_sharpe_ratios = sim_returns / sim_volatilities # Statistics of simulated portfolios -print(f"\nSimulation Statistics:") -print(f"Return range: {sim_returns.min():.2%} to {sim_returns.max():.2%}") -print(f"Volatility range: {sim_volatilities.min():.2%} to {sim_volatilities.max():.2%}") -print(f"Sharpe range: {sim_sharpe_ratios.min():.2f} to {sim_sharpe_ratios.max():.2f}") +print(f"\nSimulation Statistics:") +print(f"Return range: {sim_returns.min():.2%} to {sim_returns.max():.2%}") +print(f"Volatility range: {sim_volatilities.min():.2%} to {sim_volatilities.max():.2%}") +print(f"Sharpe range: {sim_sharpe_ratios.min():.2f} to {sim_sharpe_ratios.max():.2f}") # Best portfolios from simulation sorted_indices = sorted(range(len(sim_sharpe_ratios)), key=lambda i: sim_sharpe_ratios.iloc[i], reverse=True) top_sharpe_indices = sorted_indices[:5] -print(f"\n=== TOP 5 SIMULATED PORTFOLIOS ===") +print(f"\n=== TOP 5 SIMULATED PORTFOLIOS ===") for i, idx in enumerate(reversed(top_sharpe_indices)): - print(f"\nRank {i+1}:") - print(f" Return: {sim_returns[idx]:.2%}") - print(f" Volatility: {sim_volatilities[idx]:.2%}") - print(f" Sharpe: {sim_sharpe_ratios[idx]:.2f}") + print(f"\nRank {i+1}:") + print(f" Return: {sim_returns[idx]:.2%}") + print(f" Volatility: {sim_volatilities[idx]:.2%}") + print(f" Sharpe: {sim_sharpe_ratios[idx]:.2f}") weights = simulation_results.iloc[idx][investment_universe.columns_lvl_zero].values - print(" Weights:") + print(" Weights:") for j, weight in enumerate(weights): if weight > 0.05: # Only show weights > 5% asset_name = investment_universe.constituents[j].label - print(f" {asset_name}: {weight:.1%}") + print(f" {asset_name}: {weight:.1%}")

    @@ -273,15 +273,15 @@

    Risk-Based Portfolio Strategies

    # Equal weight portfolio using native weight_strat
     equal_weight_portfolio_df = investment_universe.make_portfolio(
    -     name="Equal Weight",
    -     weight_strat="eq_weights"
    +     name="Equal Weight",
    +     weight_strat="eq_weights"
     )
     equal_weight_portfolio = OpenTimeSeries.from_df(dframe=equal_weight_portfolio_df)
     
    -print(f"\n=== EQUAL WEIGHT PORTFOLIO ===")
    -print(f"Return: {equal_weight_portfolio.geo_ret:.2%}")
    -print(f"Volatility: {equal_weight_portfolio.vol:.2%}")
    -print(f"Sharpe: {equal_weight_portfolio.ret_vol_ratio:.2f}")
    +print(f"\n=== EQUAL WEIGHT PORTFOLIO ===")
    +print(f"Return: {equal_weight_portfolio.geo_ret:.2%}")
    +print(f"Volatility: {equal_weight_portfolio.vol:.2%}")
    +print(f"Sharpe: {equal_weight_portfolio.ret_vol_ratio:.2f}")
     
    @@ -289,15 +289,15 @@

    Equal Weight PortfolioInverse Volatility Portfolio

    # Inverse volatility weighting using native weight_strat
     inv_vol_portfolio_df = investment_universe.make_portfolio(
    -     name="Inverse Volatility",
    -     weight_strat="inv_vol"
    +     name="Inverse Volatility",
    +     weight_strat="inv_vol"
     )
     inv_vol_portfolio = OpenTimeSeries.from_df(dframe=inv_vol_portfolio_df)
     
    -print(f"\n=== INVERSE VOLATILITY PORTFOLIO ===")
    -print(f"Return: {inv_vol_portfolio.geo_ret:.2%}")
    -print(f"Volatility: {inv_vol_portfolio.vol:.2%}")
    -print(f"Sharpe: {inv_vol_portfolio.ret_vol_ratio:.2f}")
    +print(f"\n=== INVERSE VOLATILITY PORTFOLIO ===")
    +print(f"Return: {inv_vol_portfolio.geo_ret:.2%}")
    +print(f"Volatility: {inv_vol_portfolio.vol:.2%}")
    +print(f"Sharpe: {inv_vol_portfolio.ret_vol_ratio:.2f}")
     
    @@ -307,15 +307,15 @@

    Maximum Diversification Portfolio
    # Maximum diversification portfolio using native weight_strat
     # This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError
     max_div_portfolio_df = investment_universe.make_portfolio(
    -     name="Maximum Diversification",
    -     weight_strat="max_div"
    +     name="Maximum Diversification",
    +     weight_strat="max_div"
     )
     max_div_portfolio = OpenTimeSeries.from_df(dframe=max_div_portfolio_df)
     
    -print(f"\n=== MAXIMUM DIVERSIFICATION PORTFOLIO ===")
    -print(f"Return: {max_div_portfolio.geo_ret:.2%}")
    -print(f"Volatility: {max_div_portfolio.vol:.2%}")
    -print(f"Sharpe: {max_div_portfolio.ret_vol_ratio:.2f}")
    +print(f"\n=== MAXIMUM DIVERSIFICATION PORTFOLIO ===")
    +print(f"Return: {max_div_portfolio.geo_ret:.2%}")
    +print(f"Volatility: {max_div_portfolio.vol:.2%}")
    +print(f"Sharpe: {max_div_portfolio.ret_vol_ratio:.2f}")
     

    @@ -324,15 +324,15 @@

    Maximum Diversification Portfolio

    # Minimum volatility overweight portfolio using native weight_strat
     min_vol_portfolio_df = investment_universe.make_portfolio(
    -     name="Min Vol Overweight",
    -     weight_strat="min_vol_overweight"
    +     name="Min Vol Overweight",
    +     weight_strat="min_vol_overweight"
     )
     min_vol_portfolio = OpenTimeSeries.from_df(dframe=min_vol_portfolio_df)
     
    -print(f"\n=== MINIMUM VOLATILITY OVERWEIGHT PORTFOLIO ===")
    -print(f"Return: {min_vol_portfolio.geo_ret:.2%}")
    -print(f"Volatility: {min_vol_portfolio.vol:.2%}")
    -print(f"Sharpe: {min_vol_portfolio.ret_vol_ratio:.2f}")
    +print(f"\n=== MINIMUM VOLATILITY OVERWEIGHT PORTFOLIO ===")
    +print(f"Return: {min_vol_portfolio.geo_ret:.2%}")
    +print(f"Volatility: {min_vol_portfolio.vol:.2%}")
    +print(f"Sharpe: {min_vol_portfolio.ret_vol_ratio:.2f}")
     
    @@ -347,18 +347,18 @@

    Portfolio Comparison] # Add optimized portfolios if available -if 'max_sharpe_weights' in locals(): +if 'max_sharpe_weights' in locals(): investment_universe.weights = max_sharpe_weights.tolist() max_sharpe_portfolio_df = investment_universe.make_portfolio( - name="Max Sharpe (Optimized)" + name="Max Sharpe (Optimized)" ) max_sharpe_portfolio = OpenTimeSeries.from_df(dframe=max_sharpe_portfolio_df) portfolios.append(max_sharpe_portfolio) -if 'min_vol_weights' in locals(): +if 'min_vol_weights' in locals(): investment_universe.weights = min_vol_weights.tolist() min_vol_portfolio_df = investment_universe.make_portfolio( - name="Min Vol (Optimized)" + name="Min Vol (Optimized)" ) min_vol_portfolio = OpenTimeSeries.from_df(dframe=min_vol_portfolio_df) portfolios.append(min_vol_portfolio) @@ -368,10 +368,10 @@

    Portfolio Comparisoncomparison_metrics = comparison_frame.all_properties() # Display key metrics -key_metrics = comparison_metrics.loc[['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']] -key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] +key_metrics = comparison_metrics.loc[['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']] +key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] -print(f"\n=== PORTFOLIO STRATEGY COMPARISON ===") +print(f"\n=== PORTFOLIO STRATEGY COMPARISON ===") print((key_metrics * 100).round(2)) # Convert to percentages

    @@ -412,7 +412,7 @@

    Weight Strategy Details) # This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError -portfolio_df = frame.make_portfolio(name="Max Div", weight_strat="max_div") +portfolio_df = frame.make_portfolio(name="Max Div", weight_strat="max_div")

    @@ -423,10 +423,10 @@

    Weight Strategy DetailsBacktesting Framework

    # Define strategies to backtest using native weight_strat
     strategies = {
    -     'Equal Weight': 'eq_weights',
    -     'Inverse Volatility': 'inv_vol',
    -     'Max Diversification': 'max_div',
    -     'Min Vol Overweight': 'min_vol_overweight'
    +     'Equal Weight': 'eq_weights',
    +     'Inverse Volatility': 'inv_vol',
    +     'Max Diversification': 'max_div',
    +     'Min Vol Overweight': 'min_vol_overweight'
     }
     
     # Run backtest using native strategies
    @@ -439,28 +439,28 @@ 

    Backtesting Framework) portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) backtest_results[strategy_name] = { - 'return': portfolio.geo_ret, - 'volatility': portfolio.vol, - 'sharpe': portfolio.ret_vol_ratio, - 'max_drawdown': portfolio.max_drawdown, - 'calmar': portfolio.geo_ret / abs(portfolio.max_drawdown) if portfolio.max_drawdown != 0 else float('nan') + 'return': portfolio.geo_ret, + 'volatility': portfolio.vol, + 'sharpe': portfolio.ret_vol_ratio, + 'max_drawdown': portfolio.max_drawdown, + 'calmar': portfolio.geo_ret / abs(portfolio.max_drawdown) if portfolio.max_drawdown != 0 else float('nan') } -print(f"\n=== BACKTEST RESULTS ===") +print(f"\n=== BACKTEST RESULTS ===") for strategy_name, metrics in backtest_results.items(): - print(f"\n{strategy_name}:") - print(f" Return: {metrics['return']:.4f}") - print(f" Volatility: {metrics['volatility']:.4f}") - print(f" Sharpe: {metrics['sharpe']:.4f}") - print(f" Max Drawdown: {metrics['max_drawdown']:.4f}") - print(f" Calmar: {metrics['calmar']:.4f}") + print(f"\n{strategy_name}:") + print(f" Return: {metrics['return']:.4f}") + print(f" Volatility: {metrics['volatility']:.4f}") + print(f" Sharpe: {metrics['sharpe']:.4f}") + print(f" Max Drawdown: {metrics['max_drawdown']:.4f}") + print(f" Calmar: {metrics['calmar']:.4f}") # Rank strategies -sorted_strategies = sorted(backtest_results.items(), key=lambda x: x[1]['sharpe'], reverse=True) +sorted_strategies = sorted(backtest_results.items(), key=lambda x: x[1]['sharpe'], reverse=True) best_strategy = sorted_strategies[0][0] -print(f"\nBest performing strategy: {best_strategy}") -print(f"Sharpe ratio: {sorted_strategies[0][1]['sharpe']:.3f}") +print(f"\nBest performing strategy: {best_strategy}") +print(f"Sharpe ratio: {sorted_strategies[0][1]['sharpe']:.3f}")

    @@ -468,12 +468,12 @@

    Backtesting FrameworkExport Optimization Results

    # Export using openseries native methods
     # Export frame data
    -investment_universe.to_xlsx('portfolio_optimization_results.xlsx')
    +investment_universe.to_xlsx('portfolio_optimization_results.xlsx')
     
     # Note: For comprehensive Excel export with multiple sheets,
     # the DataFrames returned by all_properties() and correl_matrix
     # are pandas DataFrames and support to_excel() method
    -print("\nOptimization results exported to 'portfolio_optimization_results.xlsx'")
    +print("\nOptimization results exported to 'portfolio_optimization_results.xlsx'")
     
    @@ -492,28 +492,28 @@

    Using Real Fund Data for Optimization# Define fund universe for optimization fund_universe_isins = [ - "SE0015243886", # Global High Yield - "SE0011337195", # Global Equity - "SE0011670843", # Global Bond - "SE0017832280", # Alternative Strategy - "SE0017832330", # Multi-Asset Strategy + "SE0015243886", # Global High Yield + "SE0011337195", # Global Equity + "SE0011670843", # Global Bond + "SE0017832280", # Alternative Strategy + "SE0017832330", # Multi-Asset Strategy ] # Load fund data using openseries methods -response = requests_get(url="https://api.captor.se/public/api/nav", timeout=10) +response = requests_get(url="https://api.captor.se/public/api/nav", timeout=10) response.raise_for_status() series_list = [] result = response.json() for data in result: - if data["isin"] in fund_universe_isins: + if data["isin"] in fund_universe_isins: series = OpenTimeSeries.from_arrays( - name=data["longName"], - isin=data["isin"], - baseccy=data["currency"], - dates=data["dates"], - values=data["navPerUnit"], + name=data["longName"], + isin=data["isin"], + baseccy=data["currency"], + dates=data["dates"], + values=data["navPerUnit"], valuetype=ValueType.PRICE, ) series_list.append(series) @@ -524,8 +524,8 @@

    Using Real Fund Data for Optimization# Process data using openseries methods fund_universe = fund_universe.value_nan_handle().trunc_frame().to_cumret() -print(f"Fund universe created with {fund_universe.item_count} funds") -print(f"Analysis period: {fund_universe.first_idx} to {fund_universe.last_idx}") +print(f"Fund universe created with {fund_universe.item_count} funds") +print(f"Analysis period: {fund_universe.first_idx} to {fund_universe.last_idx}")

    @@ -538,8 +538,8 @@

    Advanced Optimization with Real Data# Create current portfolio (equal weights) current_portfolio_df = fund_universe.make_portfolio( - name="Current Portfolio", - weight_strat="eq_weights", + name="Current Portfolio", + weight_strat="eq_weights", ) current_portfolio = OpenTimeSeries.from_df(dframe=current_portfolio_df) @@ -566,16 +566,16 @@

    Advanced Optimization with Real Datasim_frame=simulated_portfolios, line_frame=frontier, point_frame=plot_data, - point_frame_mode="markers+text", - title="Real Fund Portfolio Optimization", + point_frame_mode="markers+text", + title="Real Fund Portfolio Optimization", add_logo=False, auto_open=False, - output_type="div", + output_type="div", ) optimization_plot = optimization_plot.update_layout(width=1200, height=700) # Display the optimization results -optimization_plot.show(config=figdict["config"]) +optimization_plot.show(config=figdict["config"])

    @@ -586,38 +586,38 @@

    Performance Comparison Analysis# Equal weight portfolio equal_weight_portfolio_df = fund_universe.make_portfolio( - name="Equal Weight", weight_strat="eq_weights" + name="Equal Weight", weight_strat="eq_weights" ) equal_weight_portfolio = OpenTimeSeries.from_df(dframe=equal_weight_portfolio_df) -strategies['Equal Weight'] = equal_weight_portfolio +strategies['Equal Weight'] = equal_weight_portfolio # Optimal portfolio from efficient frontier fund_universe.weights = optimal_portfolio[-fund_universe.item_count:].tolist() -optimal_portfolio_df = fund_universe.make_portfolio(name="Optimal Portfolio") +optimal_portfolio_df = fund_universe.make_portfolio(name="Optimal Portfolio") optimal_portfolio_series = OpenTimeSeries.from_df(dframe=optimal_portfolio_df) -strategies['Optimal Portfolio'] = optimal_portfolio_series +strategies['Optimal Portfolio'] = optimal_portfolio_series # Create comparison frame comparison_frame = OpenFrame(constituents=list(strategies.values())) comparison_metrics = comparison_frame.all_properties() # Display key metrics -key_metrics = comparison_metrics.loc[['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']] -key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] +key_metrics = comparison_metrics.loc[['geo_ret', 'vol', 'ret_vol_ratio', 'max_drawdown']] +key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] -print("=== PORTFOLIO STRATEGY COMPARISON ===") +print("=== PORTFOLIO STRATEGY COMPARISON ===") print((key_metrics * 100).round(2)) # Calculate improvement metrics improvement = { - 'Return Improvement': (optimal_portfolio_series.geo_ret - equal_weight_portfolio.geo_ret) * 100, - 'Volatility Change': (optimal_portfolio_series.vol - equal_weight_portfolio.vol) * 100, - 'Sharpe Improvement': optimal_portfolio_series.ret_vol_ratio - equal_weight_portfolio.ret_vol_ratio, + 'Return Improvement': (optimal_portfolio_series.geo_ret - equal_weight_portfolio.geo_ret) * 100, + 'Volatility Change': (optimal_portfolio_series.vol - equal_weight_portfolio.vol) * 100, + 'Sharpe Improvement': optimal_portfolio_series.ret_vol_ratio - equal_weight_portfolio.ret_vol_ratio, } -print("\n=== OPTIMIZATION IMPROVEMENTS ===") +print("\n=== OPTIMIZATION IMPROVEMENTS ===") for metric, value in improvement.items(): - print(f"{metric}: {value:+.2f}") + print(f"{metric}: {value:+.2f}")

    @@ -626,28 +626,28 @@

    Performance Comparison Analysis

    Here’s how to perform portfolio optimization using openseries methods directly:

    # Example: Optimize ETF portfolio using openseries methods
    -etf_tickers = ["VTI", "VEA", "VWO", "BND", "VNQ"]
    +etf_tickers = ["VTI", "VEA", "VWO", "BND", "VNQ"]
     
     # Load data using openseries methods
     assets = []
     for ticker in etf_tickers:
          # This may fail if the ticker is invalid or data unavailable
    -     data = yf.Ticker(ticker).history(period="5y")
    -     series = OpenTimeSeries.from_df(dframe=data['Close'])
    +     data = yf.Ticker(ticker).history(period="5y")
    +     series = OpenTimeSeries.from_df(dframe=data['Close'])
          series.set_new_label(lvl_zero=ticker)
          assets.append(series)
     
     if len(assets) < 2:
    -     print("Need at least 2 assets for optimization")
    +     print("Need at least 2 assets for optimization")
     else:
          frame = OpenFrame(constituents=assets)
     
          # Use openseries native weight strategies
          strategies = {
    -          'Equal Weight': 'eq_weights',
    -          'Inverse Volatility': 'inv_vol',
    -          'Max Diversification': 'max_div',
    -          'Min Vol Overweight': 'min_vol_overweight'
    +          'Equal Weight': 'eq_weights',
    +          'Inverse Volatility': 'inv_vol',
    +          'Max Diversification': 'max_div',
    +          'Min Vol Overweight': 'min_vol_overweight'
          }
     
          # Create portfolios using openseries make_portfolio method
    @@ -657,19 +657,19 @@ 

    Complete Optimization Workflowportfolio_df = frame.make_portfolio(name=name, weight_strat=weight_strat) portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) results[name] = { - 'Return': portfolio.geo_ret, - 'Volatility': portfolio.vol, - 'Sharpe': portfolio.ret_vol_ratio, - 'Max Drawdown': portfolio.max_drawdown + 'Return': portfolio.geo_ret, + 'Volatility': portfolio.vol, + 'Sharpe': portfolio.ret_vol_ratio, + 'Max Drawdown': portfolio.max_drawdown } - print("=== PORTFOLIO OPTIMIZATION RESULTS ===") + print("=== PORTFOLIO OPTIMIZATION RESULTS ===") for name, metrics in results.items(): - print(f"\n{name}:") - print(f" Return: {metrics['Return']*100:.2f}%") - print(f" Volatility: {metrics['Volatility']*100:.2f}%") - print(f" Sharpe: {metrics['Sharpe']:.2f}") - print(f" Max Drawdown: {metrics['Max Drawdown']*100:.2f}%") + print(f"\n{name}:") + print(f" Return: {metrics['Return']*100:.2f}%") + print(f" Volatility: {metrics['Volatility']*100:.2f}%") + print(f" Sharpe: {metrics['Sharpe']:.2f}") + print(f" Max Drawdown: {metrics['Max Drawdown']*100:.2f}%")

    diff --git a/docs/build/html/examples/single_asset.html b/docs/build/html/examples/single_asset.html index 25a0ad5d..84345bd1 100644 --- a/docs/build/html/examples/single_asset.html +++ b/docs/build/html/examples/single_asset.html @@ -131,34 +131,34 @@

    Basic Setupimport numpy as np # Download Apple stock data -ticker = yf.Ticker("AAPL") -data = ticker.history(period="5y") +ticker = yf.Ticker("AAPL") +data = ticker.history(period="5y") # Create OpenTimeSeries apple = OpenTimeSeries.from_df( - dframe=data['Close'] + dframe=data['Close'] ) # Set descriptive label -apple.set_new_label(lvl_zero="Apple Inc. (AAPL)") +apple.set_new_label(lvl_zero="Apple Inc. (AAPL)") -print(f"Loaded {apple.length} observations") -print(f"Date range: {apple.first_idx} to {apple.last_idx}") +print(f"Loaded {apple.length} observations") +print(f"Date range: {apple.first_idx} to {apple.last_idx}")

    Performance Analysis

    # Basic performance metrics
    -print("=== PERFORMANCE METRICS ===")
    -print(f"Total Return: {apple.value_ret:.2%}")
    -print(f"Annualized Return: {apple.geo_ret:.2%}")
    -print(f"Annualized Volatility: {apple.vol:.2%}")
    -print(f"Sharpe Ratio: {apple.ret_vol_ratio:.2f}")
    +print("=== PERFORMANCE METRICS ===")
    +print(f"Total Return: {apple.value_ret:.2%}")
    +print(f"Annualized Return: {apple.geo_ret:.2%}")
    +print(f"Annualized Volatility: {apple.vol:.2%}")
    +print(f"Sharpe Ratio: {apple.ret_vol_ratio:.2f}")
     
     # Get all metrics at once
     all_metrics = apple.all_properties()
    -print("\n=== ALL METRICS ===")
    +print("\n=== ALL METRICS ===")
     print(all_metrics)
     
    @@ -166,13 +166,13 @@

    Performance Analysis

    Risk Analysis

    # Risk metrics
    -print("=== RISK ANALYSIS ===")
    -print(f"Maximum Drawdown: {apple.max_drawdown:.2%}")
    -print(f"Max Drawdown Date: {apple.max_drawdown_date}")
    -print(f"95% VaR (daily): {apple.var_down:.2%}")
    -print(f"95% CVaR (daily): {apple.cvar_down:.2%}")
    -print(f"Worst Single Day: {apple.worst:.2%}")
    -print(f"Sortino Ratio: {apple.sortino_ratio:.2f}")
    +print("=== RISK ANALYSIS ===")
    +print(f"Maximum Drawdown: {apple.max_drawdown:.2%}")
    +print(f"Max Drawdown Date: {apple.max_drawdown_date}")
    +print(f"95% VaR (daily): {apple.var_down:.2%}")
    +print(f"95% CVaR (daily): {apple.cvar_down:.2%}")
    +print(f"Worst Single Day: {apple.worst:.2%}")
    +print(f"Sortino Ratio: {apple.sortino_ratio:.2f}")
     

    @@ -180,7 +180,7 @@

    Risk Analysis

    # Convert to returns (modifies original)
     apple.value_to_ret()
    -print(f"Returns series length: {apple.length}")
    +print(f"Returns series length: {apple.length}")
     
     # Create drawdown series (modifies original)
     apple.to_drawdown_series()
    @@ -189,8 +189,8 @@ 

    Time Series Transformationsapple.value_to_log() # Resample to monthly (modifies original) -apple.resample_to_business_period_ends(freq="BME") -print(f"Monthly data points: {apple.length}") +apple.resample_to_business_period_ends(freq="BME") +print(f"Monthly data points: {apple.length}")

    @@ -198,8 +198,8 @@

    Time Series Transformations

    # Rolling volatility (1-year window)
     rolling_vol = apple.rolling_vol(observations=252)
    -print(f"Current 1Y volatility: {rolling_vol.iloc[-1, 0]:.2%}")
    -print(f"Average 1Y volatility: {rolling_vol.mean().iloc[0]:.2%}")
    +print(f"Current 1Y volatility: {rolling_vol.iloc[-1, 0]:.2%}")
    +print(f"Average 1Y volatility: {rolling_vol.mean().iloc[0]:.2%}")
     
     # Rolling returns (30-day)
     rolling_returns = apple.rolling_return(observations=30)
    @@ -227,24 +227,24 @@ 

    Calendar Analysis
    # Annual returns by calendar year
     years = [2019, 2020, 2021, 2022, 2023, 2024]
     
    -print("=== CALENDAR YEAR RETURNS ===")
    +print("=== CALENDAR YEAR RETURNS ===")
     for year in years:
          # This may fail if no data exists for the year
          year_return = apple.value_ret_calendar_period(year=year)
    -     print(f"{year}: {year_return:.2%}")
    +     print(f"{year}: {year_return:.2%}")
     

    Export Results

    # Export to Excel
    -apple.to_xlsx("apple_analysis.xlsx")
    +apple.to_xlsx("apple_analysis.xlsx")
     
     # Export metrics to CSV
    -all_metrics.to_csv("apple_metrics.csv")
    +all_metrics.to_csv("apple_metrics.csv")
     
     # Export to JSON
    -apple.to_json("apple_data.json")
    +apple.to_json("apple_data.json")
     
    @@ -255,55 +255,55 @@

    Complete Analysis Workflowfrom openseries import OpenTimeSeries # Example: Analyze Apple stock using openseries methods -ticker_symbol = "AAPL" +ticker_symbol = "AAPL" # Download data using openseries methods ticker = yf.Ticker(ticker_symbol) -data = ticker.history(period="5y") +data = ticker.history(period="5y") # Create series using openseries from_df method series = OpenTimeSeries.from_df( - dframe=data['Close'], + dframe=data['Close'], name=ticker_symbol ) # Analysis using openseries properties and methods -print(f"=== {ticker_symbol} ANALYSIS ===") -print(f"Period: {series.first_idx} to {series.last_idx}") -print(f"Observations: {series.length}") +print(f"=== {ticker_symbol} ANALYSIS ===") +print(f"Period: {series.first_idx} to {series.last_idx}") +print(f"Observations: {series.length}") # Key metrics using openseries properties metrics = { - 'Total Return': f"{series.value_ret:.2%}", - 'Annual Return': f"{series.geo_ret:.2%}", - 'Volatility': f"{series.vol:.2%}", - 'Sharpe Ratio': f"{series.ret_vol_ratio:.2f}", - 'Max Drawdown': f"{series.max_drawdown:.2%}", - '95% VaR': f"{series.var_down:.2%}", - 'Skewness': f"{series.skew:.2f}", - 'Kurtosis': f"{series.kurtosis:.2f}" + 'Total Return': f"{series.value_ret:.2%}", + 'Annual Return': f"{series.geo_ret:.2%}", + 'Volatility': f"{series.vol:.2%}", + 'Sharpe Ratio': f"{series.ret_vol_ratio:.2f}", + 'Max Drawdown': f"{series.max_drawdown:.2%}", + '95% VaR': f"{series.var_down:.2%}", + 'Skewness': f"{series.skew:.2f}", + 'Kurtosis': f"{series.kurtosis:.2f}" } for metric, value in metrics.items(): - print(f"{metric}: {value}") + print(f"{metric}: {value}") # Export results using openseries to_xlsx method -filename = f"{ticker_symbol.lower()}_analysis.xlsx" +filename = f"{ticker_symbol.lower()}_analysis.xlsx" series.to_xlsx(filename) -print(f"\nResults exported to {filename}") +print(f"\nResults exported to {filename}") # Example: Analyze multiple assets -tickers = ["AAPL", "TSLA", "MSFT"] +tickers = ["AAPL", "TSLA", "MSFT"] for ticker_symbol in tickers: ticker = yf.Ticker(ticker_symbol) - data = ticker.history(period="2y") - series = OpenTimeSeries.from_df(dframe=data['Close']) + data = ticker.history(period="2y") + series = OpenTimeSeries.from_df(dframe=data['Close']) series.set_new_label(lvl_zero=ticker_symbol) - print(f"\n{ticker_symbol}:") - print(f" Return: {series.geo_ret:.2%}") - print(f" Volatility: {series.vol:.2%}") - print(f" Sharpe: {series.ret_vol_ratio:.2f}") + print(f"\n{ticker_symbol}:") + print(f" Return: {series.geo_ret:.2%}") + print(f" Volatility: {series.vol:.2%}") + print(f" Sharpe: {series.ret_vol_ratio:.2f}")

    diff --git a/docs/build/html/index.html b/docs/build/html/index.html index 035452ee..56e77f78 100644 --- a/docs/build/html/index.html +++ b/docs/build/html/index.html @@ -156,18 +156,18 @@

    Quick Startimport yfinance as yf # Download data -ticker = yf.Ticker("^GSPC") -history = ticker.history(period="5y") +ticker = yf.Ticker("^GSPC") +history = ticker.history(period="5y") # Create OpenTimeSeries -series = OpenTimeSeries.from_df(dframe=history.loc[:, "Close"]) -series.set_new_label(lvl_zero="S&P 500") +series = OpenTimeSeries.from_df(dframe=history.loc[:, "Close"]) +series.set_new_label(lvl_zero="S&P 500") # Calculate key metrics -print(f"Annual Return: {series.geo_ret:.2%}") -print(f"Volatility: {series.vol:.2%}") -print(f"Sharpe Ratio: {series.ret_vol_ratio:.2f}") -print(f"Max Drawdown: {series.max_drawdown:.2%}") +print(f"Annual Return: {series.geo_ret:.2%}") +print(f"Volatility: {series.vol:.2%}") +print(f"Sharpe Ratio: {series.ret_vol_ratio:.2f}") +print(f"Max Drawdown: {series.max_drawdown:.2%}") # Create interactive plot series.plot_series() diff --git a/docs/build/html/tutorials/advanced_features.html b/docs/build/html/tutorials/advanced_features.html index ab8eca10..d2a184da 100644 --- a/docs/build/html/tutorials/advanced_features.html +++ b/docs/build/html/tutorials/advanced_features.html @@ -136,18 +136,18 @@

    Multi-Factor Model Analysis# Load factor data (Fama-French factors would be ideal, using proxies here) factor_tickers = { - "^GSPC": "Market", - "^RUT": "Small Cap", # Size factor proxy - "EFA": "International", # International factor - "TLT": "Bonds" # Interest rate factor + "^GSPC": "Market", + "^RUT": "Small Cap", # Size factor proxy + "EFA": "International", # International factor + "TLT": "Bonds" # Interest rate factor } # Load factor data factor_series = [] for ticker, name in factor_tickers.items(): # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="3y") - series = OpenTimeSeries.from_df(dframe=data['Close']) + data = yf.Ticker(ticker).history(period="3y") + series = OpenTimeSeries.from_df(dframe=data['Close']) series.set_new_label(lvl_zero=name) factor_series.append(series) @@ -155,9 +155,9 @@

    Multi-Factor Model Analysisfactors = OpenFrame(constituents=factor_series) # Load individual stock for analysis -stock_data = yf.Ticker("AAPL").history(period="3y") -apple = OpenTimeSeries.from_df(dframe=stock_data['Close']) -apple.set_new_label(lvl_zero="Apple") +stock_data = yf.Ticker("AAPL").history(period="3y") +apple = OpenTimeSeries.from_df(dframe=stock_data['Close']) +apple.set_new_label(lvl_zero="Apple") # Add stock to factor frame for regression analysis_frame = OpenFrame(constituents=factor_series + [apple]) @@ -168,17 +168,17 @@

    Multi-Factor Model Analysisdependent_variable_idx=-1 # Apple is the last series (dependent variable) ) -print("\n=== MULTI-FACTOR REGRESSION RESULTS ===") -print("Regression Summary:") -print(regression_results['summary']) +print("\n=== MULTI-FACTOR REGRESSION RESULTS ===") +print("Regression Summary:") +print(regression_results['summary']) -print("\nFactor Loadings (Betas):") +print("\nFactor Loadings (Betas):") for i, factor_name in enumerate([s.label for s in factor_series]): - beta = regression_results['coefficients'][i+1] # Skip intercept - print(f" {factor_name}: {beta:.4f}") + beta = regression_results['coefficients'][i+1] # Skip intercept + print(f" {factor_name}: {beta:.4f}") -print(f"\nR-squared: {regression_results['r_squared']:.4f}") -print(f"Adjusted R-squared: {regression_results['adj_r_squared']:.4f}") +print(f"\nR-squared: {regression_results['r_squared']:.4f}") +print(f"Adjusted R-squared: {regression_results['adj_r_squared']:.4f}")

    @@ -191,19 +191,19 @@

    Rolling Factor Analysis# Calculate rolling beta rolling_beta = stock_vs_market.rolling_beta(observations=252) # 1-year rolling -print(f"\n=== ROLLING BETA ANALYSIS ===") -print(f"Current Beta: {rolling_beta.iloc[-1, 0]:.3f}") -print(f"Average Beta: {rolling_beta.mean().iloc[0]:.3f}") -print(f"Beta Range: {rolling_beta.min().iloc[0]:.3f} to {rolling_beta.max().iloc[0]:.3f}") -print(f"Beta Volatility: {rolling_beta.std().iloc[0]:.3f}") +print(f"\n=== ROLLING BETA ANALYSIS ===") +print(f"Current Beta: {rolling_beta.iloc[-1, 0]:.3f}") +print(f"Average Beta: {rolling_beta.mean().iloc[0]:.3f}") +print(f"Beta Range: {rolling_beta.min().iloc[0]:.3f} to {rolling_beta.max().iloc[0]:.3f}") +print(f"Beta Volatility: {rolling_beta.std().iloc[0]:.3f}") # Rolling correlation rolling_corr = stock_vs_market.rolling_corr(observations=252) -print(f"\n=== ROLLING CORRELATION ANALYSIS ===") -print(f"Current Correlation: {rolling_corr.iloc[-1, 0]:.3f}") -print(f"Average Correlation: {rolling_corr.mean().iloc[0]:.3f}") -print(f"Correlation Range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}") +print(f"\n=== ROLLING CORRELATION ANALYSIS ===") +print(f"Current Correlation: {rolling_corr.iloc[-1, 0]:.3f}") +print(f"Average Correlation: {rolling_corr.mean().iloc[0]:.3f}") +print(f"Correlation Range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}")

    @@ -222,45 +222,45 @@

    Creating Custom Plots# Create a custom subplot figure fig = make_subplots( rows=2, cols=2, - subplot_titles=('Price Chart', 'Volume', 'Returns Distribution', 'Drawdown'), - specs=[[{"secondary_y": True}, {"type": "bar"}], - [{"type": "histogram"}, {"type": "scatter"}]] + subplot_titles=('Price Chart', 'Volume', 'Returns Distribution', 'Drawdown'), + specs=[[{"secondary_y": True}, {"type": "bar"}], + [{"type": "histogram"}, {"type": "scatter"}]] ) # Add traces (example data) fig.add_trace( - go.Scatter(x=[1, 2, 3, 4], y=[10, 11, 12, 13], name="Price"), + go.Scatter(x=[1, 2, 3, 4], y=[10, 11, 12, 13], name="Price"), row=1, col=1 ) fig.add_trace( - go.Bar(x=[1, 2, 3, 4], y=[100, 200, 150, 300], name="Volume"), + go.Bar(x=[1, 2, 3, 4], y=[100, 200, 150, 300], name="Volume"), row=1, col=2 ) fig.add_trace( - go.Histogram(x=[0.01, -0.02, 0.015, -0.01, 0.02], name="Returns"), + go.Histogram(x=[0.01, -0.02, 0.015, -0.01, 0.02], name="Returns"), row=2, col=1 ) fig.add_trace( - go.Scatter(x=[1, 2, 3, 4], y=[0, -0.05, -0.03, -0.08], name="Drawdown"), + go.Scatter(x=[1, 2, 3, 4], y=[0, -0.05, -0.03, -0.08], name="Drawdown"), row=2, col=2 ) # Update layout -fig.update_layout(height=800, title_text="Custom Multi-Panel Dashboard") +fig.update_layout(height=800, title_text="Custom Multi-Panel Dashboard") # Export to responsive HTML output_path = export_plotly_figure( figure=fig, - fig_config={"responsive": True}, - output_type="file", - filename="custom_dashboard.html", - include_plotlyjs="cdn", - plotfile=Path("output/custom_dashboard.html"), - title="Custom Financial Dashboard", + fig_config={"responsive": True}, + output_type="file", + filename="custom_dashboard.html", + include_plotlyjs="cdn", + plotfile=Path("output/custom_dashboard.html"), + title="Custom Financial Dashboard", auto_open=True, ) -print(f"Dashboard saved to: {output_path}") +print(f"Dashboard saved to: {output_path}")

    @@ -274,27 +274,27 @@

    Using with Plotly Express# Create sample data df = pd.DataFrame({ - 'Date': pd.date_range('2020-01-01', periods=100), - 'Asset_A': 100 + pd.Series(range(100)).cumsum() * 0.1, - 'Asset_B': 100 + pd.Series(range(100)).cumsum() * 0.15, + 'Date': pd.date_range('2020-01-01', periods=100), + 'Asset_A': 100 + pd.Series(range(100)).cumsum() * 0.1, + 'Asset_B': 100 + pd.Series(range(100)).cumsum() * 0.15, }) # Create a Plotly Express figure fig = px.line( - df, x='Date', y=['Asset_A', 'Asset_B'], - title='Asset Comparison', - labels={'value': 'Price', 'variable': 'Asset'} + df, x='Date', y=['Asset_A', 'Asset_B'], + title='Asset Comparison', + labels={'value': 'Price', 'variable': 'Asset'} ) # Export with responsive HTML export_plotly_figure( figure=fig, - fig_config={"responsive": True, "displayModeBar": True}, - output_type="file", - filename="asset_comparison.html", - include_plotlyjs="cdn", - plotfile=Path("output/asset_comparison.html"), - title="Asset Price Comparison", + fig_config={"responsive": True, "displayModeBar": True}, + output_type="file", + filename="asset_comparison.html", + include_plotlyjs="cdn", + plotfile=Path("output/asset_comparison.html"), + title="Asset Price Comparison", auto_open=False, )

    @@ -313,10 +313,10 @@

    Inline HTML Outputhtml_div = export_plotly_figure( figure=fig, fig_config={}, - output_type="div", - filename="my_plot.html", - include_plotlyjs="cdn", - plotfile=Path("dummy.html"), # Ignored for div output + output_type="div", + filename="my_plot.html", + include_plotlyjs="cdn", + plotfile=Path("dummy.html"), # Ignored for div output ) # html_div can now be embedded in HTML documents diff --git a/docs/build/html/tutorials/basic_analysis.html b/docs/build/html/tutorials/basic_analysis.html index da40b8cd..916f9ae9 100644 --- a/docs/build/html/tutorials/basic_analysis.html +++ b/docs/build/html/tutorials/basic_analysis.html @@ -141,19 +141,19 @@

    Setting Upfrom datetime import date, datetime # Download S&P 500 data for the last 5 years -ticker = yf.Ticker("^GSPC") -data = ticker.history(period="5y") +ticker = yf.Ticker("^GSPC") +data = ticker.history(period="5y") # Create OpenTimeSeries sp500 = OpenTimeSeries.from_df( - dframe=data['Close'] + dframe=data['Close'] ) # Set a descriptive label -sp500.set_new_label(lvl_zero="S&P 500 Index") +sp500.set_new_label(lvl_zero="S&P 500 Index") -print(f"Loaded {sp500.length} observations") -print(f"Date range: {sp500.first_idx} to {sp500.last_idx}") +print(f"Loaded {sp500.length} observations") +print(f"Date range: {sp500.first_idx} to {sp500.last_idx}")

    @@ -162,20 +162,20 @@

    Basic Performance Metrics
    # Total return over the period
     total_return = sp500.value_ret
    -print(f"Total Return: {total_return:.2%}")
    +print(f"Total Return: {total_return:.2%}")
     
     # Annualized return (CAGR)
     annual_return = sp500.geo_ret
    -print(f"Annualized Return (CAGR): {annual_return:.2%}")
    +print(f"Annualized Return (CAGR): {annual_return:.2%}")
     
     # Arithmetic mean return
     arithmetic_return = sp500.arithmetic_ret
    -print(f"Arithmetic Mean Return: {arithmetic_return:.2%}")
    +print(f"Arithmetic Mean Return: {arithmetic_return:.2%}")
     
     # Time period analysis
    -print(f"Investment period: {sp500.yearfrac:.2f} years")
    -print(f"Number of observations: {sp500.length}")
    -print(f"Periods per year: {sp500.periods_in_a_year:.1f}")
    +print(f"Investment period: {sp500.yearfrac:.2f} years")
    +print(f"Number of observations: {sp500.length}")
    +print(f"Periods per year: {sp500.periods_in_a_year:.1f}")
     

    @@ -184,23 +184,23 @@

    Risk Analysis
    # Volatility (annualized standard deviation)
     volatility = sp500.vol
    -print(f"Annualized Volatility: {volatility:.2%}")
    +print(f"Annualized Volatility: {volatility:.2%}")
     
     # Downside deviation (volatility of negative returns only)
     downside_vol = sp500.downside_deviation
    -print(f"Downside Deviation: {downside_vol:.2%}")
    +print(f"Downside Deviation: {downside_vol:.2%}")
     
     # Value at Risk (95% confidence level)
     var_95 = sp500.var_down
    -print(f"95% Value at Risk (daily): {var_95:.2%}")
    +print(f"95% Value at Risk (daily): {var_95:.2%}")
     
     # Conditional Value at Risk (Expected Shortfall)
     cvar_95 = sp500.cvar_down
    -print(f"95% CVaR (daily): {cvar_95:.2%}")
    +print(f"95% CVaR (daily): {cvar_95:.2%}")
     
     # Maximum single-day loss
     worst_day = sp500.worst
    -print(f"Worst single day: {worst_day:.2%}")
    +print(f"Worst single day: {worst_day:.2%}")
     

    @@ -209,19 +209,19 @@

    Risk-Adjusted ReturnsCalculate risk-adjusted performance metrics:

    # Sharpe Ratio (return per unit of total risk)
     sharpe_ratio = sp500.ret_vol_ratio
    -print(f"Sharpe Ratio: {sharpe_ratio:.2f}")
    +print(f"Sharpe Ratio: {sharpe_ratio:.2f}")
     
     # Sortino Ratio (return per unit of downside risk)
     sortino_ratio = sp500.sortino_ratio
    -print(f"Sortino Ratio: {sortino_ratio:.2f}")
    +print(f"Sortino Ratio: {sortino_ratio:.2f}")
     
     # Kappa-3 Ratio (penalizes larger downside deviations more)
     kappa3_ratio = sp500.kappa3_ratio
    -print(f"Kappa-3 Ratio: {kappa3_ratio:.2f}")
    +print(f"Kappa-3 Ratio: {kappa3_ratio:.2f}")
     
     # Omega Ratio
     omega_ratio = sp500.omega_ratio
    -print(f"Omega Ratio: {omega_ratio:.2f}")
    +print(f"Omega Ratio: {omega_ratio:.2f}")
     
    @@ -231,8 +231,8 @@

    Drawdown Analysis

    @@ -255,30 +255,30 @@

    Distribution Analysis# Note: value_to_ret() modifies the original series in place # Restore the original series for further analysis -sp500 = OpenTimeSeries.from_df(dframe=data['Close']) -sp500.set_new_label(lvl_zero="S&P 500 Index") +sp500 = OpenTimeSeries.from_df(dframe=data['Close']) +sp500.set_new_label(lvl_zero="S&P 500 Index") # Skewness (asymmetry of the distribution) skewness = sp500.skew -print(f"Skewness: {skewness:.2f}") +print(f"Skewness: {skewness:.2f}") if skewness < 0: - print(" → Negative skew: more extreme negative returns") + print(" → Negative skew: more extreme negative returns") elif skewness > 0: - print(" → Positive skew: more extreme positive returns") + print(" → Positive skew: more extreme positive returns") # Kurtosis (tail heaviness) kurtosis = sp500.kurtosis -print(f"Kurtosis: {kurtosis:.2f}") +print(f"Kurtosis: {kurtosis:.2f}") if kurtosis > 3: - print(" → Fat tails: more extreme returns than normal distribution") + print(" → Fat tails: more extreme returns than normal distribution") # Percentage of positive days positive_share = sp500.positive_share -print(f"Positive Days: {positive_share:.1%}") +print(f"Positive Days: {positive_share:.1%}") # Current Z-score (how unusual is the last return?) z_score = sp500.z_score -print(f"Last Return Z-score: {z_score:.2f}") +print(f"Last Return Z-score: {z_score:.2f}")

    @@ -286,22 +286,22 @@

    Distribution AnalysisMonthly and Annual Analysis

    Break down performance by different time periods:

    # Resample to monthly data (modifies original)
    -sp500.resample_to_business_period_ends(freq="BME")
    -print(f"Monthly observations: {sp500.length}")
    +sp500.resample_to_business_period_ends(freq="BME")
    +print(f"Monthly observations: {sp500.length}")
     
     # Monthly metrics
     monthly_return = sp500.geo_ret
     monthly_vol = sp500.vol
    -print(f"Monthly Return (annualized): {monthly_return:.2%}")
    -print(f"Monthly Volatility (annualized): {monthly_vol:.2%}")
    +print(f"Monthly Return (annualized): {monthly_return:.2%}")
    +print(f"Monthly Volatility (annualized): {monthly_vol:.2%}")
     
     # Worst month
     worst_month = sp500.worst_month
    -print(f"Worst Month: {worst_month:.2%}")
    +print(f"Worst Month: {worst_month:.2%}")
     
     # Annual data (modifies original)
    -sp500.resample_to_business_period_ends(freq="BYE")
    -print(f"Annual observations: {sp500.length}")
    +sp500.resample_to_business_period_ends(freq="BYE")
    +print(f"Annual observations: {sp500.length}")
     
    @@ -312,7 +312,7 @@

    Calendar Year Returnsfor year in years: # This may fail if no data exists for the year year_return = sp500.value_ret_calendar_period(year=year) - print(f"{year}: {year_return:.2%}") + print(f"{year}: {year_return:.2%}")

    @@ -322,20 +322,20 @@

    Rolling Analysis
    # 252-day (1-year) rolling volatility
     rolling_vol = sp500.rolling_vol(observations=252)
    -print(f"Rolling volatility calculated for {len(rolling_vol)} periods")
    +print(f"Rolling volatility calculated for {len(rolling_vol)} periods")
     
     # 30-day rolling returns
     rolling_returns = sp500.rolling_return(observations=30)
     
     # Plot rolling volatility
     # Convert to OpenTimeSeries for plotting
    -vol_dates = rolling_vol.index.strftime('%Y-%m-%d').tolist()
    +vol_dates = rolling_vol.index.strftime('%Y-%m-%d').tolist()
     vol_values = rolling_vol.iloc[:, 0].tolist()
     
     vol_series = OpenTimeSeries.from_arrays(
          dates=vol_dates,
          values=vol_values,
    -     name="Rolling Volatility"
    +     name="Rolling Volatility"
     )
     
     vol_series.plot_series()
    @@ -347,12 +347,12 @@ 

    Comprehensive ReportGet all metrics at once:

    # Generate comprehensive metrics report
     all_metrics = sp500.all_properties()
    -print("\n=== COMPREHENSIVE ANALYSIS REPORT ===")
    +print("\n=== COMPREHENSIVE ANALYSIS REPORT ===")
     print(all_metrics)
     
     # Save to Excel for further analysis
    -sp500.to_xlsx(filename="sp500_analysis.xlsx")
    -all_metrics.to_excel(excel_writer="sp500_metrics.xlsx", engine="openpyxl")
    +sp500.to_xlsx(filename="sp500_analysis.xlsx")
    +all_metrics.to_excel(excel_writer="sp500_metrics.xlsx", engine="openpyxl")
     
    @@ -378,26 +378,26 @@

    Visualization

    Let’s compare with a bond index:

    # Download bond data (10-year Treasury)
    -bond_ticker = yf.Ticker("^TNX")
    -bond_data = bond_ticker.history(period="5y")
    +bond_ticker = yf.Ticker("^TNX")
    +bond_data = bond_ticker.history(period="5y")
     
     # Create bond series (using yield data)
     bonds = OpenTimeSeries.from_df(
    -     dframe=bond_data['Close']
    +     dframe=bond_data['Close']
     )
    -bonds.set_new_label(lvl_zero="10Y Treasury Yield")
    +bonds.set_new_label(lvl_zero="10Y Treasury Yield")
     
     # Create frame for comparison
     comparison_frame = OpenFrame(constituents=[sp500, bonds])
     
     # Compare metrics
     comparison_metrics = comparison_frame.all_properties()
    -print("\n=== ASSET COMPARISON ===")
    +print("\n=== ASSET COMPARISON ===")
     print(comparison_metrics)
     
     # Calculate correlation
     correlation_matrix = comparison_frame.correl_matrix
    -print("\n=== CORRELATION MATRIX ===")
    +print("\n=== CORRELATION MATRIX ===")
     print(correlation_matrix)
     
    @@ -410,48 +410,48 @@

    Advanced Risk Metricsvar_95 = sp500.var_down_func(level=0.95) var_99 = sp500.var_down_func(level=0.99) -print(f"90% VaR: {var_90:.2%}") -print(f"95% VaR: {var_95:.2%}") -print(f"99% VaR: {var_99:.2%}") +print(f"90% VaR: {var_90:.2%}") +print(f"95% VaR: {var_95:.2%}") +print(f"99% VaR: {var_99:.2%}") # CVaR at different confidence levels cvar_90 = sp500.cvar_down_func(level=0.90) cvar_95 = sp500.cvar_down_func(level=0.95) cvar_99 = sp500.cvar_down_func(level=0.99) -print(f"90% CVaR: {cvar_90:.2%}") -print(f"95% CVaR: {cvar_95:.2%}") -print(f"99% CVaR: {cvar_99:.2%}") +print(f"90% CVaR: {cvar_90:.2%}") +print(f"95% CVaR: {cvar_95:.2%}") +print(f"99% CVaR: {cvar_99:.2%}") # Implied volatility from VaR (assuming normal distribution) vol_from_var = sp500.vol_from_var -print(f"Volatility implied from VaR: {vol_from_var:.2%}") -print(f"Actual volatility: {sp500.vol:.2%}") +print(f"Volatility implied from VaR: {vol_from_var:.2%}") +print(f"Actual volatility: {sp500.vol:.2%}")

    Summary and Interpretation

    -
    print("\n=== INVESTMENT SUMMARY ===")
    -print(f"Asset: {sp500.label}")
    -print(f"Period: {sp500.first_idx} to {sp500.last_idx}")
    -print(f"Total Return: {sp500.value_ret:.2%}")
    -print(f"Annualized Return: {sp500.geo_ret:.2%}")
    -print(f"Annualized Volatility: {sp500.vol:.2%}")
    -print(f"Sharpe Ratio: {sp500.ret_vol_ratio:.2f}")
    -print(f"Maximum Drawdown: {sp500.max_drawdown:.2%}")
    -print(f"95% VaR (daily): {sp500.var_down:.2%}")
    +
    print("\n=== INVESTMENT SUMMARY ===")
    +print(f"Asset: {sp500.label}")
    +print(f"Period: {sp500.first_idx} to {sp500.last_idx}")
    +print(f"Total Return: {sp500.value_ret:.2%}")
    +print(f"Annualized Return: {sp500.geo_ret:.2%}")
    +print(f"Annualized Volatility: {sp500.vol:.2%}")
    +print(f"Sharpe Ratio: {sp500.ret_vol_ratio:.2f}")
    +print(f"Maximum Drawdown: {sp500.max_drawdown:.2%}")
    +print(f"95% VaR (daily): {sp500.var_down:.2%}")
     
     # Risk assessment
     if sp500.ret_vol_ratio > 1.0:
    -     print("✓ Good risk-adjusted returns (Sharpe > 1.0)")
    +     print("✓ Good risk-adjusted returns (Sharpe > 1.0)")
     else:
    -     print("⚠ Moderate risk-adjusted returns (Sharpe < 1.0)")
    +     print("⚠ Moderate risk-adjusted returns (Sharpe < 1.0)")
     
     if abs(sp500.max_drawdown) < 0.20:
    -     print("✓ Moderate maximum drawdown (< 20%)")
    +     print("✓ Moderate maximum drawdown (< 20%)")
     else:
    -     print("⚠ Significant maximum drawdown (> 20%)")
    +     print("⚠ Significant maximum drawdown (> 20%)")
     

    This tutorial provides a comprehensive foundation for financial analysis using openseries. You can adapt these techniques for any financial time series data.

    diff --git a/docs/build/html/tutorials/portfolio_analysis.html b/docs/build/html/tutorials/portfolio_analysis.html index d0a1366b..5fd7ee4d 100644 --- a/docs/build/html/tutorials/portfolio_analysis.html +++ b/docs/build/html/tutorials/portfolio_analysis.html @@ -151,31 +151,31 @@

    Setting Up the Data# Define our universe of assets tickers = { - "^GSPC": "S&P 500", - "EFA": "EAFE International", - "EEM": "Emerging Markets", - "AGG": "US Aggregate Bonds", - "VNQ": "US REITs", - "GLD": "Gold", - "DBC": "Commodities" + "^GSPC": "S&P 500", + "EFA": "EAFE International", + "EEM": "Emerging Markets", + "AGG": "US Aggregate Bonds", + "VNQ": "US REITs", + "GLD": "Gold", + "DBC": "Commodities" } # Download 5 years of data series_list = [] for ticker, name in tickers.items(): # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="5y") + data = yf.Ticker(ticker).history(period="5y") series = OpenTimeSeries.from_df( - dframe=data['Close'] + dframe=data['Close'] ) series.set_new_label(lvl_zero=name) series_list.append(series) - print(f"Loaded {name}: {series.length} observations") + print(f"Loaded {name}: {series.length} observations") # Create OpenFrame assets = OpenFrame(constituents=series_list) -print(f"\nCreated frame with {assets.item_count} assets") -print(f"Common date range: {assets.first_idx} to {assets.last_idx}") +print(f"\nCreated frame with {assets.item_count} assets") +print(f"Common date range: {assets.first_idx} to {assets.last_idx}")

    @@ -184,22 +184,22 @@

    Asset Analysis
    # Get metrics for all assets
     asset_metrics = assets.all_properties()
    -print("=== INDIVIDUAL ASSET METRICS ===")
    +print("=== INDIVIDUAL ASSET METRICS ===")
     print(asset_metrics)
     
     # Key metrics comparison
    -returns = asset_metrics.loc['Geometric return']
    -volatilities = asset_metrics.loc['Volatility']
    -sharpe_ratios = asset_metrics.loc['Return vol ratio']
    -max_drawdowns = asset_metrics.loc['Max drawdown']
    +returns = asset_metrics.loc['Geometric return']
    +volatilities = asset_metrics.loc['Volatility']
    +sharpe_ratios = asset_metrics.loc['Return vol ratio']
    +max_drawdowns = asset_metrics.loc['Max drawdown']
     
    -print("\n=== ASSET COMPARISON ===")
    +print("\n=== ASSET COMPARISON ===")
     for asset in returns.index:
    -    print(f"{asset}:")
    -    print(f"  Annual Return: {returns[asset]:.2%}")
    -    print(f"  Volatility: {volatilities[asset]:.2%}")
    -    print(f"  Sharpe Ratio: {sharpe_ratios[asset]:.2f}")
    -    print(f"  Max Drawdown: {max_drawdowns[asset]:.2%}")
    +    print(f"{asset}:")
    +    print(f"  Annual Return: {returns[asset]:.2%}")
    +    print(f"  Volatility: {volatilities[asset]:.2%}")
    +    print(f"  Sharpe Ratio: {sharpe_ratios[asset]:.2f}")
    +    print(f"  Max Drawdown: {max_drawdowns[asset]:.2%}")
     

    @@ -208,24 +208,24 @@

    Correlation AnalysisUnderstanding correlations is crucial for portfolio construction:

    # Calculate correlation matrix
     correlation_matrix = assets.correl_matrix
    -print("\n=== CORRELATION MATRIX ===")
    +print("\n=== CORRELATION MATRIX ===")
     print(correlation_matrix.round(3))
     
     # Identify highly correlated pairs
    -print("\n=== HIGHLY CORRELATED PAIRS (>0.7) ===")
    +print("\n=== HIGHLY CORRELATED PAIRS (>0.7) ===")
     for i in range(len(correlation_matrix.columns)):
          for j in range(i+1, len(correlation_matrix.columns)):
               corr = correlation_matrix.iloc[i, j]
               if abs(corr) > 0.7:
                     asset1 = correlation_matrix.columns[i]
                     asset2 = correlation_matrix.columns[j]
    -                print(f"{asset1} - {asset2}: {corr:.3f}")
    +                print(f"{asset1} - {asset2}: {corr:.3f}")
     
     # Average correlation with other assets
     avg_correlations = correlation_matrix.mean()
    -print("\n=== AVERAGE CORRELATIONS ===")
    +print("\n=== AVERAGE CORRELATIONS ===")
     for asset, avg_corr in avg_correlations.items():
    -     print(f"{asset}: {avg_corr:.3f}")
    +     print(f"{asset}: {avg_corr:.3f}")
     
    @@ -235,12 +235,12 @@

    Simple Portfolio Construction

    Equal Weight Portfolio

    # Create equal-weighted portfolio using native weight_strat
    -portfolio_df = assets.make_portfolio(name="Equal Weight Portfolio", weight_strat="eq_weights")
    +portfolio_df = assets.make_portfolio(name="Equal Weight Portfolio", weight_strat="eq_weights")
     equal_weight_portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
     
    -print(f"Equal Weight Portfolio Return: {equal_weight_portfolio.geo_ret:.2%}")
    -print(f"Equal Weight Portfolio Volatility: {equal_weight_portfolio.vol:.2%}")
    -print(f"Equal Weight Portfolio Sharpe: {equal_weight_portfolio.ret_vol_ratio:.2f}")
    +print(f"Equal Weight Portfolio Return: {equal_weight_portfolio.geo_ret:.2%}")
    +print(f"Equal Weight Portfolio Volatility: {equal_weight_portfolio.vol:.2%}")
    +print(f"Equal Weight Portfolio Sharpe: {equal_weight_portfolio.ret_vol_ratio:.2f}")
     
    @@ -251,24 +251,24 @@

    Custom Weight Portfoliocustom_weights = [0.50, 0.15, 0.10, 0.15, 0.05, 0.03, 0.02] assets.weights = custom_weights -portfolio_df = assets.make_portfolio(name="Custom Weighted") +portfolio_df = assets.make_portfolio(name="Custom Weighted") custom_portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) -print(f"Custom Portfolio Return: {custom_portfolio.geo_ret:.2%}") -print(f"Custom Portfolio Volatility: {custom_portfolio.vol:.2%}") -print(f"Custom Portfolio Sharpe: {custom_portfolio.ret_vol_ratio:.2f}") +print(f"Custom Portfolio Return: {custom_portfolio.geo_ret:.2%}") +print(f"Custom Portfolio Volatility: {custom_portfolio.vol:.2%}") +print(f"Custom Portfolio Sharpe: {custom_portfolio.ret_vol_ratio:.2f}")

    Risk Parity Portfolio

    # Use native inverse volatility weighting (risk parity)
    -portfolio_df = assets.make_portfolio(name="Risk Parity", weight_strat="inv_vol")
    +portfolio_df = assets.make_portfolio(name="Risk Parity", weight_strat="inv_vol")
     risk_parity_portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
     
    -print(f"Risk Parity Portfolio Return: {risk_parity_portfolio.geo_ret:.2%}")
    -print(f"Risk Parity Portfolio Volatility: {risk_parity_portfolio.vol:.2%}")
    -print(f"Risk Parity Portfolio Sharpe: {risk_parity_portfolio.ret_vol_ratio:.2f}")
    +print(f"Risk Parity Portfolio Return: {risk_parity_portfolio.geo_ret:.2%}")
    +print(f"Risk Parity Portfolio Volatility: {risk_parity_portfolio.vol:.2%}")
    +print(f"Risk Parity Portfolio Sharpe: {risk_parity_portfolio.ret_vol_ratio:.2f}")
     
    @@ -282,14 +282,14 @@

    Maximum Diversification Strategy# This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError max_div_portfolio_df = assets.make_portfolio( - name="Maximum Diversification", - weight_strat="max_div" + name="Maximum Diversification", + weight_strat="max_div" ) max_div_portfolio = OpenTimeSeries.from_df(dframe=max_div_portfolio_df) -print(f"Max Diversification Return: {max_div_portfolio.geo_ret:.2%}") -print(f"Max Diversification Volatility: {max_div_portfolio.vol:.2%}") -print(f"Max Diversification Sharpe: {max_div_portfolio.ret_vol_ratio:.2f}") +print(f"Max Diversification Return: {max_div_portfolio.geo_ret:.2%}") +print(f"Max Diversification Volatility: {max_div_portfolio.vol:.2%}") +print(f"Max Diversification Sharpe: {max_div_portfolio.ret_vol_ratio:.2f}")

    @@ -298,14 +298,14 @@

    Minimum Volatility Overweight Strategy
    # This may fail with various exceptions
     min_vol_portfolio_df = assets.make_portfolio(
    -     name="Min Vol Overweight",
    -     weight_strat="min_vol_overweight"
    +     name="Min Vol Overweight",
    +     weight_strat="min_vol_overweight"
     )
     min_vol_portfolio = OpenTimeSeries.from_df(dframe=min_vol_portfolio_df)
     
    -print(f"Min Vol Overweight Return: {min_vol_portfolio.geo_ret:.2%}")
    -print(f"Min Vol Overweight Volatility: {min_vol_portfolio.vol:.2%}")
    -print(f"Min Vol Overweight Sharpe: {min_vol_portfolio.ret_vol_ratio:.2f}")
    +print(f"Min Vol Overweight Return: {min_vol_portfolio.geo_ret:.2%}")
    +print(f"Min Vol Overweight Volatility: {min_vol_portfolio.vol:.2%}")
    +print(f"Min Vol Overweight Sharpe: {min_vol_portfolio.ret_vol_ratio:.2f}")
     

    @@ -313,10 +313,10 @@

    Minimum Volatility Overweight Strategy

    When comparing multiple strategies, it’s important to handle potential failures gracefully:

    @@ -356,25 +356,25 @@

    Efficient Frontierseed=42 ) -print("Efficient frontier calculated successfully") -print(f"Number of frontier points: {len(frontier_df)}") -print(f"Number of simulated portfolios: {len(simulated_df)}") +print("Efficient frontier calculated successfully") +print(f"Number of frontier points: {len(frontier_df)}") +print(f"Number of simulated portfolios: {len(simulated_df)}") # Find maximum Sharpe ratio portfolio -sharpe_ratios = frontier_df['ret'] / frontier_df['stdev'] +sharpe_ratios = frontier_df['ret'] / frontier_df['stdev'] max_sharpe_idx = sharpe_ratios.idxmax() -print(f"\n=== MAXIMUM SHARPE RATIO PORTFOLIO ===") -print(f"Expected Return: {frontier_df.iloc[max_sharpe_idx]['ret']:.2%}") -print(f"Volatility: {frontier_df.iloc[max_sharpe_idx]['stdev']:.2%}") -print(f"Sharpe Ratio: {sharpe_ratios.iloc[max_sharpe_idx]:.2f}") +print(f"\n=== MAXIMUM SHARPE RATIO PORTFOLIO ===") +print(f"Expected Return: {frontier_df.iloc[max_sharpe_idx]['ret']:.2%}") +print(f"Volatility: {frontier_df.iloc[max_sharpe_idx]['stdev']:.2%}") +print(f"Sharpe Ratio: {sharpe_ratios.iloc[max_sharpe_idx]:.2f}") # Get optimal weights optimal_weights = optimal_portfolio[-len(assets.constituents):] -print("\nOptimal Weights:") +print("\nOptimal Weights:") for i, weight in enumerate(optimal_weights): asset_name = assets.constituents[i].label - print(f" {asset_name}: {weight:.1%}") + print(f" {asset_name}: {weight:.1%}")

    @@ -388,21 +388,21 @@

    Monte Carlo Portfolio Simulationseed=42 ) -print(f"\nSimulated {len(simulation_results)} random portfolios") +print(f"\nSimulated {len(simulation_results)} random portfolios") # Find best performing portfolios -sim_sharpe_ratios = simulation_results['ret'] / simulation_results['stdev'] +sim_sharpe_ratios = simulation_results['ret'] / simulation_results['stdev'] # Top 5 Sharpe ratios sorted_indices = sorted(range(len(sim_sharpe_ratios)), key=lambda i: sim_sharpe_ratios.iloc[i], reverse=True) top_indices = sorted_indices[:5] -print("\n=== TOP 5 SIMULATED PORTFOLIOS ===") +print("\n=== TOP 5 SIMULATED PORTFOLIOS ===") for i, idx in enumerate(top_indices, 1): - print(f"\nRank {i}:") - print(f" Return: {simulation_results.iloc[idx]['ret']:.2%}") - print(f" Volatility: {simulation_results.iloc[idx]['stdev']:.2%}") - print(f" Sharpe: {sim_sharpe_ratios.iloc[idx]:.2f}") + print(f"\nRank {i}:") + print(f" Return: {simulation_results.iloc[idx]['ret']:.2%}") + print(f" Volatility: {simulation_results.iloc[idx]['stdev']:.2%}") + print(f" Sharpe: {sim_sharpe_ratios.iloc[idx]:.2f}")

    @@ -421,10 +421,10 @@

    Portfolio Comparisonportfolio_metrics = comparison_frame.all_properties() # Focus on key metrics -key_metrics = portfolio_metrics.loc[['Geometric return', 'Volatility', 'Return vol ratio', 'Max drawdown']] -key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] +key_metrics = portfolio_metrics.loc[['Geometric return', 'Volatility', 'Return vol ratio', 'Max drawdown']] +key_metrics.index = ['Annual Return', 'Volatility', 'Sharpe Ratio', 'Max Drawdown'] -print("\n=== PORTFOLIO COMPARISON ===") +print("\n=== PORTFOLIO COMPARISON ===") print((key_metrics * 100).round(2)) # Convert to percentages

    @@ -434,21 +434,21 @@

    Risk Attribution
    # Calculate portfolio statistics using openseries methods
     # Create equal weight portfolio
    -equal_weight_portfolio_df = assets.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
    +equal_weight_portfolio_df = assets.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
     equal_weight_portfolio = OpenTimeSeries.from_df(dframe=equal_weight_portfolio_df)
     
    -print("\n=== RISK ATTRIBUTION (Equal Weight Portfolio) ===")
    -print(f"Portfolio Volatility: {equal_weight_portfolio.vol:.4f}")
    -print(f"Portfolio Return: {equal_weight_portfolio.geo_ret:.4f}")
    +print("\n=== RISK ATTRIBUTION (Equal Weight Portfolio) ===")
    +print(f"Portfolio Volatility: {equal_weight_portfolio.vol:.4f}")
    +print(f"Portfolio Return: {equal_weight_portfolio.geo_ret:.4f}")
     
     # Individual asset contributions can be analyzed using openseries properties
     for i, series in enumerate(assets.constituents):
         weight = equal_weights[i]
         asset_vol = series.vol
    -    print(f"\n{series.label}:")
    -    print(f"  Weight: {weight:.4f}")
    -    print(f"  Individual Volatility: {asset_vol:.4f}")
    -    print(f"  Weighted Contribution: {weight * asset_vol:.4f}")
    +    print(f"\n{series.label}:")
    +    print(f"  Weight: {weight:.4f}")
    +    print(f"  Individual Volatility: {asset_vol:.4f}")
    +    print(f"  Weighted Contribution: {weight * asset_vol:.4f}")
     

    @@ -457,24 +457,24 @@

    Performance AttributionAnalyze performance contribution over time:

    # Calculate performance attribution using openseries
     # Individual asset performance is available through openseries properties
    -print("\n=== PERFORMANCE ATTRIBUTION ===")
    +print("\n=== PERFORMANCE ATTRIBUTION ===")
     for i, series in enumerate(assets.constituents):
         weight = equal_weights[i]
         asset_return = series.geo_ret
         contribution = weight * asset_return
    -    print(f"{series.label}:")
    -    print(f"  Weight: {weight:.2%}")
    -    print(f"  Return: {asset_return:.2%}")
    -    print(f"  Contribution: {contribution:.2%}")
    +    print(f"{series.label}:")
    +    print(f"  Weight: {weight:.2%}")
    +    print(f"  Return: {asset_return:.2%}")
    +    print(f"  Contribution: {contribution:.2%}")
     
     # Cumulative contribution
     cumulative_contrib = (1 + weighted_returns).cumprod()
     
    -print("\n=== PERFORMANCE ATTRIBUTION ===")
    -print("Final cumulative contribution by asset:")
    +print("\n=== PERFORMANCE ATTRIBUTION ===")
    +print("Final cumulative contribution by asset:")
     final_contrib = cumulative_contrib.iloc[-1]
     for asset, contrib in final_contrib.items():
    -     print(f"  {asset}: {contrib:.3f}")
    +     print(f"  {asset}: {contrib:.3f}")
     
    @@ -490,16 +490,16 @@

    Rolling Portfolio Analysis# Calculate rolling correlation rolling_corr = portfolio_vs_market.rolling_corr(observations=252) # 1-year rolling -print(f"\nRolling correlation calculated for {len(rolling_corr)} periods") -print(f"Average correlation: {rolling_corr.mean().iloc[0]:.3f}") -print(f"Correlation range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}") +print(f"\nRolling correlation calculated for {len(rolling_corr)} periods") +print(f"Average correlation: {rolling_corr.mean().iloc[0]:.3f}") +print(f"Correlation range: {rolling_corr.min().iloc[0]:.3f} to {rolling_corr.max().iloc[0]:.3f}") # Rolling portfolio volatility portfolio_rolling_vol = equal_weight_portfolio.rolling_vol(observations=252) -print(f"\nRolling volatility statistics:") -print(f"Average volatility: {portfolio_rolling_vol.mean().iloc[0]:.2%}") -print(f"Volatility range: {portfolio_rolling_vol.min().iloc[0]:.2%} to {portfolio_rolling_vol.max().iloc[0]:.2%}") +print(f"\nRolling volatility statistics:") +print(f"Average volatility: {portfolio_rolling_vol.mean().iloc[0]:.2%}") +print(f"Volatility range: {portfolio_rolling_vol.min().iloc[0]:.2%} to {portfolio_rolling_vol.max().iloc[0]:.2%}")

    @@ -508,13 +508,13 @@

    Rebalancing AnalysisAnalyze the impact of rebalancing frequency using the realistic rebalanced_portfolio method:

    # Compare different rebalancing frequencies using realistic simulation
     frequencies = [1, 21, 63]  # Daily, monthly, quarterly
    -frequency_names = ["Daily", "Monthly", "Quarterly"]
    +frequency_names = ["Daily", "Monthly", "Quarterly"]
     
     rebalanced_portfolios = []
     
     for freq, name in zip(frequencies, frequency_names):
          portfolio = assets.rebalanced_portfolio(
    -          name=f"{name} Rebalanced",
    +          name=f"{name} Rebalanced",
               frequency=freq,
               bal_weights=equal_weights
          )
    @@ -522,7 +522,7 @@ 

    Rebalancing Analysis# Compare with theoretical portfolio assets.weights = equal_weights -theoretical_portfolio_df = assets.make_portfolio(name="Theoretical") +theoretical_portfolio_df = assets.make_portfolio(name="Theoretical") theoretical_portfolio = OpenTimeSeries.from_df(dframe=theoretical_portfolio_df) # Create comprehensive comparison @@ -530,23 +530,23 @@

    Rebalancing Analysiscomparison_frame = OpenFrame(constituents=all_portfolios) comparison_metrics = comparison_frame.all_properties() -print("\n=== REALISTIC REBALANCING COMPARISON ===") -print("Strategy | Return | Volatility | Sharpe | Max DD") -print("-" * 50) +print("\n=== REALISTIC REBALANCING COMPARISON ===") +print("Strategy | Return | Volatility | Sharpe | Max DD") +print("-" * 50) for series in all_portfolios: - ret = comparison_metrics.loc['Geometric return', series.label].iloc[0] * 100 - vol = comparison_metrics.loc['Volatility', series.label].iloc[0] * 100 - sharpe = comparison_metrics.loc['Return vol ratio', series.label].iloc[0] - max_dd = comparison_metrics.loc['Max drawdown', series.label].iloc[0] * 100 + ret = comparison_metrics.loc['Geometric return', series.label].iloc[0] * 100 + vol = comparison_metrics.loc['Volatility', series.label].iloc[0] * 100 + sharpe = comparison_metrics.loc['Return vol ratio', series.label].iloc[0] + max_dd = comparison_metrics.loc['Max drawdown', series.label].iloc[0] * 100 - print(f"{series.label:>15} | {ret:6.2f}% | {vol:10.2f}% | {sharpe:6.2f} | {max_dd:6.2f}%") + print(f"{series.label:>15} | {ret:6.2f}% | {vol:10.2f}% | {sharpe:6.2f} | {max_dd:6.2f}%") # Analyze transaction costs -print(f"\n=== TRANSACTION COST ANALYSIS ===") +print(f"\n=== TRANSACTION COST ANALYSIS ===") for freq, name in zip(frequencies, frequency_names): detailed_portfolio = assets.rebalanced_portfolio( - name=f"{name} Detailed", + name=f"{name} Detailed", frequency=freq, bal_weights=equal_weights, drop_extras=False # Get detailed trading data @@ -555,12 +555,12 @@

    Rebalancing Analysis# Count rebalancing events rebalancing_days = 0 for series in detailed_portfolio.constituents: - if "buysell_qty" in series.label: + if "buysell_qty" in series.label: # Count days with non-zero trading trading_days = (series.tsdf != 0).any(axis=1).sum() rebalancing_days = max(rebalancing_days, trading_days) - print(f"{name:>15}: {rebalancing_days} rebalancing events") + print(f"{name:>15}: {rebalancing_days} rebalancing events")

    @@ -575,9 +575,9 @@

    Stress Testingworst_days_threshold = market_returns_df.quantile(0.05).iloc[0] worst_days = market_returns_df[market_returns_df <= worst_days_threshold] -print(f"\n=== STRESS TEST RESULTS ===") -print(f"Market stress threshold: {worst_days_threshold:.2%}") -print(f"Number of stress days: {len(worst_days)}") +print(f"\n=== STRESS TEST RESULTS ===") +print(f"Market stress threshold: {worst_days_threshold:.2%}") +print(f"Number of stress days: {len(worst_days)}") # Portfolio performance during stress (modifies original) equal_weight_portfolio.value_to_ret() @@ -587,41 +587,41 @@

    Stress Testingstress_dates = worst_days.index portfolio_stress_returns = portfolio_returns_df.loc[stress_dates] -print(f"Portfolio average return during stress: {portfolio_stress_returns.mean().iloc[0]:.2%}") -print(f"Portfolio worst day during stress: {portfolio_stress_returns.min().iloc[0]:.2%}") +print(f"Portfolio average return during stress: {portfolio_stress_returns.mean().iloc[0]:.2%}") +print(f"Portfolio worst day during stress: {portfolio_stress_returns.min().iloc[0]:.2%}")

    Summary Report

    Generate a comprehensive portfolio analysis report:

    -
    print("\n" + "="*60)
    -print("PORTFOLIO ANALYSIS SUMMARY REPORT")
    -print("="*60)
    -
    -print(f"\nAnalysis Period: {assets.first_idx} to {assets.last_idx}")
    -print(f"Number of Assets: {assets.item_count}")
    -print(f"Asset Universe: {', '.join([s.label for s in assets.constituents])}")
    -
    -print(f"\n--- EQUAL WEIGHT PORTFOLIO PERFORMANCE ---")
    -print(f"Total Return: {equal_weight_portfolio.value_ret:.2%}")
    -print(f"Annualized Return: {equal_weight_portfolio.geo_ret:.2%}")
    -print(f"Annualized Volatility: {equal_weight_portfolio.vol:.2%}")
    -print(f"Sharpe Ratio: {equal_weight_portfolio.ret_vol_ratio:.2f}")
    -print(f"Maximum Drawdown: {equal_weight_portfolio.max_drawdown:.2%}")
    -print(f"95% VaR (daily): {equal_weight_portfolio.var_down:.2%}")
    -
    -print(f"\n--- PORTFOLIO CHARACTERISTICS ---")
    +
    print("\n" + "="*60)
    +print("PORTFOLIO ANALYSIS SUMMARY REPORT")
    +print("="*60)
    +
    +print(f"\nAnalysis Period: {assets.first_idx} to {assets.last_idx}")
    +print(f"Number of Assets: {assets.item_count}")
    +print(f"Asset Universe: {', '.join([s.label for s in assets.constituents])}")
    +
    +print(f"\n--- EQUAL WEIGHT PORTFOLIO PERFORMANCE ---")
    +print(f"Total Return: {equal_weight_portfolio.value_ret:.2%}")
    +print(f"Annualized Return: {equal_weight_portfolio.geo_ret:.2%}")
    +print(f"Annualized Volatility: {equal_weight_portfolio.vol:.2%}")
    +print(f"Sharpe Ratio: {equal_weight_portfolio.ret_vol_ratio:.2f}")
    +print(f"Maximum Drawdown: {equal_weight_portfolio.max_drawdown:.2%}")
    +print(f"95% VaR (daily): {equal_weight_portfolio.var_down:.2%}")
    +
    +print(f"\n--- PORTFOLIO CHARACTERISTICS ---")
     avg_correlation = correlation_matrix.mean().mean()
    -print(f"Average Asset Correlation: {avg_correlation:.3f}")
    -print(f"Portfolio Diversification Benefit: {(asset_metrics.loc['Volatility'].mean() - equal_weight_portfolio.vol):.2%}")
    +print(f"Average Asset Correlation: {avg_correlation:.3f}")
    +print(f"Portfolio Diversification Benefit: {(asset_metrics.loc['Volatility'].mean() - equal_weight_portfolio.vol):.2%}")
     
     # Export results
    -portfolio_metrics.to_excel("portfolio_analysis.xlsx")
    -correlation_matrix.to_excel("correlation_matrix.xlsx")
    +portfolio_metrics.to_excel("portfolio_analysis.xlsx")
    +correlation_matrix.to_excel("correlation_matrix.xlsx")
     
    -print(f"\nResults exported to Excel files")
    -print("Analysis complete!")
    +print(f"\nResults exported to Excel files")
    +print("Analysis complete!")
     

    This tutorial provides a comprehensive framework for portfolio analysis using openseries. You can extend these techniques for more sophisticated portfolio management strategies.

    diff --git a/docs/build/html/tutorials/risk_management.html b/docs/build/html/tutorials/risk_management.html index 4352bb34..4d977a15 100644 --- a/docs/build/html/tutorials/risk_management.html +++ b/docs/build/html/tutorials/risk_management.html @@ -138,31 +138,31 @@

    Setting Up Risk Analysisfrom openseries import OpenTimeSeries, OpenFrame from datetime import datetime, timedelta import warnings -warnings.filterwarnings('ignore') +warnings.filterwarnings('ignore') # Download data for a mixed portfolio tickers = { - "AAPL": "Apple Inc.", - "GOOGL": "Alphabet Inc.", - "MSFT": "Microsoft Corp.", - "TSLA": "Tesla Inc.", - "SPY": "SPDR S&P 500 ETF", - "QQQ": "Invesco QQQ Trust", - "TLT": "iShares 20+ Year Treasury", - "GLD": "SPDR Gold Shares" + "AAPL": "Apple Inc.", + "GOOGL": "Alphabet Inc.", + "MSFT": "Microsoft Corp.", + "TSLA": "Tesla Inc.", + "SPY": "SPDR S&P 500 ETF", + "QQQ": "Invesco QQQ Trust", + "TLT": "iShares 20+ Year Treasury", + "GLD": "SPDR Gold Shares" } # Download 3 years of data series_list = [] for ticker, name in tickers.items(): # This may fail if the ticker is invalid or data unavailable - data = yf.Ticker(ticker).history(period="3y") + data = yf.Ticker(ticker).history(period="3y") series = OpenTimeSeries.from_df( - dframe=data['Close'] + dframe=data['Close'] ) series.set_new_label(lvl_zero=name) series_list.append(series) - print(f"Loaded {name}: {series.length} observations") + print(f"Loaded {name}: {series.length} observations") # Create portfolio frame portfolio_assets = OpenFrame(constituents=series_list) @@ -172,75 +172,75 @@

    Setting Up Risk Analysis# Set weights on the frame first portfolio_df = portfolio_assets.make_portfolio( - name="Diversified Portfolio", - weight_strat="eq_weights" + name="Diversified Portfolio", + weight_strat="eq_weights" ) portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) -print(f"\nPortfolio created with {n_assets} assets") -print(f"Date range: {portfolio.first_idx} to {portfolio.last_idx}") +print(f"\nPortfolio created with {n_assets} assets") +print(f"Date range: {portfolio.first_idx} to {portfolio.last_idx}")

    Basic Risk Metrics

    Start with fundamental risk measurements:

    -
    print("=== BASIC RISK METRICS ===")
    +
    print("=== BASIC RISK METRICS ===")
     
     # Volatility measures
    -print(f"Annualized Volatility: {portfolio.vol:.2%}")
    -print(f"Downside Deviation: {portfolio.downside_deviation:.2%}")
    +print(f"Annualized Volatility: {portfolio.vol:.2%}")
    +print(f"Downside Deviation: {portfolio.downside_deviation:.2%}")
     
     # Return distribution
    -print(f"Skewness: {portfolio.skew:.3f}")
    -print(f"Kurtosis: {portfolio.kurtosis:.3f}")
    +print(f"Skewness: {portfolio.skew:.3f}")
    +print(f"Kurtosis: {portfolio.kurtosis:.3f}")
     
     # Tail risk
    -print(f"Worst Single Day: {portfolio.worst:.2%}")
    -print(f"Worst Month: {portfolio.worst_month:.2%}")
    +print(f"Worst Single Day: {portfolio.worst:.2%}")
    +print(f"Worst Month: {portfolio.worst_month:.2%}")
     
     # Drawdown analysis
    -print(f"Maximum Drawdown: {portfolio.max_drawdown:.2%}")
    -print(f"Max Drawdown Date: {portfolio.max_drawdown_date}")
    +print(f"Maximum Drawdown: {portfolio.max_drawdown:.2%}")
    +print(f"Max Drawdown Date: {portfolio.max_drawdown_date}")
     

    Value at Risk (VaR) Analysis

    Calculate VaR at different confidence levels:

    -
    print("\n=== VALUE AT RISK ANALYSIS ===")
    +
    print("\n=== VALUE AT RISK ANALYSIS ===")
     
     # VaR at different confidence levels
     confidence_levels = [0.90, 0.95, 0.99]
     
     for level in confidence_levels:
          var_value = portfolio.var_down_func(level=level)
    -     print(f"{level*100:.0f}% VaR (daily): {var_value:.2%}")
    +     print(f"{level*100:.0f}% VaR (daily): {var_value:.2%}")
     
     # Convert daily VaR to different time horizons
     # Assuming normal distribution and independence
     daily_var_95 = portfolio.var_down_func(level=0.95)
     
    -print(f"\n=== VaR TIME HORIZONS (95% confidence) ===")
    -print(f"1-day VaR: {daily_var_95:.2%}")
    +print(f"\n=== VaR TIME HORIZONS (95% confidence) ===")
    +print(f"1-day VaR: {daily_var_95:.2%}")
     # Scale VaR to different time horizons
    -print(f"1-week VaR: {daily_var_95 * (5 ** 0.5):.2%}")
    -print(f"1-month VaR: {daily_var_95 * (22 ** 0.5):.2%}")
    -print(f"1-year VaR: {daily_var_95 * (252 ** 0.5):.2%}")
    +print(f"1-week VaR: {daily_var_95 * (5 ** 0.5):.2%}")
    +print(f"1-month VaR: {daily_var_95 * (22 ** 0.5):.2%}")
    +print(f"1-year VaR: {daily_var_95 * (252 ** 0.5):.2%}")
     

    Conditional Value at Risk (CVaR)

    Analyze expected shortfall beyond VaR:

    -
    print("\n=== CONDITIONAL VALUE AT RISK (CVaR) ===")
    +
    print("\n=== CONDITIONAL VALUE AT RISK (CVaR) ===")
     
     for level in confidence_levels:
          cvar_value = portfolio.cvar_down_func(level=level)
          var_value = portfolio.var_down_func(level=level)
     
    -     print(f"{level*100:.0f}% CVaR: {cvar_value:.2%} (VaR: {var_value:.2%})")
    -     print(f"  Expected loss beyond VaR: {cvar_value - var_value:.2%}")
    +     print(f"{level*100:.0f}% CVaR: {cvar_value:.2%} (VaR: {var_value:.2%})")
    +     print(f"  Expected loss beyond VaR: {cvar_value - var_value:.2%}")
     
    @@ -250,23 +250,23 @@

    Rolling Risk Analysis
    # Calculate rolling risk metrics
     window = 252  # 1-year rolling window
     
    -print(f"\n=== ROLLING RISK ANALYSIS ({window}-day window) ===")
    +print(f"\n=== ROLLING RISK ANALYSIS ({window}-day window) ===")
     
     # Rolling volatility
     rolling_vol = portfolio.rolling_vol(observations=window)
    -print(f"Rolling Volatility - Current: {rolling_vol.iloc[-1, 0]:.2%}")
    -print(f"Rolling Volatility - Average: {rolling_vol.mean().iloc[0]:.2%}")
    -print(f"Rolling Volatility - Range: {rolling_vol.min().iloc[0]:.2%} to {rolling_vol.max().iloc[0]:.2%}")
    +print(f"Rolling Volatility - Current: {rolling_vol.iloc[-1, 0]:.2%}")
    +print(f"Rolling Volatility - Average: {rolling_vol.mean().iloc[0]:.2%}")
    +print(f"Rolling Volatility - Range: {rolling_vol.min().iloc[0]:.2%} to {rolling_vol.max().iloc[0]:.2%}")
     
     # Rolling VaR
     rolling_var = portfolio.rolling_var_down(observations=window)
    -print(f"Rolling VaR (95%) - Current: {rolling_var.iloc[-1, 0]:.2%}")
    -print(f"Rolling VaR (95%) - Average: {rolling_var.mean().iloc[0]:.2%}")
    +print(f"Rolling VaR (95%) - Current: {rolling_var.iloc[-1, 0]:.2%}")
    +print(f"Rolling VaR (95%) - Average: {rolling_var.mean().iloc[0]:.2%}")
     
     # Rolling CVaR
     rolling_cvar = portfolio.rolling_cvar_down(observations=window)
    -print(f"Rolling CVaR (95%) - Current: {rolling_cvar.iloc[-1, 0]:.2%}")
    -print(f"Rolling CVaR (95%) - Average: {rolling_cvar.mean().iloc[0]:.2%}")
    +print(f"Rolling CVaR (95%) - Current: {rolling_cvar.iloc[-1, 0]:.2%}")
    +print(f"Rolling CVaR (95%) - Average: {rolling_cvar.mean().iloc[0]:.2%}")
     

    @@ -275,7 +275,7 @@

    Stress Testing

    Historical Stress Testing

    -
    print("\n=== HISTORICAL STRESS TESTING ===")
    +
     
     

    Scenario Analysis

    -
    print("\n=== SCENARIO ANALYSIS ===")
    +
    print("\n=== SCENARIO ANALYSIS ===")
     
     # Define stress scenarios (percentage moves in underlying assets)
     scenarios = {
    -     "Market Crash": [-0.20, -0.25, -0.22, -0.30, -0.18, -0.20, 0.05, 0.10],
    -     "Tech Selloff": [-0.35, -0.40, -0.30, -0.45, -0.10, -0.15, 0.02, 0.03],
    -     "Interest Rate Shock": [-0.10, -0.12, -0.08, -0.15, -0.05, -0.08, -0.15, 0.01],
    -     "Flight to Quality": [0.05, 0.02, 0.08, -0.10, 0.10, 0.12, 0.20, 0.15]
    +     "Market Crash": [-0.20, -0.25, -0.22, -0.30, -0.18, -0.20, 0.05, 0.10],
    +     "Tech Selloff": [-0.35, -0.40, -0.30, -0.45, -0.10, -0.15, 0.02, 0.03],
    +     "Interest Rate Shock": [-0.10, -0.12, -0.08, -0.15, -0.05, -0.08, -0.15, 0.01],
    +     "Flight to Quality": [0.05, 0.02, 0.08, -0.10, 0.10, 0.12, 0.20, 0.15]
     }
     
    -print("Portfolio impact under stress scenarios:")
    +print("Portfolio impact under stress scenarios:")
     for scenario_name, asset_moves in scenarios.items():
          # Calculate portfolio impact
          portfolio_impact = sum(w * move for w, move in zip(equal_weights, asset_moves))
    -     print(f"  {scenario_name}: {portfolio_impact:.2%}")
    +     print(f"  {scenario_name}: {portfolio_impact:.2%}")
     
    @@ -332,7 +332,7 @@

    Scenario Analysis

    Monte Carlo Risk Simulation

    Use Monte Carlo methods for risk assessment:

    -
    print("\n=== MONTE CARLO RISK SIMULATION ===")
    +
    print("\n=== MONTE CARLO RISK SIMULATION ===")
     
     # Import the simulate_portfolios function
     from openseries.portfoliotools import simulate_portfolios
    @@ -349,9 +349,9 @@ 

    Monte Carlo Risk Simulation) # Extract portfolio metrics from simulation -portfolio_returns = simulated_portfolios['ret'] -portfolio_volatilities = simulated_portfolios['stdev'] -portfolio_sharpes = simulated_portfolios['sharpe'] +portfolio_returns = simulated_portfolios['ret'] +portfolio_volatilities = simulated_portfolios['stdev'] +portfolio_sharpes = simulated_portfolios['sharpe'] # Calculate risk metrics from simulation # Calculate 5th percentile manually @@ -360,34 +360,34 @@

    Monte Carlo Risk Simulationsim_var_95 = sorted_returns[percentile_idx] sim_cvar_95 = portfolio_returns[portfolio_returns <= sim_var_95].mean() -print(f"Monte Carlo Results ({num_simulations:,} simulations):") -print(f"Expected Return: {portfolio_returns.mean():.2%}") -print(f"Average Volatility: {portfolio_volatilities.mean():.2%}") -print(f"95% VaR: {sim_var_95:.2%}") -print(f"95% CVaR: {sim_cvar_95:.2%}") +print(f"Monte Carlo Results ({num_simulations:,} simulations):") +print(f"Expected Return: {portfolio_returns.mean():.2%}") +print(f"Average Volatility: {portfolio_volatilities.mean():.2%}") +print(f"95% VaR: {sim_var_95:.2%}") +print(f"95% CVaR: {sim_cvar_95:.2%}") # Calculate percentiles manually worst_idx = int(len(sorted_returns) * 0.001) best_idx = int(len(sorted_returns) * 0.999) -print(f"Worst Case (0.1%): {sorted_returns[worst_idx]:.2%}") -print(f"Best Case (99.9%): {sorted_returns[best_idx]:.2%}") -print(f"Average Sharpe Ratio: {portfolio_sharpes.mean():.3f}") +print(f"Worst Case (0.1%): {sorted_returns[worst_idx]:.2%}") +print(f"Best Case (99.9%): {sorted_returns[best_idx]:.2%}") +print(f"Average Sharpe Ratio: {portfolio_sharpes.mean():.3f}") # Show distribution of portfolio characteristics -print(f"\nPortfolio Distribution:") -print(f"Return Range: {portfolio_returns.min():.2%} to {portfolio_returns.max():.2%}") -print(f"Volatility Range: {portfolio_volatilities.min():.2%} to {portfolio_volatilities.max():.2%}") -print(f"Sharpe Range: {portfolio_sharpes.min():.3f} to {portfolio_sharpes.max():.3f}") +print(f"\nPortfolio Distribution:") +print(f"Return Range: {portfolio_returns.min():.2%} to {portfolio_returns.max():.2%}") +print(f"Volatility Range: {portfolio_volatilities.min():.2%} to {portfolio_volatilities.max():.2%}") +print(f"Sharpe Range: {portfolio_sharpes.min():.3f} to {portfolio_sharpes.max():.3f}")

    Risk Decomposition

    Analyze risk contribution by asset:

    -
    print("\n=== RISK DECOMPOSITION ===")
    +
    print("\n=== RISK DECOMPOSITION ===")
     
     # Calculate individual asset volatilities using OpenFrame
     asset_metrics = portfolio_assets.all_properties()
    -asset_vols = asset_metrics.loc['Volatility'].values
    +asset_vols = asset_metrics.loc['Volatility'].values
     
     # Portfolio volatility
     portfolio_vol = portfolio.vol
    @@ -397,50 +397,50 @@ 

    Risk Decomposition# Risk contribution analysis using openseries # Create portfolio to get portfolio-level metrics -portfolio_df = portfolio_assets.make_portfolio(name="Portfolio", weight_strat="eq_weights") +portfolio_df = portfolio_assets.make_portfolio(name="Portfolio", weight_strat="eq_weights") portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) portfolio_vol = portfolio.vol -print("Risk Contribution Analysis:") +print("Risk Contribution Analysis:") for i, series in enumerate(portfolio_assets.constituents): weight = equal_weights[i] asset_vol = asset_vols[i] - print(f"\n{series.label}:") - print(f" Weight: {weight:.4f}") - print(f" Individual Volatility: {asset_vol:.4f}") - print(f" Weighted Volatility Contribution: {weight * asset_vol:.4f}") -print(f"\nPortfolio Volatility: {portfolio_vol:.4f}") + print(f"\n{series.label}:") + print(f" Weight: {weight:.4f}") + print(f" Individual Volatility: {asset_vol:.4f}") + print(f" Weighted Volatility Contribution: {weight * asset_vol:.4f}") +print(f"\nPortfolio Volatility: {portfolio_vol:.4f}") # Verify portfolio metrics -print(f"\nVerification:") -print(f"Portfolio volatility: {portfolio_vol:.4f}") +print(f"\nVerification:") +print(f"Portfolio volatility: {portfolio_vol:.4f}")

    Risk-Adjusted Performance

    Evaluate risk-adjusted returns:

    -
    print("\n=== RISK-ADJUSTED PERFORMANCE ===")
    +
    print("\n=== RISK-ADJUSTED PERFORMANCE ===")
     
     # Sharpe ratio
    -print(f"Sharpe Ratio: {portfolio.ret_vol_ratio:.3f}")
    +print(f"Sharpe Ratio: {portfolio.ret_vol_ratio:.3f}")
     
     # Sortino ratio (downside risk only)
    -print(f"Sortino Ratio: {portfolio.sortino_ratio:.3f}")
    +print(f"Sortino Ratio: {portfolio.sortino_ratio:.3f}")
     
     # Kappa-3 ratio (higher-order downside risk)
    -print(f"Kappa-3 Ratio: {portfolio.kappa3_ratio:.3f}")
    +print(f"Kappa-3 Ratio: {portfolio.kappa3_ratio:.3f}")
     
     # Omega ratio
    -print(f"Omega Ratio: {portfolio.omega_ratio:.3f}")
    +print(f"Omega Ratio: {portfolio.omega_ratio:.3f}")
     
     # Compare with individual assets
    -print(f"\n=== RISK-ADJUSTED COMPARISON ===")
    +print(f"\n=== RISK-ADJUSTED COMPARISON ===")
     all_assets = portfolio_assets.constituents + [portfolio]
     comparison_frame = OpenFrame(constituents=all_assets)
     
     risk_adj_metrics = comparison_frame.all_properties(
    -     properties=['ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'omega_ratio']
    +     properties=['ret_vol_ratio', 'sortino_ratio', 'kappa3_ratio', 'omega_ratio']
     )
     
     print(risk_adj_metrics.round(3))
    @@ -450,134 +450,134 @@ 

    Risk-Adjusted Performance

    Risk Monitoring Dashboard

    Create a comprehensive risk monitoring summary using openseries properties and methods:

    -
    print("\n" + "="*60)
    -print("RISK MONITORING DASHBOARD")
    -print("="*60)
    +
    print("\n" + "="*60)
    +print("RISK MONITORING DASHBOARD")
    +print("="*60)
     
     # Current date and lookback period
     current_date = portfolio.last_idx
     lookback_date = portfolio.first_idx
     
    -print(f"Portfolio: {portfolio.label}")
    -print(f"Current Date: {current_date}")
    -print(f"Analysis Period: {lookback_date} to {current_date}")
    -print(f"Observations: {portfolio.length}")
    +print(f"Portfolio: {portfolio.label}")
    +print(f"Current Date: {current_date}")
    +print(f"Analysis Period: {lookback_date} to {current_date}")
    +print(f"Observations: {portfolio.length}")
     
     # Risk metrics using openseries properties
    -print(f"\n--- CURRENT RISK METRICS ---")
    -print(f"Volatility (annualized): {portfolio.vol:.2%}")
    -print(f"Downside Deviation: {portfolio.downside_deviation:.2%}")
    -print(f"95% VaR (daily): {portfolio.var_down:.2%}")
    -print(f"95% CVaR (daily): {portfolio.cvar_down:.2%}")
    -print(f"Maximum Drawdown: {portfolio.max_drawdown:.2%}")
    +print(f"\n--- CURRENT RISK METRICS ---")
    +print(f"Volatility (annualized): {portfolio.vol:.2%}")
    +print(f"Downside Deviation: {portfolio.downside_deviation:.2%}")
    +print(f"95% VaR (daily): {portfolio.var_down:.2%}")
    +print(f"95% CVaR (daily): {portfolio.cvar_down:.2%}")
    +print(f"Maximum Drawdown: {portfolio.max_drawdown:.2%}")
     
     # Performance metrics using openseries properties
    -print(f"\n--- PERFORMANCE METRICS ---")
    -print(f"Total Return: {portfolio.value_ret:.2%}")
    -print(f"Annualized Return: {portfolio.geo_ret:.2%}")
    -print(f"Sharpe Ratio: {portfolio.ret_vol_ratio:.3f}")
    -print(f"Sortino Ratio: {portfolio.sortino_ratio:.3f}")
    +print(f"\n--- PERFORMANCE METRICS ---")
    +print(f"Total Return: {portfolio.value_ret:.2%}")
    +print(f"Annualized Return: {portfolio.geo_ret:.2%}")
    +print(f"Sharpe Ratio: {portfolio.ret_vol_ratio:.3f}")
    +print(f"Sortino Ratio: {portfolio.sortino_ratio:.3f}")
     
     # Distribution characteristics using openseries properties
    -print(f"\n--- RETURN DISTRIBUTION ---")
    -print(f"Skewness: {portfolio.skew:.3f}")
    -print(f"Kurtosis: {portfolio.kurtosis:.3f}")
    -print(f"Positive Days: {portfolio.positive_share:.1%}")
    +print(f"\n--- RETURN DISTRIBUTION ---")
    +print(f"Skewness: {portfolio.skew:.3f}")
    +print(f"Kurtosis: {portfolio.kurtosis:.3f}")
    +print(f"Positive Days: {portfolio.positive_share:.1%}")
     
     # Recent performance using openseries properties
     recent_return = portfolio.z_score
    -print(f"\n--- RECENT ACTIVITY ---")
    -print(f"Last Return Z-Score: {recent_return:.2f}")
    +print(f"\n--- RECENT ACTIVITY ---")
    +print(f"Last Return Z-Score: {recent_return:.2f}")
     
     if abs(recent_return) > 2:
    -     print("  ⚠️  ALERT: Recent return is unusual (|z| > 2)")
    +     print("  ⚠️  ALERT: Recent return is unusual (|z| > 2)")
     elif abs(recent_return) > 3:
    -     print("  🚨 WARNING: Recent return is extreme (|z| > 3)")
    +     print("  🚨 WARNING: Recent return is extreme (|z| > 3)")
     else:
    -     print("  ✅ Recent return is within normal range")
    +     print("  ✅ Recent return is within normal range")
     
     # Risk alerts based on openseries metrics
    -print(f"\n--- RISK ALERTS ---")
    +print(f"\n--- RISK ALERTS ---")
     alerts = []
     
     if portfolio.vol > 0.25:
    -     alerts.append("High volatility (>25%)")
    +     alerts.append("High volatility (>25%)")
     
     if abs(portfolio.max_drawdown) > 0.20:
    -     alerts.append("Large maximum drawdown (>20%)")
    +     alerts.append("Large maximum drawdown (>20%)")
     
     if portfolio.ret_vol_ratio < 0.5:
    -     alerts.append("Low Sharpe ratio (<0.5)")
    +     alerts.append("Low Sharpe ratio (<0.5)")
     
     if portfolio.skew < -1:
    -     alerts.append("Highly negative skew (<-1)")
    +     alerts.append("Highly negative skew (<-1)")
     
     if portfolio.kurtosis > 5:
    -     alerts.append("High kurtosis (>5) - fat tails")
    +     alerts.append("High kurtosis (>5) - fat tails")
     
     if alerts:
          for alert in alerts:
    -          print(f"  ⚠️  {alert}")
    +          print(f"  ⚠️  {alert}")
     else:
    -     print("  ✅ No risk alerts")
    +     print("  ✅ No risk alerts")
     

    Risk Limits and Controls

    Implement risk limit monitoring:

    -
    print("\n=== RISK LIMITS MONITORING ===")
    +
    print("\n=== RISK LIMITS MONITORING ===")
     
     # Define risk limits
     risk_limits = {
    -     'max_volatility': 0.20,      # 20% annual volatility
    -     'max_var_daily': -0.03,      # 3% daily VaR
    -     'max_drawdown': -0.15,       # 15% maximum drawdown
    -     'min_sharpe': 0.5,           # Minimum Sharpe ratio
    -     'max_concentration': 0.30    # Maximum single asset weight
    +     'max_volatility': 0.20,      # 20% annual volatility
    +     'max_var_daily': -0.03,      # 3% daily VaR
    +     'max_drawdown': -0.15,       # 15% maximum drawdown
    +     'min_sharpe': 0.5,           # Minimum Sharpe ratio
    +     'max_concentration': 0.30    # Maximum single asset weight
     }
     
     # Check current metrics against limits
     current_metrics = {
    -     'volatility': portfolio.vol,
    -     'var_daily': portfolio.var_down,
    -     'drawdown': portfolio.max_drawdown,
    -     'sharpe': portfolio.ret_vol_ratio,
    -     'max_weight': max(equal_weights)
    +     'volatility': portfolio.vol,
    +     'var_daily': portfolio.var_down,
    +     'drawdown': portfolio.max_drawdown,
    +     'sharpe': portfolio.ret_vol_ratio,
    +     'max_weight': max(equal_weights)
     }
     
    -print("Risk Limit Monitoring:")
    -print("-" * 40)
    +print("Risk Limit Monitoring:")
    +print("-" * 40)
     
     # Volatility check
    -if current_metrics['volatility'] > risk_limits['max_volatility']:
    -     print(f"❌ BREACH: Volatility {current_metrics['volatility']:.2%} > {risk_limits['max_volatility']:.2%}")
    +if current_metrics['volatility'] > risk_limits['max_volatility']:
    +     print(f"❌ BREACH: Volatility {current_metrics['volatility']:.2%} > {risk_limits['max_volatility']:.2%}")
     else:
    -     print(f"✅ OK: Volatility {current_metrics['volatility']:.2%} <= {risk_limits['max_volatility']:.2%}")
    +     print(f"✅ OK: Volatility {current_metrics['volatility']:.2%} <= {risk_limits['max_volatility']:.2%}")
     
     # VaR check
    -if current_metrics['var_daily'] < risk_limits['max_var_daily']:
    -     print(f"❌ BREACH: VaR {current_metrics['var_daily']:.2%} < {risk_limits['max_var_daily']:.2%}")
    +if current_metrics['var_daily'] < risk_limits['max_var_daily']:
    +     print(f"❌ BREACH: VaR {current_metrics['var_daily']:.2%} < {risk_limits['max_var_daily']:.2%}")
     else:
    -     print(f"✅ OK: VaR {current_metrics['var_daily']:.2%} >= {risk_limits['max_var_daily']:.2%}")
    +     print(f"✅ OK: VaR {current_metrics['var_daily']:.2%} >= {risk_limits['max_var_daily']:.2%}")
     
     # Drawdown check
    -if current_metrics['drawdown'] < risk_limits['max_drawdown']:
    -     print(f"❌ BREACH: Drawdown {current_metrics['drawdown']:.2%} < {risk_limits['max_drawdown']:.2%}")
    +if current_metrics['drawdown'] < risk_limits['max_drawdown']:
    +     print(f"❌ BREACH: Drawdown {current_metrics['drawdown']:.2%} < {risk_limits['max_drawdown']:.2%}")
     else:
    -     print(f"✅ OK: Drawdown {current_metrics['drawdown']:.2%} >= {risk_limits['max_drawdown']:.2%}")
    +     print(f"✅ OK: Drawdown {current_metrics['drawdown']:.2%} >= {risk_limits['max_drawdown']:.2%}")
     
     # Sharpe ratio check
    -if current_metrics['sharpe'] < risk_limits['min_sharpe']:
    -     print(f"❌ BREACH: Sharpe {current_metrics['sharpe']:.3f} < {risk_limits['min_sharpe']:.3f}")
    +if current_metrics['sharpe'] < risk_limits['min_sharpe']:
    +     print(f"❌ BREACH: Sharpe {current_metrics['sharpe']:.3f} < {risk_limits['min_sharpe']:.3f}")
     else:
    -     print(f"✅ OK: Sharpe {current_metrics['sharpe']:.3f} >= {risk_limits['min_sharpe']:.3f}")
    +     print(f"✅ OK: Sharpe {current_metrics['sharpe']:.3f} >= {risk_limits['min_sharpe']:.3f}")
     
     # Concentration check
    -if current_metrics['max_weight'] > risk_limits['max_concentration']:
    -     print(f"❌ BREACH: Max weight {current_metrics['max_weight']:.2%} > {risk_limits['max_concentration']:.2%}")
    +if current_metrics['max_weight'] > risk_limits['max_concentration']:
    +     print(f"❌ BREACH: Max weight {current_metrics['max_weight']:.2%} > {risk_limits['max_concentration']:.2%}")
     else:
    -     print(f"✅ OK: Max weight {current_metrics['max_weight']:.2%} <= {risk_limits['max_concentration']:.2%}")
    +     print(f"✅ OK: Max weight {current_metrics['max_weight']:.2%} <= {risk_limits['max_concentration']:.2%}")
     
    @@ -586,53 +586,53 @@

    Export Risk Report
    # Create comprehensive risk report
     # Create risk report using openseries methods
    -print("\n=== RISK REPORT ===")
    -print("Risk metrics are available through openseries properties:")
    +print("\n=== RISK REPORT ===")
    +print("Risk metrics are available through openseries properties:")
     for series in portfolio_assets.constituents:
    -    print(f"\n{series.label}:")
    -    print(f"  VaR (95%): {series.var_down:.4f}")
    -    print(f"  CVaR (95%): {series.cvar_down:.4f}")
    -    print(f"  Volatility: {series.vol:.4f}")
    -    print(f"  Max Drawdown: {series.max_drawdown:.4f}")
    +    print(f"\n{series.label}:")
    +    print(f"  VaR (95%): {series.var_down:.4f}")
    +    print(f"  CVaR (95%): {series.cvar_down:.4f}")
    +    print(f"  Volatility: {series.vol:.4f}")
    +    print(f"  Max Drawdown: {series.max_drawdown:.4f}")
     
     # Note: For comprehensive Excel export, use openseries to_xlsx() method
    -portfolio_assets.to_xlsx('risk_analysis_report.xlsx')
    +portfolio_assets.to_xlsx('risk_analysis_report.xlsx')
     
     # Alternative: risk_report = pd.DataFrame({
    -     'Metric': [
    -          'Annualized Return', 'Annualized Volatility', 'Sharpe Ratio',
    -          'Sortino Ratio', 'Maximum Drawdown', '95% VaR (daily)',
    -          '95% CVaR (daily)', 'Skewness', 'Kurtosis', 'Positive Days %'
    +     'Metric': [
    +          'Annualized Return', 'Annualized Volatility', 'Sharpe Ratio',
    +          'Sortino Ratio', 'Maximum Drawdown', '95% VaR (daily)',
    +          '95% CVaR (daily)', 'Skewness', 'Kurtosis', 'Positive Days %'
          ],
    -     'Value': [
    -          f"{portfolio.geo_ret:.2%}",
    -          f"{portfolio.vol:.2%}",
    -          f"{portfolio.ret_vol_ratio:.3f}",
    -          f"{portfolio.sortino_ratio:.3f}",
    -          f"{portfolio.max_drawdown:.2%}",
    -          f"{portfolio.var_down:.2%}",
    -          f"{portfolio.cvar_down:.2%}",
    -          f"{portfolio.skew:.3f}",
    -          f"{portfolio.kurtosis:.3f}",
    -          f"{portfolio.positive_share:.1%}"
    +     'Value': [
    +          f"{portfolio.geo_ret:.2%}",
    +          f"{portfolio.vol:.2%}",
    +          f"{portfolio.ret_vol_ratio:.3f}",
    +          f"{portfolio.sortino_ratio:.3f}",
    +          f"{portfolio.max_drawdown:.2%}",
    +          f"{portfolio.var_down:.2%}",
    +          f"{portfolio.cvar_down:.2%}",
    +          f"{portfolio.skew:.3f}",
    +          f"{portfolio.kurtosis:.3f}",
    +          f"{portfolio.positive_share:.1%}"
          ]
     })
     
     # Export to Excel
     # Export using openseries native method (commented out ExcelWriter approach)
    -# with pd.ExcelWriter('risk_analysis_report.xlsx') as writer:
    -     risk_report.to_excel(writer, sheet_name='Risk Metrics', index=False)
    -     risk_decomp.to_excel(writer, sheet_name='Risk Decomposition', index=False)
    -     correlation_matrix.to_excel(writer, sheet_name='Correlations')
    +# with pd.ExcelWriter('risk_analysis_report.xlsx') as writer:
    +     risk_report.to_excel(writer, sheet_name='Risk Metrics', index=False)
    +     risk_decomp.to_excel(writer, sheet_name='Risk Decomposition', index=False)
    +     correlation_matrix.to_excel(writer, sheet_name='Correlations')
     
          # Add rolling metrics if available
    -     if 'rolling_vol' in locals():
    -          rolling_vol.to_excel(writer, sheet_name='Rolling Volatility')
    -     if 'rolling_var' in locals():
    -          rolling_var.to_excel(writer, sheet_name='Rolling VaR')
    +     if 'rolling_vol' in locals():
    +          rolling_vol.to_excel(writer, sheet_name='Rolling Volatility')
    +     if 'rolling_var' in locals():
    +          rolling_var.to_excel(writer, sheet_name='Rolling VaR')
     
    -print(f"\nRisk analysis report exported to 'risk_analysis_report.xlsx'")
    -print("Risk management analysis complete!")
    +print(f"\nRisk analysis report exported to 'risk_analysis_report.xlsx'")
    +print("Risk management analysis complete!")
     

    This comprehensive risk management tutorial provides the foundation for implementing robust risk controls and monitoring systems using openseries.

    diff --git a/docs/build/html/user_guide/core_concepts.html b/docs/build/html/user_guide/core_concepts.html index bc4aa1bc..3f8d87dc 100644 --- a/docs/build/html/user_guide/core_concepts.html +++ b/docs/build/html/user_guide/core_concepts.html @@ -203,16 +203,16 @@

    Core Properties) series = OpenTimeSeries.from_df( - dframe=simulation.to_dataframe(name="Sample Asset", end=dt.date(2023, 12, 31)), + dframe=simulation.to_dataframe(name="Sample Asset", end=dt.date(2023, 12, 31)), valuetype=ValueType.RTRN ).to_cumret() # Convert returns to cumulative prices # Core properties -print(f"Name: {series.label}") -print(f"Length: {series.length}") -print(f"First date: {series.first_idx}") -print(f"Last date: {series.last_idx}") -print(f"Value type: {series.valuetype}") +print(f"Name: {series.label}") +print(f"Length: {series.length}") +print(f"First date: {series.first_idx}") +print(f"Last date: {series.last_idx}") +print(f"Value type: {series.valuetype}")

    @@ -228,7 +228,7 @@

    Data Immutability# Transformations modify the original object (method chaining) series.value_to_ret() # Modifies original series -print(f"Series length: {series.length}") # Usually length - 1 +print(f"Series length: {series.length}") # Usually length - 1

    @@ -238,16 +238,16 @@

    Value Types
    from openseries import ValueType
     
     # Common value types
    -print(ValueType.PRICE)      # "Price(Close)"
    -print(ValueType.RTRN)       # "Return(Total)"
    -print(ValueType.ROLLVOL)    # "Rolling volatility"
    +print(ValueType.PRICE)      # "Price(Close)"
    +print(ValueType.RTRN)       # "Return(Total)"
    +print(ValueType.ROLLVOL)    # "Rolling volatility"
     
     # Check series type
    -print(f"Series type: {series.valuetype}")
    +print(f"Series type: {series.valuetype}")
     
     # Type changes with transformations
     series.value_to_ret()  # Modifies original
    -print(f"Returns type: {series.valuetype}")
    +print(f"Returns type: {series.valuetype}")
     

    @@ -273,7 +273,7 @@

    Managing Multiple Seriesframe = OpenFrame( constituents=[ OpenTimeSeries.from_df( - dframe=simulation.to_dataframe(name="Asset", end=dt.date(2023, 12, 31)), + dframe=simulation.to_dataframe(name="Asset", end=dt.date(2023, 12, 31)), column_nmbr=serie, valuetype=ValueType.RTRN, ).to_cumret() # Convert returns to cumulative prices @@ -282,9 +282,9 @@

    Managing Multiple Series) # Frame properties -print(f"Number of series: {frame.item_count}") -print(f"Column names: {frame.columns_lvl_zero}") -print(f"Common length: {frame.length}") +print(f"Number of series: {frame.item_count}") +print(f"Column names: {frame.columns_lvl_zero}") +print(f"Common length: {frame.length}")

    @@ -293,10 +293,10 @@

    Data Alignment

    @@ -336,15 +336,15 @@

    Return Calculations

    Metrics are annualized using the actual number of observations per year:

    # Automatic calculation of periods per year
    -print(f"Periods per year: {series.periods_in_a_year:.1f}")
    +print(f"Periods per year: {series.periods_in_a_year:.1f}")
     
     # Annualized return (geometric mean)
     annual_return = series.geo_ret
    -print(f"Annualized return: {annual_return:.2%}")
    +print(f"Annualized return: {annual_return:.2%}")
     
     # Annualized volatility
     annual_vol = series.vol
    -print(f"Annualized volatility: {annual_vol:.2%}")
    +print(f"Annualized volatility: {annual_vol:.2%}")
     
    @@ -353,19 +353,19 @@

    Risk Metrics
    # Value at Risk (95% confidence)
     var_95 = series.var_down
    -print(f"95% VaR: {var_95:.2%}")
    +print(f"95% VaR: {var_95:.2%}")
     
     # Conditional Value at Risk (Expected Shortfall)
     cvar_95 = series.cvar_down
    -print(f"95% CVaR: {cvar_95:.2%}")
    +print(f"95% CVaR: {cvar_95:.2%}")
     
     # Maximum Drawdown
     max_dd = series.max_drawdown
    -print(f"Maximum Drawdown: {max_dd:.2%}")
    +print(f"Maximum Drawdown: {max_dd:.2%}")
     
     # Sortino Ratio (downside deviation)
     sortino = series.sortino_ratio
    -print(f"Sortino Ratio: {sortino:.2f}")
    +print(f"Sortino Ratio: {sortino:.2f}")
     

    @@ -375,14 +375,14 @@

    Date Handling

    Business Day Calendars

    openseries integrates with business day calendars:

    -
    # Align to specific country's business days (modifies original)
    -series.align_index_to_local_cdays(countries="US")
    +
    # Align to specific country's business days (modifies original)
    +series.align_index_to_local_cdays(countries="US")
     
     # Multiple countries (intersection of business days) (modifies original)
    -series.align_index_to_local_cdays(countries=["US", "GB"])
    +series.align_index_to_local_cdays(countries=["US", "GB"])
     
     # Custom markets using pandas-market-calendars (modifies original)
    -series.align_index_to_local_cdays(markets="NYSE")
    +series.align_index_to_local_cdays(markets="NYSE")
     
    @@ -390,13 +390,13 @@

    Business Day CalendarsResampling

    Convert between different frequencies:

    # Resample to month-end (modifies original)
    -series.resample_to_business_period_ends(freq="BME")
    +series.resample_to_business_period_ends(freq="BME")
     
     # Resample to quarter-end (modifies original)
    -series.resample_to_business_period_ends(freq="BQE")
    +series.resample_to_business_period_ends(freq="BQE")
     
     # Custom resampling (modifies original)
    -series.resample(freq="W")
    +series.resample(freq="W")
     
    @@ -409,15 +409,15 @@

    Type Safety
    # Dates must be valid ISO format strings
     # This will fail with a validation error
     invalid_series = OpenTimeSeries.from_arrays(
    -     dates=["invalid-date"],
    +     dates=["invalid-date"],
          values=[100.0]
     )
     
     # Values must be numeric
     # This will fail with a validation error
     invalid_series = OpenTimeSeries.from_arrays(
    -     dates=["2023-01-01"],
    -     values=["not a number"]
    +     dates=["2023-01-01"],
    +     values=["not a number"]
     )
     

    @@ -458,8 +458,8 @@

    Transformation Methodsseries.to_cumret() # Cumulative returns # Time transformations (modify original) -series.resample_to_business_period_ends(freq="BME") -series.align_index_to_local_cdays(countries="US") +series.resample_to_business_period_ends(freq="BME") +series.align_index_to_local_cdays(countries="US")

    Methods that return new objects:

    @@ -486,8 +486,8 @@

    Analysis Methods

    Methods for saving results:

    # File exports
    -series.to_xlsx("analysis.xlsx")
    -series.to_json("data.json")
    +series.to_xlsx("analysis.xlsx")
    +series.to_json("data.json")
     
     # Visualization
     series.plot_series()
    @@ -501,22 +501,22 @@ 

    Best Practices

    Data Loading

    # Prefer from_df for pandas data
    -series = OpenTimeSeries.from_df(dframe=dataframe['Close'])
    -series.set_new_label(lvl_zero="Asset")
    +series = OpenTimeSeries.from_df(dframe=dataframe['Close'])
    +series.set_new_label(lvl_zero="Asset")
     
     # Use from_arrays for custom data
     series = OpenTimeSeries.from_arrays(dates=date_list, values=value_list)
     
     # Always set meaningful names
    -series.set_new_label(lvl_zero="Descriptive Name")
    +series.set_new_label(lvl_zero="Descriptive Name")
     

    Analysis Workflow

    # 1. Load and validate data
    -series = OpenTimeSeries.from_df(dframe=data['Close'])
    -series.set_new_label(lvl_zero="Asset")
    +series = OpenTimeSeries.from_df(dframe=data['Close'])
    +series.set_new_label(lvl_zero="Asset")
     
     # 2. Basic analysis
     metrics = series.all_properties()
    @@ -529,7 +529,7 @@ 

    Analysis Workflowseries.plot_series() # 5. Export results -series.to_xlsx(fiilename="analysis.xlsx") +series.to_xlsx(fiilename="analysis.xlsx")

    @@ -539,7 +539,7 @@

    Memory Managementseries_copy = OpenTimeSeries.from_deepcopy(series) # Large datasets - consider resampling (modifies original) -series.resample_to_business_period_ends(freq="BME") +series.resample_to_business_period_ends(freq="BME") # Clean up intermediate results del intermediate_series @@ -553,15 +553,15 @@

    Portfolio Construction# Available weight strategies strategies = { - 'eq_weights': 'Equal weights for all assets', - 'inv_vol': 'Inverse volatility weighting (risk parity)', - 'max_div': 'Maximum diversification optimization', - 'min_vol_overweight': 'Minimum volatility overweight strategy' + 'eq_weights': 'Equal weights for all assets', + 'inv_vol': 'Inverse volatility weighting (risk parity)', + 'max_div': 'Maximum diversification optimization', + 'min_vol_overweight': 'Minimum volatility overweight strategy' } # Example with error handling # This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError -portfolio_df = frame.make_portfolio(name="Max Div", weight_strat="max_div") +portfolio_df = frame.make_portfolio(name="Max Div", weight_strat="max_div")

    Understanding these core concepts will help you use openseries effectively and build more sophisticated financial analysis workflows.

    diff --git a/docs/build/html/user_guide/data_handling.html b/docs/build/html/user_guide/data_handling.html index 2e7b6471..0d3225c2 100644 --- a/docs/build/html/user_guide/data_handling.html +++ b/docs/build/html/user_guide/data_handling.html @@ -174,19 +174,19 @@

    From pandas DataFrame/Series# From pandas Series with DatetimeIndex data = pd.Series([100, 101, 99, 102], - index=pd.date_range('2023-01-01', periods=4)) + index=pd.date_range('2023-01-01', periods=4)) series = OpenTimeSeries.from_df(dframe=data) -series.set_new_label(lvl_zero="Sample") +series.set_new_label(lvl_zero="Sample") # From pandas DataFrame column df = pd.DataFrame({ - 'Date': pd.date_range('2023-01-01', periods=4), - 'Close': [100, 101, 99, 102], - 'Volume': [1000, 1100, 900, 1200] + 'Date': pd.date_range('2023-01-01', periods=4), + 'Close': [100, 101, 99, 102], + 'Volume': [1000, 1100, 900, 1200] }) -df.set_index('Date', inplace=True) -series = OpenTimeSeries.from_df(dframe=df['Close']) -series.set_new_label(lvl_zero="Stock") +df.set_index('Date', inplace=True) +series = OpenTimeSeries.from_df(dframe=df['Close']) +series.set_new_label(lvl_zero="Stock")

    @@ -194,13 +194,13 @@

    From pandas DataFrame/Series

    For custom data or when working with lists:

    # From date strings and values
    -dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04']
    +dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04']
     values = [100.0, 101.0, 99.0, 102.0]
     
     series = OpenTimeSeries.from_arrays(
          dates=dates,
          values=values,
    -     name="Custom Data"
    +     name="Custom Data"
     )
     
    @@ -215,7 +215,7 @@

    From Fixed Raterate=0.05, days=252, end_date=date(2023, 12, 31), - name="5% Fixed Rate" + name="5% Fixed Rate" )

    @@ -227,12 +227,12 @@

    Data Validation

    openseries enforces strict date formats:

    # Valid date formats
    -valid_dates = ['2023-01-01', '2023-12-31', '2024-02-29']  # ISO format
    +valid_dates = ['2023-01-01', '2023-12-31', '2024-02-29']  # ISO format
     
     # Invalid formats will raise ValidationError
     # This will fail with a validation error
     invalid_series = OpenTimeSeries.from_arrays(
    -     dates=['01/01/2023', '2023-1-1'],  # Wrong format
    +     dates=['01/01/2023', '2023-1-1'],  # Wrong format
          values=[100, 101]
     )
     
    @@ -249,9 +249,9 @@

    Value Validation# Handle NaN values appropriately values_with_nan = [100.0, np.nan, 99.0, 102.0] series = OpenTimeSeries.from_arrays( - dates=['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'], + dates=['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'], values=values_with_nan, - name="Data with NaN" + name="Data with NaN" ) # Clean NaN values (modifies original) @@ -265,7 +265,7 @@

    Length Consistency
    # This will raise an error
     # This will fail with a length mismatch error
     invalid_series = OpenTimeSeries.from_arrays(
    -     dates=['2023-01-01', '2023-01-02'],
    +     dates=['2023-01-01', '2023-01-02'],
          values=[100.0, 101.0, 102.0]  # Different length
     )
     
    @@ -278,14 +278,14 @@

    Data TransformationsPrice and Return Conversions

    # Assume we have a price series
     prices = OpenTimeSeries.from_arrays(
    -     dates=['2023-01-01', '2023-01-02', '2023-01-03'],
    +     dates=['2023-01-01', '2023-01-02', '2023-01-03'],
          values=[100.0, 102.0, 99.0],
    -     name="Stock Price"
    +     name="Stock Price"
     )
     
     # Convert to simple returns (modifies original)
     prices.value_to_ret()
    -print(f"Returns: {prices.values}")  # [0.02, -0.0294...]
    +print(f"Returns: {prices.values}")  # [0.02, -0.0294...]
     
     # Convert to log returns (modifies original)
     prices.value_to_log()
    @@ -302,19 +302,19 @@ 

    Price and Return Conversions

    Change the frequency of your data:

    # Daily to monthly (business month end) (modifies original)
    -series.resample_to_business_period_ends(freq="BME")
    +series.resample_to_business_period_ends(freq="BME")
     
     # Daily to quarterly (modifies original)
    -series.resample_to_business_period_ends(freq="BQE")
    +series.resample_to_business_period_ends(freq="BQE")
     
     # Daily to annual (modifies original)
    -series.resample_to_business_period_ends(freq="BYE")
    +series.resample_to_business_period_ends(freq="BYE")
     
     # Custom resampling with pandas frequency strings (modifies original)
    -series.resample(freq="W")
    +series.resample(freq="W")
     
     # Resample with specific method (modifies original)
    -series.resample(freq="W", method="mean")
    +series.resample(freq="W", method="mean")
     
    @@ -322,13 +322,13 @@

    Resampling

    Align data to business day calendars:

    # Align to US business days (modifies original)
    -series.align_index_to_local_cdays(countries="US")
    +series.align_index_to_local_cdays(countries="US")
     
     # Align to multiple countries (intersection) (modifies original)
    -series.align_index_to_local_cdays(countries=["US", "GB", "JP"])
    +series.align_index_to_local_cdays(countries=["US", "GB", "JP"])
     
     # Align to specific market calendar (modifies original)
    -series.align_index_to_local_cdays(markets="NYSE")
    +series.align_index_to_local_cdays(markets="NYSE")
     
    @@ -340,11 +340,11 @@

    NaN Handling Strategies
    import numpy as np
     
     # Create series with missing values
    -dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04']
    +dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04']
     values = [100.0, np.nan, 102.0, np.nan]
     
     series_with_nan = OpenTimeSeries.from_arrays(
    -     dates=dates, values=values, name="With NaN"
    +     dates=dates, values=values, name="With NaN"
     )
     
     # Forward fill missing values (for price series) (modifies original)
    @@ -359,7 +359,7 @@ 

    NaN Handling Strategies

    Dropping Missing Data

    # Remove NaN values entirely (modifies original)
    -series_with_nan.value_nan_handle(method="drop")
    +series_with_nan.value_nan_handle(method="drop")
     
    @@ -372,13 +372,13 @@

    Creating OpenFrame# Create multiple series series1 = OpenTimeSeries.from_arrays( - dates=['2023-01-01', '2023-01-02', '2023-01-03'], - values=[100, 102, 99], name="Asset A" + dates=['2023-01-01', '2023-01-02', '2023-01-03'], + values=[100, 102, 99], name="Asset A" ) series2 = OpenTimeSeries.from_arrays( - dates=['2023-01-01', '2023-01-02', '2023-01-03'], - values=[50, 51, 49], name="Asset B" + dates=['2023-01-01', '2023-01-02', '2023-01-03'], + values=[50, 51, 49], name="Asset B" ) # Create frame @@ -391,18 +391,18 @@

    Handling Different Date Ranges
    # Series with different start/end dates
     early_series = OpenTimeSeries.from_arrays(
    -     dates=['2022-12-01', '2023-01-01', '2023-01-02'],
    -     values=[95, 100, 102], name="Early Start"
    +     dates=['2022-12-01', '2023-01-01', '2023-01-02'],
    +     values=[95, 100, 102], name="Early Start"
     )
     
     late_series = OpenTimeSeries.from_arrays(
    -     dates=['2023-01-02', '2023-01-03', '2023-01-04'],
    -     values=[51, 49, 52], name="Late Start"
    +     dates=['2023-01-02', '2023-01-03', '2023-01-04'],
    +     values=[51, 49, 52], name="Late Start"
     )
     
     # Frame will align to common date range
     frame = OpenFrame(constituents=[early_series, late_series])
    -print(f"Frame date range: {frame.first_idx} to {frame.last_idx}")
    +print(f"Frame date range: {frame.first_idx} to {frame.last_idx}")
     

    @@ -410,8 +410,8 @@

    Handling Different Date Ranges

    # Add a new series
     new_series = OpenTimeSeries.from_arrays(
    -     dates=['2023-01-01', '2023-01-02', '2023-01-03'],
    -     values=[200, 205, 198], name="Asset C"
    +     dates=['2023-01-01', '2023-01-02', '2023-01-03'],
    +     values=[200, 205, 198], name="Asset C"
     )
     frame.add_timeseries(new_series)
     
    @@ -426,15 +426,15 @@ 

    Data Export and Import

    Excel Export

    # Export single series
    -series.to_xlsx(filename="single_series.xlsx")
    +series.to_xlsx(filename="single_series.xlsx")
     
     # Export frame (multiple series)
    -frame.to_xlsx(filename="multiple_series.xlsx")
    +frame.to_xlsx(filename="multiple_series.xlsx")
     
     # Export with custom sheet title
     series.to_xlsx(
    -     filename="formatted_export.xlsx",
    -     sheet_title="Analysis"
    +     filename="formatted_export.xlsx",
    +     sheet_title="Analysis"
     )
     
    @@ -442,13 +442,13 @@

    Excel Export

    JSON Export

    # Export series values only
    -series.to_json(what_output="values", filename="series_values.json")
    +series.to_json(what_output="values", filename="series_values.json")
     
     # Export full dataframe structure
    -series.to_json(what_output="tsdf", filename="series_dataframe.json")
    +series.to_json(what_output="tsdf", filename="series_dataframe.json")
     
     # Export frame data
    -frame.to_json(what_output="values", filename="frame_values.json")
    +frame.to_json(what_output="values", filename="frame_values.json")
     
    @@ -460,23 +460,23 @@

    Yahoo Finance Integration
    import yfinance as yf
     
     # Single asset
    -ticker = yf.Ticker("AAPL")
    -data = ticker.history(period="2y")
    +ticker = yf.Ticker("AAPL")
    +data = ticker.history(period="2y")
     
     apple = OpenTimeSeries.from_df(
    -     dframe=data['Close'],
    -     name="Apple Inc."
    +     dframe=data['Close'],
    +     name="Apple Inc."
     )
     
     # Multiple assets
    -tickers = ["AAPL", "GOOGL", "MSFT"]
    +tickers = ["AAPL", "GOOGL", "MSFT"]
     series_list = []
     
     for ticker_symbol in tickers:
          ticker = yf.Ticker(ticker_symbol)
    -     data = ticker.history(period="1y")
    +     data = ticker.history(period="1y")
          series = OpenTimeSeries.from_df(
    -          dframe=data['Close'],
    +          dframe=data['Close'],
               name=ticker_symbol
          )
          series_list.append(series)
    @@ -488,11 +488,11 @@ 

    Yahoo Finance Integration

    CSV Data

    # Load from CSV
    -df = pd.read_csv("stock_data.csv", index_col=0, parse_dates=True)
    +df = pd.read_csv("stock_data.csv", index_col=0, parse_dates=True)
     
     series = OpenTimeSeries.from_df(
    -     dframe=df['Close'],
    -     name="Stock from CSV"
    +     dframe=df['Close'],
    +     name="Stock from CSV"
     )
     
    @@ -503,16 +503,16 @@

    Data Quality Checks

    Validation Methods

    # Check for data quality issues
    -print(f"Series length: {series.length}")
    -print(f"Date range: {series.first_idx} to {series.last_idx}")
    -print(f"Span of days: {series.span_of_days}")
    +print(f"Series length: {series.length}")
    +print(f"Date range: {series.first_idx} to {series.last_idx}")
    +print(f"Span of days: {series.span_of_days}")
     
     # Check for gaps in data
     expected_length = (series.last_idx - series.first_idx).days + 1
     actual_length = series.length
     
     if expected_length != actual_length:
    -     print(f"Data gaps detected: expected {expected_length}, got {actual_length}")
    +     print(f"Data gaps detected: expected {expected_length}, got {actual_length}")
     
    @@ -523,11 +523,11 @@

    Outlier Detection# Detect outliers using the built-in method outliers = series.outliers(threshold=3.0) -print(f"Found {len(outliers)} outliers (|z| > 3)") +print(f"Found {len(outliers)} outliers (|z| > 3)") # For OpenFrame, outliers returns a DataFrame frame_outliers = frame.outliers(threshold=3.0) -print(f"Found outliers in frame: {len(frame_outliers)} rows") +print(f"Found outliers in frame: {len(frame_outliers)} rows") # Customize threshold and date range recent_outliers = series.outliers( @@ -546,7 +546,7 @@

    Memory Usagelarge_series = series # Assume this is large daily data # Reduce to monthly for analysis (modifies original) -large_series.resample_to_business_period_ends(freq="BME") +large_series.resample_to_business_period_ends(freq="BME") # Use monthly for computationally intensive operations monthly_metrics = large_series.all_properties() @@ -556,10 +556,10 @@

    Memory Usage

    Efficient Data Loading

    # When loading multiple assets, batch the operations
    -tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"]
    +tickers = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA"]
     
     # Download all at once
    -data = yf.download(tickers, period="2y")['Close']
    +data = yf.download(tickers, period="2y")['Close']
     
     # Create series efficiently
     series_list = []
    diff --git a/docs/build/html/user_guide/installation.html b/docs/build/html/user_guide/installation.html
    index bfb1687f..2a45baed 100644
    --- a/docs/build/html/user_guide/installation.html
    +++ b/docs/build/html/user_guide/installation.html
    @@ -184,7 +184,7 @@ 

    Core Dependencies# Create OpenTimeSeries series = OpenTimeSeries.from_df( - dframe=simulation.to_dataframe(name="Test Series", end=dt.date(2023, 12, 31)), + dframe=simulation.to_dataframe(name="Test Series", end=dt.date(2023, 12, 31)), valuetype=ValueType.RTRN ).to_cumret() # Convert returns to cumulative prices -print(f"Series length: {series.length}") -print(f"Annual return: {series.geo_ret:.2%}") +print(f"Series length: {series.length}") +print(f"Annual return: {series.geo_ret:.2%}")

    @@ -256,7 +256,7 @@

    Development Installation# Create a virtual environment and install dependencies uv venv venv source venv/bin/activate -uv pip install -e ".[dev]" +uv pip install -e ".[dev]"

    This will install additional development dependencies including:

    diff --git a/docs/build/html/user_guide/quickstart.html b/docs/build/html/user_guide/quickstart.html index eadd20a5..abf3c0f1 100644 --- a/docs/build/html/user_guide/quickstart.html +++ b/docs/build/html/user_guide/quickstart.html @@ -148,17 +148,17 @@

    Your First OpenTimeSeries# Convert simulation to OpenTimeSeries sp500 = OpenTimeSeries.from_df( - dframe=simulation.to_dataframe(name="S&P 500", end=dt.date(2023, 12, 31)), + dframe=simulation.to_dataframe(name="S&P 500", end=dt.date(2023, 12, 31)), valuetype=ValueType.RTRN ).to_cumret() # Convert returns to cumulative prices -sp500.set_new_label(lvl_zero="S&P 500") +sp500.set_new_label(lvl_zero="S&P 500") # Display basic information -print(f"Series: {sp500.label}") -print(f"Start date: {sp500.first_idx}") -print(f"End date: {sp500.last_idx}") -print(f"Number of observations: {sp500.length}") +print(f"Series: {sp500.label}") +print(f"Start date: {sp500.first_idx}") +print(f"End date: {sp500.last_idx}") +print(f"Number of observations: {sp500.length}")

    @@ -169,17 +169,17 @@

    Loading Data from External Sourcesfrom openseries import OpenTimeSeries # Download S&P 500 data -ticker = yf.Ticker("^GSPC") -data = ticker.history(period="2y") +ticker = yf.Ticker("^GSPC") +data = ticker.history(period="2y") # Create OpenTimeSeries from the Close prices -sp500 = OpenTimeSeries.from_df(dframe=data['Close']) +sp500 = OpenTimeSeries.from_df(dframe=data['Close']) # Set a more descriptive label -sp500.set_new_label(lvl_zero="S&P 500 Index") +sp500.set_new_label(lvl_zero="S&P 500 Index") -print(f"Loaded {sp500.length} observations") -print(f"Date range: {sp500.first_idx} to {sp500.last_idx}") +print(f"Loaded {sp500.length} observations") +print(f"Date range: {sp500.first_idx} to {sp500.last_idx}")

    @@ -187,21 +187,21 @@

    Loading Data from External Sources

    openseries provides a comprehensive set of financial metrics:

    # Key performance metrics
    -print(f"Total Return: {sp500.value_ret:.2%}")
    -print(f"Annualized Return (CAGR): {sp500.geo_ret:.2%}")
    -print(f"Annualized Volatility: {sp500.vol:.2%}")
    -print(f"Sharpe Ratio: {sp500.ret_vol_ratio:.2f}")
    -print(f"Maximum Drawdown: {sp500.max_drawdown:.2%}")
    +print(f"Total Return: {sp500.value_ret:.2%}")
    +print(f"Annualized Return (CAGR): {sp500.geo_ret:.2%}")
    +print(f"Annualized Volatility: {sp500.vol:.2%}")
    +print(f"Sharpe Ratio: {sp500.ret_vol_ratio:.2f}")
    +print(f"Maximum Drawdown: {sp500.max_drawdown:.2%}")
     
     # Risk metrics
    -print(f"95% VaR (daily): {sp500.var_down:.2%}")
    -print(f"95% CVaR (daily): {sp500.cvar_down:.2%}")
    -print(f"Sortino Ratio: {sp500.sortino_ratio:.2f}")
    +print(f"95% VaR (daily): {sp500.var_down:.2%}")
    +print(f"95% CVaR (daily): {sp500.cvar_down:.2%}")
    +print(f"Sortino Ratio: {sp500.sortino_ratio:.2f}")
     
     # Distribution statistics
    -print(f"Skewness: {sp500.skew:.2f}")
    -print(f"Kurtosis: {sp500.kurtosis:.2f}")
    -print(f"Positive Days: {sp500.positive_share:.1%}")
    +print(f"Skewness: {sp500.skew:.2f}")
    +print(f"Kurtosis: {sp500.kurtosis:.2f}")
    +print(f"Positive Days: {sp500.positive_share:.1%}")
     
    @@ -242,13 +242,13 @@

    Working with Multiple Assets (OpenFrame)import yfinance as yf # Download data for multiple assets -tickers = ["^GSPC", "^IXIC", "^RUT"] # S&P 500, NASDAQ, Russell 2000 -names = ["S&P 500", "NASDAQ", "Russell 2000"] +tickers = ["^GSPC", "^IXIC", "^RUT"] # S&P 500, NASDAQ, Russell 2000 +names = ["S&P 500", "NASDAQ", "Russell 2000"] series_list = [] for ticker, name in zip(tickers, names): - data = yf.Ticker(ticker).history(period="2y") - series = OpenTimeSeries.from_df(dframe=data['Close']) + data = yf.Ticker(ticker).history(period="2y") + series = OpenTimeSeries.from_df(dframe=data['Close']) series.set_new_label(lvl_zero=name) series_list.append(series) @@ -262,7 +262,7 @@

    Working with Multiple Assets (OpenFrame)# Calculate correlations correlations = frame.correl_matrix -print("\nCorrelation Matrix:") +print("\nCorrelation Matrix:") print(correlations)

    @@ -271,18 +271,18 @@

    Working with Multiple Assets (OpenFrame)

    Create and analyze portfolios:

    # Equal-weighted portfolio
    -portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
    +portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights")
     portfolio = OpenTimeSeries.from_df(dframe=portfolio_df)
     
    -print(f"Equal Weight Portfolio Return: {portfolio.geo_ret:.2%}")
    -print(f"Equal Weight Portfolio Volatility: {portfolio.vol:.2%}")
    -print(f"Equal Weight Portfolio Sharpe: {portfolio.ret_vol_ratio:.2f}")
    +print(f"Equal Weight Portfolio Return: {portfolio.geo_ret:.2%}")
    +print(f"Equal Weight Portfolio Volatility: {portfolio.vol:.2%}")
    +print(f"Equal Weight Portfolio Sharpe: {portfolio.ret_vol_ratio:.2f}")
     
     # Create custom weighted portfolio
     frame.weights = [0.8, 0.2]  # Custom allocation
    -custom_df = frame.make_portfolio(name="Custom Portfolio")
    +custom_df = frame.make_portfolio(name="Custom Portfolio")
     custom_portfolio = OpenTimeSeries.from_df(dframe=custom_df)
    -print(f"Custom Portfolio Sharpe: {custom_portfolio.ret_vol_ratio:.2f}")
    +print(f"Custom Portfolio Sharpe: {custom_portfolio.ret_vol_ratio:.2f}")
     
     # Compare with individual assets
     frame.add_timeseries(portfolio)
    @@ -297,7 +297,7 @@ 

    Data Transformationsopenseries provides various data transformation methods:

    @@ -316,10 +316,10 @@

    Data TransformationsExporting Results

    Save your analysis results:

    # Export to Excel
    -sp500.to_xlsx(filename="sp500_analysis.xlsx")
    +sp500.to_xlsx(filename="sp500_analysis.xlsx")
     
     # Export to JSON
    -sp500.to_json(filename="sp500_data.json", what_output="tsdf")
    +sp500.to_json(filename="sp500_data.json", what_output="tsdf")
     
    @@ -327,10 +327,10 @@

    Exporting Results

    openseries handles business day calendars automatically:

    # Align to Swedish business days (modifies original)
    -sp500.align_index_to_local_cdays(countries="SE")
    +sp500.align_index_to_local_cdays(countries="SE")
     
     # Use multiple countries (modifies original)
    -sp500.align_index_to_local_cdays(countries=["US", "GB"])
    +sp500.align_index_to_local_cdays(countries=["US", "GB"])
     
     # Handle missing values (modifies original)
     sp500.value_nan_handle()  # Forward fill NaN values
    @@ -361,8 +361,8 @@ 

    Key Concepts to Remember

    Here are some common usage patterns:

    # Pattern 1: Load, analyze, visualize
    -series = OpenTimeSeries.from_df(dframe=data['Close'])
    -series.set_new_label(lvl_zero="Asset")
    +series = OpenTimeSeries.from_df(dframe=data['Close'])
    +series.set_new_label(lvl_zero="Asset")
     metrics = series.all_properties()
     series.plot_series()
     
    @@ -372,14 +372,14 @@ 

    Common Patternscorrelations = frame.correl_matrix # Pattern 3: Portfolio construction (built-in strategies) -portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights") +portfolio_df = frame.make_portfolio(name="Equal Weight", weight_strat="eq_weights") portfolio = OpenTimeSeries.from_df(dframe=portfolio_df) frame.add_timeseries(portfolio) # Pattern 3b: Custom portfolio construction (create fresh frame) custom_frame = OpenFrame(constituents=[series1, series2, series3]) custom_frame.weights = [0.4, 0.3, 0.3] -custom_df = custom_frame.make_portfolio(name="Custom Portfolio") +custom_df = custom_frame.make_portfolio(name="Custom Portfolio") custom_portfolio = OpenTimeSeries.from_df(dframe=custom_df) # Pattern 4: Risk analysis diff --git a/docs/requirements.txt b/docs/requirements.txt index a47d4595..64fc5dea 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ sphinx>=9.0.4 sphinx-autobuild>=2025.8.25 sphinx-autodoc-typehints>=3.6.0 -sphinx-rtd-theme>=3.1.0rc1 +sphinx-rtd-theme>=3.1.0 diff --git a/docs/source/development/contributing.rst b/docs/source/development/contributing.rst index b5931a2b..cfb3b523 100644 --- a/docs/source/development/contributing.rst +++ b/docs/source/development/contributing.rst @@ -17,25 +17,19 @@ Development Setup git clone https://github.com/yourusername/openseries.git cd openseries -3. Install uv (if not already installed): +3. Create the development environment. This installs the pinned uv version + (``uv==0.11.21``), syncs locked ``dev`` and ``docs`` dependencies from + ``uv.lock``, and installs pre-commit hooks: .. code-block:: bash - pip install uv + make install -4. Create a virtual environment and install dependencies: +On Windows: -.. code-block:: bash - - uv venv venv - source venv/bin/activate - uv pip install -e ".[dev,docs]" - -5. Install pre-commit hooks: - -.. code-block:: bash +.. code-block:: powershell - pre-commit install + .\make.ps1 make Development Workflow ~~~~~~~~~~~~~~~~~~~~ @@ -377,7 +371,7 @@ Recommended settings in ``.vscode/settings.json``: .. code-block:: json { - "python.defaultInterpreterPath": ".venv/bin/python", + "python.defaultInterpreterPath": "venv/bin/python", "python.linting.enabled": true, "python.linting.ruffEnabled": true, "python.formatting.provider": "ruff", diff --git a/docs/source/user_guide/installation.rst b/docs/source/user_guide/installation.rst index 9561e284..69f66f93 100644 --- a/docs/source/user_guide/installation.rst +++ b/docs/source/user_guide/installation.rst @@ -49,25 +49,26 @@ openseries automatically installs the following dependencies: Core Dependencies ~~~~~~~~~~~~~~~~~ -- **pandas** (>=2.1.2,<3.0.0) - Data manipulation and analysis -- **numpy** (>=1.23.2,!=2.3.0,<3.0.0) - Numerical computing -- **pydantic** (>=2.5.2,<3.0.0) - Data validation and settings management -- **plotly** (>=5.18.0,<7.0.0) - Interactive plotting -- **scipy** (>=1.11.4,<2.0.0) - Scientific computing -- **scikit-learn** (>=1.4.0,<2.0.0) - Machine learning utilities +- **pandas** (>=2.1.2) - Data manipulation and analysis +- **numpy** (>=1.23.2) - Numerical computing +- **pydantic** (>=2.5.2) - Data validation and settings management +- **plotly** (>=5.18.0) - Interactive plotting +- **scipy** (>=1.14.1) - Scientific computing +- **scikit-learn** (>=1.4.0) - Machine learning utilities Financial and Date Utilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- **exchange-calendars** (>=4.8,<6.0) - Trading calendar support -- **holidays** (>=0.30,<1.0) - Holiday calendar support -- **python-dateutil** (>=2.8.2,<4.0.0) - Date parsing utilities +- **exchange-calendars** (>=4.8) - Trading calendar support +- **holidays** (>=0.30) - Holiday calendar support +- **python-dateutil** (>=2.8.2) - Date parsing utilities +- **tzdata** (>=2025.3) - IANA time zone data File and Network Support ~~~~~~~~~~~~~~~~~~~~~~~~~ -- **openpyxl** (>=3.1.2,<5.0.0) - Excel file support -- **requests** (>=2.20.0,<3.0.0) - HTTP library +- **openpyxl** (>=3.1.2) - Excel file support +- **requests** (>=2.20.0) - HTTP library Optional Dependencies ~~~~~~~~~~~~~~~~~~~~~ @@ -117,27 +118,27 @@ You can also run a quick test: Development Installation ------------------------ -If you plan to contribute to openseries or need the development dependencies: +If you plan to contribute to openseries or need the development dependencies, +use the same pinned tooling as CI (``uv==0.11.21``): .. code-block:: bash git clone https://github.com/CaptorAB/openseries.git cd openseries + make install - # Install uv (if not already installed) - pip install uv +On Windows, run ``.\make.ps1 make`` instead of ``make install``. - # Create a virtual environment and install dependencies - uv venv venv - source venv/bin/activate - uv pip install -e ".[dev]" +This creates ``venv``, installs locked runtime, development, and documentation +dependencies from ``uv.lock``, and installs pre-commit hooks. Development +dependencies include: -This will install additional development dependencies including: - -- **pytest** - Testing framework -- **mypy** - Static type checking -- **ruff** - Linting and formatting -- **pre-commit** - Git hooks for code quality +- **pytest** (>=9.1.0) - Testing framework +- **pytest-cov** (>=7.1.0) - Coverage plugin +- **pytest-xdist** (>=3.8.0) - Parallel test runner +- **mypy** (==2.1.0) - Static type checking +- **ruff** (==0.15.18) - Linting and formatting +- **pre-commit** (>=4.6.0) - Git hooks for code quality Troubleshooting --------------- diff --git a/openseries/__init__.py b/openseries/__init__.py index 817ee99d..dec237eb 100644 --- a/openseries/__init__.py +++ b/openseries/__init__.py @@ -1,6 +1,6 @@ """openseries package initialization.""" -__version__ = "2.1.9" +__version__ = "2.1.10" from .datefixer import ( date_fix, diff --git a/openseries/_common_model.py b/openseries/_common_model.py index 44570bf9..07ceecdd 100644 --- a/openseries/_common_model.py +++ b/openseries/_common_model.py @@ -14,7 +14,7 @@ from string import ascii_letters from typing import TYPE_CHECKING, Any, Generic, Literal, Self, cast -from numpy import asarray, float64, inf, isnan, log, maximum, sqrt +from numpy import asarray, float64, inf, isnan, linspace, log, maximum, sqrt from .owntypes import ( CaptorLogoType, @@ -58,10 +58,10 @@ to_datetime, ) from pandas.tseries.offsets import CustomBusinessDay -from plotly.figure_factory import create_distplot # type: ignore[import-untyped] from plotly.graph_objs import Figure # type: ignore[import-untyped] from pydantic import BaseModel, ConfigDict, DirectoryPath, Field from scipy.stats import ( + gaussian_kde, kurtosis, norm, skew, @@ -210,6 +210,111 @@ def _calculate_time_factor( return data.count() / fraction +_DISTPLOT_COLORS = ( + "rgb(31, 119, 180)", + "rgb(255, 127, 14)", + "rgb(44, 160, 44)", + "rgb(214, 39, 40)", + "rgb(148, 103, 189)", + "rgb(140, 86, 75)", + "rgb(227, 119, 194)", + "rgb(127, 127, 127)", + "rgb(188, 189, 34)", + "rgb(23, 190, 207)", +) +_DISTPLOT_CURVE_POINTS = 500 +_DISTPLOT_BIN_SIZE = 1.0 + + +def _create_distplot( + hist_data: list[Series[float]], + group_labels: list[str], + curve_type: LiteralPlotlyHistogramCurveType, + histnorm: LiteralPlotlyHistogramHistNorm, + *, + show_rug: bool, +) -> Figure: + """Create a distribution curve figure compatible with Plotly 6 and 7. + + Replaces ``plotly.figure_factory.create_distplot``, which was removed in + Plotly 7, for the ``plot_histogram(..., plot_type="lines")`` path. + + Args: + hist_data: One series of observations per trace. + group_labels: Legend names aligned with ``hist_data``. + curve_type: ``"kde"`` or ``"normal"`` overlay. + histnorm: Histogram normalization; ``"probability"`` scales the curve + by the default bin size as ``create_distplot`` did. + show_rug: If True, add a rug plot on a secondary y-axis. + + Returns: + A Plotly Figure of scatter line traces (and optional rug traces). + """ + curve_traces: list[dict[str, Any]] = [] + rug_traces: list[dict[str, Any]] = [] + for index, series in enumerate(hist_data): + values = asarray(series, dtype=float64) + start = float(min(series)) + end = float(max(series)) + curve_x = linspace(start, end, _DISTPLOT_CURVE_POINTS, endpoint=False) + if curve_type == "normal": + loc, scale = norm.fit(values) + curve_y = norm.pdf(curve_x, loc=loc, scale=scale) + else: + curve_y = gaussian_kde(values)(curve_x) + if histnorm == "probability": + curve_y = curve_y * _DISTPLOT_BIN_SIZE + color = _DISTPLOT_COLORS[index % len(_DISTPLOT_COLORS)] + label = group_labels[index] + curve_traces.append( + { + "type": "scatter", + "x": curve_x, + "y": curve_y, + "xaxis": "x1", + "yaxis": "y1", + "mode": "lines", + "name": label, + "legendgroup": label, + "showlegend": True, + "marker": {"color": color}, + }, + ) + if show_rug: + rug_traces.append( + { + "type": "scatter", + "x": values, + "y": [label] * len(values), + "xaxis": "x1", + "yaxis": "y2", + "mode": "markers", + "name": label, + "legendgroup": label, + "showlegend": False, + "marker": {"color": color, "symbol": "line-ns-open"}, + }, + ) + + layout: dict[str, Any] = { + "barmode": "overlay", + "hovermode": "closest", + "legend": {"traceorder": "reversed"}, + "xaxis1": {"domain": [0.0, 1.0], "anchor": "y2", "zeroline": False}, + "yaxis1": {"domain": [0.0, 1], "anchor": "free", "position": 0.0}, + } + if show_rug: + layout["yaxis1"] = {"domain": [0.35, 1], "anchor": "free", "position": 0.0} + layout["yaxis2"] = { + "domain": [0, 0.25], + "anchor": "x1", + "dtick": 1, + "showticklabels": False, + } + + return Figure(data=curve_traces + rug_traces, layout=layout) + + class _CommonModel(BaseModel, Generic[SeriesOrFloat_co]): """Declare _CommonModel.""" @@ -1437,13 +1542,12 @@ def plot_histogram( ) elif plot_type == "lines": hist_data = [self.tsdf[col] for col in self.tsdf.columns] - figure = create_distplot( + figure = _create_distplot( hist_data=hist_data, - curve_type=curve_type, group_labels=labels, - show_hist=False, - show_rug=show_rug, + curve_type=curve_type, histnorm=histnorm, + show_rug=show_rug, ) figure.update_layout(dict1=fig_dict["layout"]) else: diff --git a/openseries/portfoliotools.py b/openseries/portfoliotools.py index 41b862ec..f42d3d5a 100644 --- a/openseries/portfoliotools.py +++ b/openseries/portfoliotools.py @@ -791,7 +791,6 @@ def _generate_sharpeplot_output( filename=str(plotfile), auto_open=auto_open, auto_play=False, - link_text="", include_plotlyjs=include_plotlyjs, config=fig["config"], output_type=output_type, diff --git a/pyproject.toml b/pyproject.toml index 42ec189f..e41dea98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openseries" -version = "2.1.9" +version = "2.1.10" description = "Tools for analyzing financial timeseries." authors = [ { name = "Martin Karrin" }, @@ -51,7 +51,7 @@ dependencies = [ "requests>=2.20.0", "scipy>=1.14.1", "scikit-learn>=1.4.0", - "tzdata (>=2025.3)" + "tzdata>=2025.3", ] [project.urls] @@ -79,7 +79,7 @@ docs = [ "sphinx>=9.0.4", "sphinx-autobuild>=2025.8.25", "sphinx-autodoc-typehints>=3.6.0", - "sphinx-rtd-theme>=3.1.0rc1", + "sphinx-rtd-theme>=3.1.0", ] [build-system] @@ -97,7 +97,7 @@ openseries = [ ] [tool.mypy] -python_version = "3.11" +python_version = "3.12" mypy_path = ["."] cache_dir = ".mypy_cache" exclude = ["venv/*", "docs/*"] diff --git a/tests/test_common_model_internals.py b/tests/test_common_model_internals.py index edf55026..8885ab23 100644 --- a/tests/test_common_model_internals.py +++ b/tests/test_common_model_internals.py @@ -10,7 +10,7 @@ from numpy import float64 from pandas import DataFrame, MultiIndex, Series -from openseries._common_model import _CommonModel +from openseries._common_model import _CommonModel, _create_distplot from openseries.owntypes import ValueType _EXPECTED_SCALAR = 0.25 @@ -75,3 +75,58 @@ def test_get_or_set_countries_get_raises_without_constituents() -> None: raw = _CommonModel[float].model_construct(tsdf=DataFrame(dtype="float64")) with pytest.raises(TypeError, match="Cannot get countries without constituents"): raw._get_or_set_countries(None) + + +def _distplot_sample_series() -> Series[float]: + return Series([1.0, 1.2, 1.1, 2.0, 2.1, 1.9, 3.0, 2.8], dtype=float64) + + +def test_create_distplot_kde_without_rug() -> None: + """KDE lines plot emits one legend scatter trace and no rug axis.""" + fig = _create_distplot( + hist_data=[_distplot_sample_series()], + group_labels=["alpha"], + curve_type="kde", + histnorm="probability", + show_rug=False, + ) + if len(fig.data) != 1: + msg = f"Expected a single curve trace, got {len(fig.data)}" + raise AssertionError(msg) + trace = fig.data[0].to_plotly_json() + if trace["type"] != "scatter" or trace["mode"] != "lines": + msg = f"Unexpected curve trace: type={trace['type']} mode={trace['mode']}" + raise AssertionError(msg) + if trace["name"] != "alpha": + msg = f"Unexpected trace name: {trace['name']}" + raise AssertionError(msg) + if "yaxis2" in fig.to_dict()["layout"]: + msg = "Rug y-axis should be absent when show_rug is False" + raise AssertionError(msg) + + +def test_create_distplot_normal_with_rug() -> None: + """Normal curve plus rug uses a secondary axis and hides the rug legend.""" + labels = ["alpha", "beta"] + fig = _create_distplot( + hist_data=[_distplot_sample_series(), _distplot_sample_series() + 1.0], + group_labels=labels, + curve_type="normal", + histnorm="probability density", + show_rug=True, + ) + expected_traces = len(labels) * 2 + if len(fig.data) != expected_traces: + msg = f"Expected two curve traces and two rug traces, got {len(fig.data)}" + raise AssertionError(msg) + curve_names = [trace["name"] for trace in fig.data[:2]] + if curve_names != labels: + msg = f"Curve labels mismatch: {curve_names}" + raise AssertionError(msg) + rug = fig.data[2].to_plotly_json() + if rug["mode"] != "markers" or rug["showlegend"] is not False: + msg = "Rug traces must be unmarked in the legend" + raise AssertionError(msg) + if "yaxis2" not in fig.to_dict()["layout"]: + msg = "Rug y-axis should be present when show_rug is True" + raise AssertionError(msg) diff --git a/tests/test_version_alignment.py b/tests/test_version_alignment.py new file mode 100644 index 00000000..c68574cc --- /dev/null +++ b/tests/test_version_alignment.py @@ -0,0 +1,443 @@ +"""Test that tool and dependency versions stay aligned across config files.""" + +from __future__ import annotations + +import re +import tomllib +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).parent.parent +PYPROJECT_PATH = ROOT / "pyproject.toml" +LOCK_PATH = ROOT / "uv.lock" +PRE_COMMIT_PATH = ROOT / ".pre-commit-config.yaml" +MAKEFILE_PATH = ROOT / "Makefile" +MAKE_PS1_PATH = ROOT / "make.ps1" +DOCS_REQUIREMENTS_PATH = ROOT / "docs" / "requirements.txt" +INSTALLATION_RST_PATH = ROOT / "docs" / "source" / "user_guide" / "installation.rst" +CONTRIBUTING_RST_PATH = ROOT / "docs" / "source" / "development" / "contributing.rst" +PYTHON_VERSION_PATH = ROOT / ".python-version" +ZIZMOR_SCRIPT_PATH = ROOT / "scripts" / "run-zizmor.sh" +WORKFLOW_DIR = ROOT / ".github" / "workflows" + +UV_WORKFLOW_FILES = ( + "test.yml", + "build.yml", + "docs.yml", + "deploy.yml", + "codeql.yml", + "zizmor.yml", + "supply-chain.yml", +) + +MYPY_ADDITIONAL_DEPENDENCIES = ( + "pandas-stubs", + "pydantic", + "scipy-stubs", + "types-openpyxl", + "types-python-dateutil", + "types-requests", +) + + +class VersionAlignmentError(Exception): + """Raised when a pinned version is not aligned across project files.""" + + +def _read_text(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def _load_toml(path: Path) -> dict[str, Any]: + with path.open("rb") as handle: + return tomllib.load(handle) + + +def _requirement_parts(requirement: str) -> tuple[str, str]: + compact = requirement.strip().replace(" ", "") + for separator in ("==", ">="): + if separator in compact: + name, spec = compact.split(separator, 1) + return name, f"{separator}{spec}" + msg = f"Unsupported requirement specifier: {requirement}" + raise VersionAlignmentError(msg) + + +def _requirement_map(requirements: list[str]) -> dict[str, str]: + return dict(_requirement_parts(item) for item in requirements) + + +def _makefile_value(text: str, name: str) -> str: + match = re.search(rf"^{name} \?= (.+)$", text, flags=re.MULTILINE) + if match is None: + msg = f"{MAKEFILE_PATH.name} is missing {name}" + raise VersionAlignmentError(msg) + return match.group(1).strip() + + +def _ps1_value(text: str, name: str) -> str: + match = re.search(rf'\${name} = "([^"]+)"', text) + if match is None: + msg = f"{MAKE_PS1_PATH.name} is missing {name}" + raise VersionAlignmentError(msg) + return match.group(1) + + +def _workflow_env_value(text: str, name: str) -> str: + match = re.search(rf"^\s*{name}: \"([^\"]+)\"", text, flags=re.MULTILINE) + if match is None: + msg = f"Workflow is missing {name}" + raise VersionAlignmentError(msg) + return match.group(1) + + +def _pre_commit_rev(text: str, repo_url: str) -> str: + pattern = rf"repo: {re.escape(repo_url)}\n\s+rev: (.+)" + match = re.search(pattern, text) + if match is None: + msg = f"{PRE_COMMIT_PATH.name} is missing rev for {repo_url}" + raise VersionAlignmentError(msg) + return match.group(1).strip() + + +def _pre_commit_additional_dependencies(text: str) -> dict[str, str]: + match = re.search( + r"additional_dependencies:\n((?: - .+\n)+)", + text, + ) + if match is None: + msg = f"{PRE_COMMIT_PATH.name} is missing mypy additional_dependencies" + raise VersionAlignmentError(msg) + requirements = [ + line.strip()[2:].strip() + for line in match.group(1).splitlines() + if line.strip().startswith("- ") + ] + return _requirement_map(requirements) + + +def _lock_requires_dist( + lock_data: dict[str, Any], +) -> dict[tuple[str, str | None], str]: + packages = lock_data.get("package", []) + for package in packages: + if package.get("name") != "openseries": + continue + metadata = package.get("metadata", {}) + requires_dist = metadata.get("requires-dist", []) + mapped: dict[tuple[str, str | None], str] = {} + for item in requires_dist: + extra = None + marker = item.get("marker") + if marker is not None: + extra_match = re.search(r"extra == '([^']+)'", marker) + if extra_match is not None: + extra = extra_match.group(1) + mapped[(item["name"], extra)] = item["specifier"] + return mapped + msg = "uv.lock is missing the openseries package metadata" + raise VersionAlignmentError(msg) + + +def _python_minor_versions(requires_python: str) -> list[str]: + match = re.fullmatch(r">=(\d+\.\d+),<(\d+\.\d+)", requires_python.replace(" ", "")) + if match is None: + msg = f"Unexpected requires-python: {requires_python}" + raise VersionAlignmentError(msg) + start_major, start_minor = (int(part) for part in match.group(1).split(".")) + end_major, end_minor = (int(part) for part in match.group(2).split(".")) + if start_major != end_major: + msg = f"requires-python spans multiple majors: {requires_python}" + raise VersionAlignmentError(msg) + return [f"{start_major}.{minor}" for minor in range(start_minor, end_minor)] + + +def _raise_mismatch(label: str, expected: str, actual: str) -> None: + msg = f"{label} is {actual!r}, expected {expected!r}" + raise VersionAlignmentError(msg) + + +def _required_search(pattern: str, text: str, label: str) -> re.Match[str]: + match = re.search(pattern, text, flags=re.MULTILINE) + if match is None: + msg = f"{label} is missing" + raise VersionAlignmentError(msg) + return match + + +def _check_python_classifiers(classifiers: list[str], versions: list[str]) -> None: + for version in versions: + expected = f"Programming Language :: Python :: {version}" + if expected not in classifiers: + msg = f"pyproject.toml classifiers are missing {expected}" + raise VersionAlignmentError(msg) + + +def _check_default_python_pins(python_version: str) -> None: + actual_default = _read_text(PYTHON_VERSION_PATH).strip() + if actual_default != python_version: + _raise_mismatch(".python-version", python_version, actual_default) + language_match = _required_search( + r"^ python: python(.+)$", + _read_text(PRE_COMMIT_PATH), + "pre-commit default_language_version", + ) + if language_match.group(1) != python_version: + _raise_mismatch( + "pre-commit python", + python_version, + language_match.group(1), + ) + rtd_match = _required_search( + r'^ python: "([^"]+)"$', + _read_text(ROOT / ".readthedocs.yaml"), + ".readthedocs.yaml python version", + ) + if rtd_match.group(1) != python_version: + _raise_mismatch(".readthedocs.yaml python", python_version, rtd_match.group(1)) + + +def _check_python_matrix_and_docs(versions: list[str]) -> None: + matrix_match = _required_search( + r"python-version: \[ ([^\]]+) \]", + _read_text(WORKFLOW_DIR / "build.yml"), + "build.yml python-version matrix", + ) + matrix_versions = [ + item.strip().strip("'\"") for item in matrix_match.group(1).split(",") + ] + if matrix_versions != versions: + _raise_mismatch( + "build.yml python-version matrix", + ", ".join(versions), + ", ".join(matrix_versions), + ) + listed = ", ".join(versions) + if listed not in _read_text(INSTALLATION_RST_PATH): + msg = f"{INSTALLATION_RST_PATH.name} is missing Python versions {listed}" + raise VersionAlignmentError(msg) + + +def _check_type_checker_targets( + pyproject: dict[str, Any], + versions: list[str], +) -> None: + mypy_version = pyproject["tool"]["mypy"]["python_version"] + if mypy_version not in versions: + msg = ( + "tool.mypy python_version must be a supported Python version, " + f"got {mypy_version!r}" + ) + raise VersionAlignmentError(msg) + ruff_target = pyproject["tool"]["ruff"]["target-version"] + expected_ruff_target = f"py{versions[0].replace('.', '')}" + if ruff_target != expected_ruff_target: + _raise_mismatch("tool.ruff target-version", expected_ruff_target, ruff_target) + + +class TestVersionAlignment: + """class to verify dependency and tool versions stay aligned.""" + + def test_lockfile_matches_pyproject(self: TestVersionAlignment) -> None: + """Test uv.lock metadata matches pyproject.toml specifiers.""" + pyproject = _load_toml(PYPROJECT_PATH) + lock_requires = _lock_requires_dist(_load_toml(LOCK_PATH)) + expected: dict[tuple[str, str | None], str] = {} + for requirement in pyproject["project"]["dependencies"]: + name, specifier = _requirement_parts(requirement) + expected[(name, None)] = specifier + extras = pyproject["project"]["optional-dependencies"] + for extra, requirements in extras.items(): + for requirement in requirements: + name, specifier = _requirement_parts(requirement) + expected[(name, extra)] = specifier + if lock_requires != expected: + msg = ( + "uv.lock package metadata does not match pyproject.toml: " + f"{lock_requires} != {expected}" + ) + raise VersionAlignmentError(msg) + + def test_docs_requirements_match_docs_extra(self: TestVersionAlignment) -> None: + """Test docs/requirements.txt matches the pyproject docs extra.""" + pyproject = _load_toml(PYPROJECT_PATH) + expected = [ + item.replace(" ", "") + for item in pyproject["project"]["optional-dependencies"]["docs"] + ] + actual = [ + line.strip().replace(" ", "") + for line in _read_text(DOCS_REQUIREMENTS_PATH).splitlines() + if line.strip() and not line.strip().startswith("#") + ] + if actual != expected: + msg = ( + "docs/requirements.txt does not match pyproject docs extra: " + f"{actual} != {expected}" + ) + raise VersionAlignmentError(msg) + + def test_tool_versions_match(self: TestVersionAlignment) -> None: + """Test uv, ruff, and mypy versions match across tooling files.""" + pyproject = _load_toml(PYPROJECT_PATH) + dev_requirements = _requirement_map( + pyproject["project"]["optional-dependencies"]["dev"], + ) + pre_commit = _read_text(PRE_COMMIT_PATH) + makefile = _read_text(MAKEFILE_PATH) + make_ps1 = _read_text(MAKE_PS1_PATH) + + uv_version = _makefile_value(makefile, "UV_VERSION") + if _ps1_value(make_ps1, "UV_VERSION") != uv_version: + _raise_mismatch( + "make.ps1 UV_VERSION", + uv_version, + _ps1_value(make_ps1, "UV_VERSION"), + ) + uv_rev = _pre_commit_rev( + pre_commit, + "https://github.com/astral-sh/uv-pre-commit", + ) + if uv_rev != uv_version: + _raise_mismatch("pre-commit uv rev", uv_version, uv_rev) + + ruff_spec = dev_requirements["ruff"] + if not ruff_spec.startswith("=="): + msg = f"ruff must be pinned exactly in pyproject.toml, got {ruff_spec}" + raise VersionAlignmentError(msg) + ruff_version = ruff_spec[2:] + ruff_rev = _pre_commit_rev( + pre_commit, + "https://github.com/astral-sh/ruff-pre-commit", + ) + if ruff_rev != f"v{ruff_version}": + _raise_mismatch("pre-commit ruff rev", f"v{ruff_version}", ruff_rev) + + mypy_spec = dev_requirements["mypy"] + if not mypy_spec.startswith("=="): + msg = f"mypy must be pinned exactly in pyproject.toml, got {mypy_spec}" + raise VersionAlignmentError(msg) + mypy_version = mypy_spec[2:] + mypy_rev = _pre_commit_rev( + pre_commit, + "https://github.com/pre-commit/mirrors-mypy", + ) + if mypy_rev != f"v{mypy_version}": + _raise_mismatch("pre-commit mypy rev", f"v{mypy_version}", mypy_rev) + + for workflow_name in UV_WORKFLOW_FILES: + workflow_text = _read_text(WORKFLOW_DIR / workflow_name) + actual = _workflow_env_value(workflow_text, "UV_VERSION") + if actual != uv_version: + _raise_mismatch( + f"{workflow_name} UV_VERSION", + uv_version, + actual, + ) + + def test_audit_and_zizmor_versions_match(self: TestVersionAlignment) -> None: + """Test pip-audit and zizmor versions match across scripts and CI.""" + makefile = _read_text(MAKEFILE_PATH) + make_ps1 = _read_text(MAKE_PS1_PATH) + pip_audit_version = _makefile_value(makefile, "PIP_AUDIT_VERSION") + if _ps1_value(make_ps1, "PIP_AUDIT_VERSION") != pip_audit_version: + _raise_mismatch( + "make.ps1 PIP_AUDIT_VERSION", + pip_audit_version, + _ps1_value(make_ps1, "PIP_AUDIT_VERSION"), + ) + supply_chain = _read_text(WORKFLOW_DIR / "supply-chain.yml") + actual_pip_audit = _workflow_env_value(supply_chain, "PIP_AUDIT_VERSION") + if actual_pip_audit != pip_audit_version: + _raise_mismatch( + "supply-chain.yml PIP_AUDIT_VERSION", + pip_audit_version, + actual_pip_audit, + ) + + zizmor_workflow = _read_text(WORKFLOW_DIR / "zizmor.yml") + zizmor_version = _workflow_env_value(zizmor_workflow, "ZIZMOR_VERSION") + script_match = re.search( + r"^readonly ZIZMOR_VERSION=(.+)$", + _read_text(ZIZMOR_SCRIPT_PATH), + flags=re.MULTILINE, + ) + if script_match is None: + msg = "scripts/run-zizmor.sh is missing ZIZMOR_VERSION" + raise VersionAlignmentError(msg) + if script_match.group(1) != zizmor_version: + _raise_mismatch( + "scripts/run-zizmor.sh ZIZMOR_VERSION", + zizmor_version, + script_match.group(1), + ) + + def test_mypy_additional_dependencies_match_pyproject( + self: TestVersionAlignment, + ) -> None: + """Test pre-commit mypy extra deps match pyproject specifiers.""" + pyproject = _load_toml(PYPROJECT_PATH) + declared = _requirement_map(pyproject["project"]["dependencies"]) + declared.update( + _requirement_map(pyproject["project"]["optional-dependencies"]["dev"]), + ) + actual = _pre_commit_additional_dependencies(_read_text(PRE_COMMIT_PATH)) + expected = { + name: declared[name] + for name in MYPY_ADDITIONAL_DEPENDENCIES + if name in declared + } + if actual != expected: + msg = ( + "pre-commit mypy additional_dependencies do not match " + f"pyproject.toml: {actual} != {expected}" + ) + raise VersionAlignmentError(msg) + + def test_docs_list_pyproject_specifiers(self: TestVersionAlignment) -> None: + """Test installation and contributing docs list current specifiers.""" + pyproject = _load_toml(PYPROJECT_PATH) + installation = _read_text(INSTALLATION_RST_PATH) + contributing = _read_text(CONTRIBUTING_RST_PATH) + uv_version = _makefile_value(_read_text(MAKEFILE_PATH), "UV_VERSION") + + runtime = _requirement_map(pyproject["project"]["dependencies"]) + for name, specifier in runtime.items(): + expected = f"**{name}** ({specifier})" + if expected not in installation: + msg = f"{INSTALLATION_RST_PATH.name} is missing {expected}" + raise VersionAlignmentError(msg) + + dev = _requirement_map(pyproject["project"]["optional-dependencies"]["dev"]) + documented_dev = ( + "pytest", + "pytest-cov", + "pytest-xdist", + "mypy", + "ruff", + "pre-commit", + ) + for name in documented_dev: + expected = f"**{name}** ({dev[name]})" + if expected not in installation: + msg = f"{INSTALLATION_RST_PATH.name} is missing {expected}" + raise VersionAlignmentError(msg) + + uv_pin = f"uv=={uv_version}" + if uv_pin not in installation: + msg = f"{INSTALLATION_RST_PATH.name} is missing {uv_pin}" + raise VersionAlignmentError(msg) + if uv_pin not in contributing: + msg = f"{CONTRIBUTING_RST_PATH.name} is missing {uv_pin}" + raise VersionAlignmentError(msg) + + def test_python_versions_match(self: TestVersionAlignment) -> None: + """Test declared Python versions match CI, docs, and tool targets.""" + pyproject = _load_toml(PYPROJECT_PATH) + requires_python = pyproject["project"]["requires-python"].replace(" ", "") + versions = _python_minor_versions(requires_python) + default_version = versions[-1] + _check_python_classifiers(pyproject["project"]["classifiers"], versions) + _check_default_python_pins(default_version) + _check_python_matrix_and_docs(versions) + _check_type_checker_targets(pyproject, versions) diff --git a/uv.lock b/uv.lock index 54b68f6f..4171ce81 100644 --- a/uv.lock +++ b/uv.lock @@ -6,10 +6,10 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.12' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] @@ -24,64 +24,87 @@ wheels = [ [[package]] name = "annotated-types" -version = "0.7.0" +version = "0.8.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" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, ] [[package]] name = "anyio" -version = "4.14.0" +version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" }, + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] [[package]] name = "ast-serialize" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, - { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, - { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, - { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, - { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, - { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, - { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, - { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, - { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, - { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, - { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, - { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, - { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, - { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, - { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, - { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, - { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, - { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, - { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, - { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, - { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, - { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, - { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, - { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, - { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, - { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/a9/11851c3e02a3fea2ddc9932d1fdc7d2edaeecc0d2e11bc5f2a7fde2b0934/ast_serialize-0.8.0.tar.gz", hash = "sha256:6c37c43e4004dfb42d321ddedc569dc17ff4259296f3af577c9ea46a809bc010", size = 845638, upload-time = "2026-08-07T11:29:02.152Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/16/6e520b57cd8c75914b38c670ad4593d13c22911e4306cc7165dab8b0789b/ast_serialize-0.8.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3d822605fa7bb326ef868d25fafced7fc660fa46d9b90c02ea86d5e2f5d325f7", size = 863924, upload-time = "2026-08-07T11:27:34.579Z" }, + { url = "https://files.pythonhosted.org/packages/03/e1/48802de9b22a2bcad42ec80601a17e3f69172fe4f590e6311bcc2b323aeb/ast_serialize-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:2efa40b068197d5efb62655b43baadb842ed71c4958cccd3e8b86a35726f0119", size = 1177662, upload-time = "2026-08-07T11:27:36.196Z" }, + { url = "https://files.pythonhosted.org/packages/38/d4/323438db76bded3a1f3523a3167b8325916b2ddceb2107a330c6ec9fcf4d/ast_serialize-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:db1b957291bca08c7e72f43a12357b2948e20775d970e3fc3dac0aa3160ab725", size = 1167072, upload-time = "2026-08-07T11:27:37.646Z" }, + { url = "https://files.pythonhosted.org/packages/77/82/53c5400b54144b56de8ed7f957fd1ccd97e42482009292ab46121d15f8dd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdc0d5b18ff8fb364e87923e47c0a91d0d69dbcaeaa274591f7fd26892cc3a3a", size = 1225497, upload-time = "2026-08-07T11:27:39.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/5f/36c07327a8b91303fbf1382c7c3e8a2902072dbe1b9546138a5288e75ff0/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9da7330f3e235bf7da89b8d39205c6350fc0c08a85379743f2df9fff87d6d980", size = 1227101, upload-time = "2026-08-07T11:27:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/9d/48/5adf5c67addc7ddb328122208c6d375a84cf154984f412b4087330a157bd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3186969ee66a9863b00acc6523ace44c56974eecb348a7ea4b228d9f0b80e19", size = 1424001, upload-time = "2026-08-07T11:27:42.708Z" }, + { url = "https://files.pythonhosted.org/packages/38/a1/70074dd3869d2b0e934f91891d8d6b734361cd3b80f85ca7ece2e668ecdd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40a57b73731be45da4fa41430c4d5dc94a24b3a4faba7b9e069978c0402064ea", size = 1245545, upload-time = "2026-08-07T11:27:44.4Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/53b9c0a8a6399950c2e3546bdfab96d2b299d5b114b47eb94fd3c49c4054/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b9da3ef807eda752502446dfecea3b381c4900b7e27a5d5f4f899eb39951", size = 1248961, upload-time = "2026-08-07T11:27:45.781Z" }, + { url = "https://files.pythonhosted.org/packages/eb/13/3651d3812548a2bda15e26e5dd51aadb48cf682d0865370255fcf0e367dd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:293cc1c5bfa741f8e3fbe8175b9c07beee487c9a6fdbb25a5acad9f1df2d30a9", size = 1243877, upload-time = "2026-08-07T11:27:47.325Z" }, + { url = "https://files.pythonhosted.org/packages/21/a0/521f0bf000f675e9312a4aae2c8ba7a992405d072a85c485e08fd59433b9/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0910c3442a75216dde0f102d854ba2aaa71d2482e0ee213630b9bf29584fba3", size = 1293903, upload-time = "2026-08-07T11:27:49.264Z" }, + { url = "https://files.pythonhosted.org/packages/b1/7e/402fc902568aa2ee65865a3e151f000db0153da8ce6b1be4c9c349025f8d/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43dd6d596879bb1cb8a12cc9dae7bb10090a39a35883026c24f82488a195619a", size = 1401070, upload-time = "2026-08-07T11:27:50.947Z" }, + { url = "https://files.pythonhosted.org/packages/ff/7c/97d4b66c057f1706fc8be6dd532cc77c988794357c8f4ffdb6adabb39562/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8c9d537f59e936392cfd3597789d1390304dd659efc3c486ce7f40fb6b8a9f53", size = 1502602, upload-time = "2026-08-07T11:27:52.364Z" }, + { url = "https://files.pythonhosted.org/packages/89/6f/72cc3b71562001bba46e898ccfbf1844f7939b3e28912736206102f2e5a8/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f0190a33d7f97c65e9069f7a7f40499eea6b5cbe260c558378109caf20ce934b", size = 1495848, upload-time = "2026-08-07T11:27:53.803Z" }, + { url = "https://files.pythonhosted.org/packages/a0/53/d6f629d1e49308b2f363dae028baa213ec222c9106fa1f7f0d1f7b41499a/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:77308ae6c5cf5264cc0f01a7c556ec77a9e68eb1f61b093534d698139fdc3b14", size = 1556556, upload-time = "2026-08-07T11:27:55.342Z" }, + { url = "https://files.pythonhosted.org/packages/ee/22/340f35dd8dfc6d412d53dc20699ca014b8d228db923e8ed4759c512b162c/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8d53a23f27e1ed3a36b2d26fd2a1a6228c8e85a1ed62ff7cdb44bd610769f20a", size = 1417822, upload-time = "2026-08-07T11:27:56.712Z" }, + { url = "https://files.pythonhosted.org/packages/11/29/6dde5c13fbebc051d3a6df4ec0a6fd1d5359333cc1193f7f609f3410b4d8/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa5e7cb08f96fed9121f77b224151e41caf88feab9d652bb46c78202b6fbeda", size = 1445153, upload-time = "2026-08-07T11:27:58.275Z" }, + { url = "https://files.pythonhosted.org/packages/62/c5/f473a8ed030f7a0ca24b9849cca184677a50c053867a7b808c2e1289bbd3/ast_serialize-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:fa70ed4dea0bb18b30a1789c77baa701d0ef30c474f2ccabdea61e25623a8827", size = 1063711, upload-time = "2026-08-07T11:27:59.793Z" }, + { url = "https://files.pythonhosted.org/packages/23/63/39e171fcd38ca057c2e1979d5ee81ac7a3502784abe3d83df7454f7a0978/ast_serialize-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d8b3c8eee4c1baef9d4e84d2a59a805501617127be42615cb48970b15b0892b6", size = 1103740, upload-time = "2026-08-07T11:28:01.405Z" }, + { url = "https://files.pythonhosted.org/packages/21/1c/d00762b399e7726d68d0a088cc946e3a4c60f1c6176f557608f672f627f3/ast_serialize-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:ac4f0a83c55a9b782f79ad55a5247b7db123c1db405959791c2ef886e9710c9f", size = 1076021, upload-time = "2026-08-07T11:28:02.947Z" }, + { url = "https://files.pythonhosted.org/packages/4c/11/911210c3c78923273a9211a2b6cfc4c8aa723b30dab3e1c8d19afb983b40/ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:86b8a1e6d90467345356098b040150e82fbc26d24a7a202224b13dc1f6264ca0", size = 1177715, upload-time = "2026-08-07T11:28:04.654Z" }, + { url = "https://files.pythonhosted.org/packages/77/89/6282881c8587606638db153cbe21e1e0c4d1f3970dee1aa0610a1c62a026/ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:39e92ff8e8cb45947fe9007174b2950e1fb098e6abd00266a13cd3bcf6675068", size = 1169347, upload-time = "2026-08-07T11:28:06.1Z" }, + { url = "https://files.pythonhosted.org/packages/97/78/a9f846a03a340ff3728c915f23338ca742742f3292700559cdb3ad999b1e/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85d8d18db5b2dfcb3b7e38a4d600ca35504c0ed8a6f75cd1c811e4ffe248a15", size = 1225916, upload-time = "2026-08-07T11:28:07.654Z" }, + { url = "https://files.pythonhosted.org/packages/c0/15/aba6ef8a988a6eceb6f0359589aac509e29ae2dba67fd9bfd5af0c3f13e7/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9830ff7e764f74d9eefb01170c61a9f0fd2c027dac5fcb72e064decd57d56371", size = 1227135, upload-time = "2026-08-07T11:28:09.504Z" }, + { url = "https://files.pythonhosted.org/packages/94/29/3f63d696ea7c5b8abadcecc3505be51bd900daaccc522ed8322fa5b05a93/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6479d9722a4cd21b578f5478074c41e6169f04811996ec881655560f703a5bba", size = 1425040, upload-time = "2026-08-07T11:28:11.044Z" }, + { url = "https://files.pythonhosted.org/packages/e2/5d/0aac338604ff59df5774d4304307898982252f325ff7cafe31d52fedcb65/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a63bed264e818cd83eec11feed0f50aa162542b91132ef58afebc857182763a5", size = 1246278, upload-time = "2026-08-07T11:28:12.519Z" }, + { url = "https://files.pythonhosted.org/packages/23/ca/9f1ef795bb724719532bd86dbec11e5b66857d3fbe9b6772baec0191a6ed/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d187197d234aa45d6cfa2b096be5f666e8cc2e7eb3722d0ab8926293cf5720c", size = 1250029, upload-time = "2026-08-07T11:28:13.896Z" }, + { url = "https://files.pythonhosted.org/packages/dc/25/5e061372d2ed953b9ba3b9c4f73de3b8e9234cda3f6c088db4686801d0e1/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:2d39a56282cfcc0d8eeea37267c754be59c98d48505c23b1dae5c6011f3813dd", size = 1243575, upload-time = "2026-08-07T11:28:15.37Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c1/ae7da218053120635a4ca802366c69f707203641af95372eeb83f70dfd52/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7cc5f10386994c0f4844f1e6d6a97127e9b478660eb6dec2b257644f0acab64", size = 1294396, upload-time = "2026-08-07T11:28:16.813Z" }, + { url = "https://files.pythonhosted.org/packages/2e/89/271d1f49c5269fcddcc789ea3f25be401f6723fc1138aeda539f4d05516d/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6102f2f985c2e542be85cd857678ec9356fefa792b93cadfadd31139f5696f27", size = 1401987, upload-time = "2026-08-07T11:28:18.333Z" }, + { url = "https://files.pythonhosted.org/packages/55/be/4e7d77fcf571ac7cb5cf7115a20c36642bd7d29473b45dfaaefeb9618f90/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:3a8660fe66667b76a6e9dccd1d33e66b229fde3b308db991c041609226c005b6", size = 1502904, upload-time = "2026-08-07T11:28:20.039Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ae/ed1de2db7e019d4236fbc164ffa5ef9a6022a300a342bbf142d21b7c141e/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:e7266307e5fba39836edb79def8608887af48820508bff3c5f2941e1e04d1534", size = 1496967, upload-time = "2026-08-07T11:28:21.734Z" }, + { url = "https://files.pythonhosted.org/packages/92/89/5fea507fae5c5f18b7dc7f95e5c00956574b8c717b8fd2049c504fab0b18/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca7e6fd1ad845d1cc649dc2ecd499db2f8f46af5bf8da7b70dd858774cc038b", size = 1559041, upload-time = "2026-08-07T11:28:23.194Z" }, + { url = "https://files.pythonhosted.org/packages/42/71/478d69df21b64e064554a68134c94be304270316ca676a94e63c389a636a/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:2880350b13d3eae69a0d70bc1fb6c9bfaca4dbd0e20ba8cd1aa483080b56ff06", size = 1417367, upload-time = "2026-08-07T11:28:24.601Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2d/8962dc8d5b3a9dc27b36f9db199afa25264c741505469d9ec10ffbfd2ba7/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:ab0f9a59f7d63d0d441b56b9a818b273705264352d5115cfee12e940e816d958", size = 1446178, upload-time = "2026-08-07T11:28:26.152Z" }, + { url = "https://files.pythonhosted.org/packages/4f/22/14d2ad4fd1d1bcd0dc687ca268e0630069f45162496260c0efb70ee0ea72/ast_serialize-0.8.0-cp315-abi3.abi3t-win32.whl", hash = "sha256:0485a25ef519c62e749ee3c1ad8070e591b380d67226349eb5a70b228dc1ac4a", size = 1063811, upload-time = "2026-08-07T11:28:27.864Z" }, + { url = "https://files.pythonhosted.org/packages/18/1d/84a327c0202a41aa5fdba3ade33904d6d8f3b9e6806fa83568d835395850/ast_serialize-0.8.0-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:bd84d60bca7079e741be4ac5dbe237751a59d7f6f9f0126b11880d63822cbe16", size = 1105518, upload-time = "2026-08-07T11:28:29.691Z" }, + { url = "https://files.pythonhosted.org/packages/8c/92/74556dec52fde85a2ad84ed159991b916241043788609c15d8b77e14570b/ast_serialize-0.8.0-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:057769b5921336eb2d9124f2a731b42ed05ffdac559b840dbdf6f3937cf153dc", size = 1076319, upload-time = "2026-08-07T11:28:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e3/6142e920fec6ef7bccabd8c24ed8ed99f8bdc6cb8b065e1df7c6a3b2d667/ast_serialize-0.8.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e1bd223df0f6c96b396975fa604cb33bce53d9b4a0185490be4c4a289f7c9c87", size = 1184007, upload-time = "2026-08-07T11:28:34.654Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e9/6e8be8df02b35d85e2b8809f7f1cfa290bdf5882b55127a539d049482db0/ast_serialize-0.8.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ddd3b61f45c132da66c5476b281891e08c1fd87fbdabe8a6973e1622efc85f06", size = 1177588, upload-time = "2026-08-07T11:28:36.318Z" }, + { url = "https://files.pythonhosted.org/packages/8c/80/7e0fd2e2e2aba257820db4a8657c4c356844d36b914b20a4af294bcfb902/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f9caa63fad8241257ae401b5ff0a64026c6adb36b8e86cbe8782d9ea505daf6", size = 1234575, upload-time = "2026-08-07T11:28:37.772Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/3bae0af06f9b1bae3001c44d64215f5b567877e7aae9ffd45db11c3a7647/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3926fa117b5e65019853a2969966d11c7175af377a3425991f3fe73784412405", size = 1236015, upload-time = "2026-08-07T11:28:39.14Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c4/ce2d41a1bc22508e82618901f7e10f2a5e2f9556553fea90624daf9875e2/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:485f1113af805e9e170b95ef993ca3fbd4f89c04bab25c58b4fc632d854801ab", size = 1432808, upload-time = "2026-08-07T11:28:40.664Z" }, + { url = "https://files.pythonhosted.org/packages/1a/90/f5058f209756dd70e958b7538aaa82d25d24944baf9ec8ae6f27b06fcacc/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccebbed24f1281062d5852353c72c47502955926cfcb8345ffb3a44d87ff3d3", size = 1256251, upload-time = "2026-08-07T11:28:42.223Z" }, + { url = "https://files.pythonhosted.org/packages/bf/32/7f77ea87fa0836daab706ed5cb7f903bb25fa26a77439011aee626af11d8/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:252f883290d1cdb728eb7fe1d9a7221b88af5a329aae0bc91ddee4dafb820331", size = 1258574, upload-time = "2026-08-07T11:28:43.751Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5a/75b82ad2725b5e8e8c742732f9e76c6738a292d0709e1f60d10a973730b4/ast_serialize-0.8.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:96abc072ad29db8d02194afd47d68987322622787daceae82398d7b69f3ba2e6", size = 1254075, upload-time = "2026-08-07T11:28:45.28Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/8c20ed4eea805516a3fd23dd4a721ce28c64f50f0e4b359969f60a8c97a6/ast_serialize-0.8.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9118ad3e369727060b2696fc4078f250ecffca4248ba87f537f55cea9f9dce06", size = 1301018, upload-time = "2026-08-07T11:28:46.851Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5b/9f14430f12fe830b656fb38f8e2e05ee13b02a88967660bef46af0ab22a8/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f359df4bd921918af8bebd142a376c77511d7151cc8ba852760b587b5a4a54f3", size = 1409951, upload-time = "2026-08-07T11:28:48.312Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3d/084882eca93c842bd4262591a071ec7f825340644035e51501208cc5a8d4/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:e94f9121d13fa36cbf21314783c77d05ae3a0868decd18cf5233fdcc6de49ac8", size = 1509544, upload-time = "2026-08-07T11:28:49.847Z" }, + { url = "https://files.pythonhosted.org/packages/ce/73/ea84852096c2036c61cc0b2f97b90242207419f534dc671060ee1c8e05cb/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:54f95b486018d262bcb387a9afd96f0da74508b442762b80c769454a6fbb3ee3", size = 1505671, upload-time = "2026-08-07T11:28:51.239Z" }, + { url = "https://files.pythonhosted.org/packages/cb/88/287b9a5300c1f2f651d259f670931b63110adc265b7613c885b44c5bc53d/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c38b915511e32bc718c49dbce98ff9af36bac0ad6a604f58000cd5e3aecdba7", size = 1563685, upload-time = "2026-08-07T11:28:53.112Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f3/1bc3a79afcf0c2a8d2c37182d0d659d1545a9d7f7f6dc9cf3e63d6c17135/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:9a2ef9cf12f2de4f1028c42c1dd7d775255e0fb3e5bb48896c97e35ef52366fe", size = 1427977, upload-time = "2026-08-07T11:28:54.418Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cd/440c798957e14e31776bfeb024d8fafe0bb1d5b89c51c2f067e69938f7b0/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f18048fe9f6dd266bd577cdec48bdcecb74faaa01fe941324435483b013ed2a", size = 1454335, upload-time = "2026-08-07T11:28:55.968Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4a/587eb36dcc240a54c8660f599464516b469ecad96f0dbdb6bccbedb50745/ast_serialize-0.8.0-cp39-abi3-win32.whl", hash = "sha256:31883542dd6c94d178f5db3d32fbd69c5eb88b3a7c018e7ac8cc0c45195ddbed", size = 1068858, upload-time = "2026-08-07T11:28:57.541Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a4/3e887bbd92164e183cb6e412c6a3e9198ddd446d7fe405958293ef5ef49c/ast_serialize-0.8.0-cp39-abi3-win_amd64.whl", hash = "sha256:861794565b06337005c1447ef23103a3d5a627d08bdc827870d00d0b28ef5f51", size = 1111839, upload-time = "2026-08-07T11:28:59Z" }, + { url = "https://files.pythonhosted.org/packages/25/6c/b400476d3ceba681ab929787edc9554f6d88fcc69435eb681b00fc0457a5/ast_serialize-0.8.0-cp39-abi3-win_arm64.whl", hash = "sha256:b2a5978662fd4db463dfb4b974d2b10ac6430b98f5333aabc7051909df3561d0", size = 1083655, upload-time = "2026-08-07T11:29:00.349Z" }, ] [[package]] @@ -95,11 +118,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.6.17" +version = "2026.7.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] [[package]] @@ -113,103 +136,126 @@ wheels = [ [[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/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" }, +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585, upload-time = "2026-08-15T08:16:46.646Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189, upload-time = "2026-08-15T08:16:48.287Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724, upload-time = "2026-08-15T08:16:49.67Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078, upload-time = "2026-08-15T08:16:51.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650, upload-time = "2026-08-15T08:16:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325, upload-time = "2026-08-15T08:16:54.085Z" }, + { url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140, upload-time = "2026-08-15T08:16:55.604Z" }, + { url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791, upload-time = "2026-08-15T08:16:57.103Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730, upload-time = "2026-08-15T08:16:58.418Z" }, + { url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791, upload-time = "2026-08-15T08:16:59.918Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598, upload-time = "2026-08-15T08:17:01.421Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217, upload-time = "2026-08-15T08:17:02.782Z" }, + { url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417, upload-time = "2026-08-15T08:17:04.216Z" }, + { url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774, upload-time = "2026-08-15T08:17:05.58Z" }, + { url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653, upload-time = "2026-08-15T08:17:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630, upload-time = "2026-08-15T08:17:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, + { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, + { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, + { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, + { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, + { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, + { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318, upload-time = "2026-08-15T08:18:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897, upload-time = "2026-08-15T08:18:05.997Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848, upload-time = "2026-08-15T08:18:07.397Z" }, + { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163, upload-time = "2026-08-15T08:18:08.989Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823, upload-time = "2026-08-15T08:18:10.458Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458, upload-time = "2026-08-15T08:18:11.989Z" }, + { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717, upload-time = "2026-08-15T08:18:13.401Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111, upload-time = "2026-08-15T08:18:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128, upload-time = "2026-08-15T08:18:16.515Z" }, + { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240, upload-time = "2026-08-15T08:18:18.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282, upload-time = "2026-08-15T08:18:19.625Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597, upload-time = "2026-08-15T08:18:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376, upload-time = "2026-08-15T08:18:22.57Z" }, + { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715, upload-time = "2026-08-15T08:18:24.235Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848, upload-time = "2026-08-15T08:18:25.726Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521, upload-time = "2026-08-15T08:18:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054, upload-time = "2026-08-15T08:18:28.568Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580, upload-time = "2026-08-15T08:18:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325, upload-time = "2026-08-15T08:18:31.877Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175, upload-time = "2026-08-15T08:18:33.466Z" }, + { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123, upload-time = "2026-08-15T08:18:34.913Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682, upload-time = "2026-08-15T08:18:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826, upload-time = "2026-08-15T08:18:37.887Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861, upload-time = "2026-08-15T08:18:39.315Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758, upload-time = "2026-08-15T08:18:40.88Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950, upload-time = "2026-08-15T08:18:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329, upload-time = "2026-08-15T08:18:43.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137, upload-time = "2026-08-15T08:18:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820, upload-time = "2026-08-15T08:18:46.842Z" }, + { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504, upload-time = "2026-08-15T08:18:48.353Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087, upload-time = "2026-08-15T08:18:49.859Z" }, + { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269, upload-time = "2026-08-15T08:18:51.601Z" }, + { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766, upload-time = "2026-08-15T08:18:53.23Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814, upload-time = "2026-08-15T08:18:54.768Z" }, + { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074, upload-time = "2026-08-15T08:18:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476, upload-time = "2026-08-15T08:18:57.889Z" }, + { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115, upload-time = "2026-08-15T08:18:59.418Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, ] [[package]] name = "click" -version = "8.4.1" +version = "8.5.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, ] [[package]] @@ -223,101 +269,86 @@ wheels = [ [[package]] name = "coverage" -version = "7.14.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/d7/477ad149490e6cb849f28abea1dabb9c823cea72e7500c81b4240ce619c0/coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f", size = 219848, upload-time = "2026-05-26T20:38:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/91/82/a5eb47257c50601bb7b9a9d2857c67b7a3a85ad74180eb2c98bb1fbe0ce5/coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4", size = 220354, upload-time = "2026-05-26T20:38:40.232Z" }, - { url = "https://files.pythonhosted.org/packages/43/8b/78419b5391a5cb706b6544390507e469d83ffc9a8248b02c4011aceb9365/coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1", size = 250771, upload-time = "2026-05-26T20:38:41.782Z" }, - { url = "https://files.pythonhosted.org/packages/77/63/e77aaacd491182210d639636b7a8bba23ffffa9b82aa3762da9431855fa9/coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f", size = 252683, upload-time = "2026-05-26T20:38:43.305Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/a022e3cfbec2ac241640003cb3a817e161d9c7f5aa9b49173756cdc03204/coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129", size = 254791, upload-time = "2026-05-26T20:38:45.361Z" }, - { url = "https://files.pythonhosted.org/packages/61/d6/967e408aca4c1ceb88cb0cc677169110ae7f5995fb5eaf5fb1f5a1bb8f5d/coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860", size = 256748, upload-time = "2026-05-26T20:38:46.91Z" }, - { url = "https://files.pythonhosted.org/packages/b8/be/869188f7fe28638078ec479331ace6dc5f7b40b7153eb616f47ab79404d8/coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c", size = 250907, upload-time = "2026-05-26T20:38:48.493Z" }, - { url = "https://files.pythonhosted.org/packages/07/aa/adb7d3b4278d690e68703abcd76ab1b948242e3668d921711551b78f9ddb/coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7", size = 252483, upload-time = "2026-05-26T20:38:50.074Z" }, - { url = "https://files.pythonhosted.org/packages/43/61/331c74103c62dcb0c4b9b3a0de9a61aca016208b0a90f109592a9f9ecc28/coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec", size = 250545, upload-time = "2026-05-26T20:38:51.613Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b6/c5dae3c104d89be04828f61810e6b3473825482e4c288cc4ed04553e08ae/coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef", size = 254310, upload-time = "2026-05-26T20:38:53.503Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a1/2b9d5863e3b83c01ad8199e3c597802fbb3a9dc90b058885804c20296d31/coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df", size = 250266, upload-time = "2026-05-26T20:38:55.414Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5e/0e511fbdb269359be26fe678a1c3fa1f2aa2a01573cc3f54268c8d6d4797/coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9", size = 251174, upload-time = "2026-05-26T20:38:57.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/10/e55307b622b3dd9671cb321824502dc10f93e72f2802b9946159a8edadeb/coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548", size = 222354, upload-time = "2026-05-26T20:38:58.727Z" }, - { url = "https://files.pythonhosted.org/packages/71/cf/107421693cfb71e4f1ca5bf70443f64d4161878068d07a3e51c7ad21d17b/coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e", size = 223290, upload-time = "2026-05-26T20:39:00.413Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1d/3e3644585eb29e9dafefb19555078529a4d7cce12bd21929664eea989277/coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3", size = 221953, upload-time = "2026-05-26T20:39:02.159Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, - { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, - { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, - { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, - { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, - { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, - { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, - { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, - { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, - { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, - { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, - { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, - { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, - { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, - { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, - { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, - { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, - { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, - { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, - { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, - { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, - { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, - { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, - { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, - { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, - { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, - { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, - { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, - { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, - { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, - { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, - { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, - { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, - { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, - { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, - { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, - { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, - { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, - { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, - { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, - { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, - { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, - { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, +version = "7.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952, upload-time = "2026-08-06T13:50:24.442Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/66/edcec7d7a0b524aa8923e22925fde6fe50ce005a113dca13ae1581455c4c/coverage-7.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbac5abad70df71019988f83f26ac7092ff2642975def4429e98dc7585ef3490", size = 222367, upload-time = "2026-08-06T13:47:15.578Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c6/ab8de429e2e8548faf58ec7e1674a4ce00414b4113942d3fe87109cf0f68/coverage-7.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:357a173465c7ce028d07a95cc2b63b5bf59f50ecdd5ad75c5cbb78ada984048e", size = 222874, upload-time = "2026-08-06T13:47:16.961Z" }, + { url = "https://files.pythonhosted.org/packages/be/c4/3b7b49587e8a6b9af79b3eb468d443d6042b6d65b47aa26586846a0d6566/coverage-7.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21b803935e2efc3acebe9697197a294fccf5dc4e5382bd6369542ff7a7d2a1d7", size = 253287, upload-time = "2026-08-06T13:47:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/fb/65/ec03b743a2a229c72cc1eff3e57be9d3564e9c6b4d5aba2d70744a3fc0d8/coverage-7.15.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a2b580774a4786c1053157c0165e04476e03ff293993d7c148eee784a94bae6", size = 255199, upload-time = "2026-08-06T13:47:19.765Z" }, + { url = "https://files.pythonhosted.org/packages/41/4b/5163729e4b6582d61975cfd3ccab45b4ec53e21cf156d9941cb025188468/coverage-7.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9464451c4efffe8d47ace5a540b10b0dc10e879066290f8600872b7f54a419d", size = 257308, upload-time = "2026-08-06T13:47:21.206Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/2167a0f08fb87d702fa423a48578a32865464b7c9e1db3911ad7812ab414/coverage-7.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de602f34123c2f4af1c1869c6dbbbd60da6d5983bf01937367295d135cccbfce", size = 259268, upload-time = "2026-08-06T13:47:22.503Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e5/68eebae3053dbd48508edea559c21b23fbdf3460784f91370c83a86a6acd/coverage-7.15.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6879ded16a27f3eeca19b900c147e81616e7054db451471a611b2755ee5249f7", size = 253392, upload-time = "2026-08-06T13:47:23.88Z" }, + { url = "https://files.pythonhosted.org/packages/1a/46/fd4ced40a2b691c774e515c9b69500bfa64c7960b67fcee4b2f6fad97fc3/coverage-7.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:986be58c3ab54aae8d3496a6225eea74f760fdbe739b38bd442c7e8d133aa53b", size = 255001, upload-time = "2026-08-06T13:47:25.469Z" }, + { url = "https://files.pythonhosted.org/packages/53/25/ae2e5fa710bb6957a9aadeb9e3598d3b3e4af6587ce857ad42e8639a3f30/coverage-7.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6103639613fe6c1e989082948419bc77a2d26b6c825c99d7fad25f7d3d87afc", size = 253061, upload-time = "2026-08-06T13:47:26.845Z" }, + { url = "https://files.pythonhosted.org/packages/d7/31/67ddc0365db2c6e93ac8580bc4bbc50f65273262f973f63ebcdbc15c0495/coverage-7.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3af93dddb5659276c63bc16ac6466ac2033a70ca816097bbc06345b8ccdf571", size = 256831, upload-time = "2026-08-06T13:47:28.217Z" }, + { url = "https://files.pythonhosted.org/packages/f6/78/82b8fd18f57fb13f12d98fe874995bb2c4f9f17be8aff762c426323fdb96/coverage-7.15.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b10075e5421d04265766a6d1dac809bbeb8a946fbb23c8f82c227409b2190719", size = 252781, upload-time = "2026-08-06T13:47:29.712Z" }, + { url = "https://files.pythonhosted.org/packages/0a/eb/6c74ef4dd12b252e573c49bdef9e2ac265bf3dbb79b8d7feb3266e084e9e/coverage-7.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a67a9f78b2942d87ba8ce3059c642164d2aedd65337377fb52fe9803656bc5c7", size = 253692, upload-time = "2026-08-06T13:47:31.192Z" }, + { url = "https://files.pythonhosted.org/packages/5a/66/eb9aed1c3fd2d36ee00eb173f434b14fa607fc056739c9a89ff4244010ea/coverage-7.15.4-cp311-cp311-win32.whl", hash = "sha256:69484d1aca26e322e1c3ce03f09341e84524ababad2d7202161738d83cc9f82e", size = 224461, upload-time = "2026-08-06T13:47:32.572Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6d/81fa4161dfb3ed9d74e40d58647eff83a56b7612e78352581280fce2f477/coverage-7.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:63fd6fcd1dd6e158f7eb78606e72933b3f6d01e7b747f99c6c12d764307a0fdc", size = 224937, upload-time = "2026-08-06T13:47:34.205Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c1/d8dacf683c6cad3cf85ce68fd3774a6774ec402128822fdfaed920f11e6a/coverage-7.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:ea82116c9893fa89e929b7f197ee5a1950a76e91cc5c85ba503fc02379d04890", size = 224479, upload-time = "2026-08-06T13:47:36.118Z" }, + { url = "https://files.pythonhosted.org/packages/1d/48/bc8d4ba7b37551a767bd863f15b3f80182b271c2f55975356f5f7dbe94c2/coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22", size = 222543, upload-time = "2026-08-06T13:47:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/20/dd/88d6f83f1fffc974a3691a34a97951c5b12df7512a6782c5963883cbc058/coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97", size = 222905, upload-time = "2026-08-06T13:47:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/bd/5c/54ee0d4748585bb0acab9891cd8d92f2d3593165b4e59fc9de113bfb3140/coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d", size = 254407, upload-time = "2026-08-06T13:47:40.488Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3f/f0642a372f494bd0d7dad3b497083b910194a5f1c88be2c94fef707c3b59/coverage-7.15.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:899b9da30f3c6c336566e3707495bb23e8302d39d862f01fa78c48b99b9437e2", size = 257145, upload-time = "2026-08-06T13:47:41.931Z" }, + { url = "https://files.pythonhosted.org/packages/71/17/8b46d0ed68251016002ec972c8fc0119961a765d0984cafb8bf317c43758/coverage-7.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d15715e8c46552827e5e4f30a35575a2dbcad14454cf3284c54483946bd16931", size = 258257, upload-time = "2026-08-06T13:47:43.527Z" }, + { url = "https://files.pythonhosted.org/packages/30/b8/8498a0e72d0adbe15477dd07463d2b3bb2c9f6a4815e8589e50939e2c3ae/coverage-7.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:002a438859f7b430bc99afeaf01a6d187dad1d0dc907b64cdeffc632a5db8fd8", size = 260517, upload-time = "2026-08-06T13:47:45.121Z" }, + { url = "https://files.pythonhosted.org/packages/41/e1/7dce19c3bdb1e3dd63e769508216500edad81bd5f69a26d724e32aceaf78/coverage-7.15.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4193a04b518f7968f3099755f5509ee7cccc6dc2b92a6b14841934d22e222c9", size = 254785, upload-time = "2026-08-06T13:47:46.541Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b1/e1494703c675a2561723cd9b89f45c9168782c31280c611b1f767851e57c/coverage-7.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e98dcc55d572b38e69d117da7e8e8efb8500f1f5eaf81ecd460a63220790b839", size = 256176, upload-time = "2026-08-06T13:47:48.155Z" }, + { url = "https://files.pythonhosted.org/packages/73/76/a5629d270fb638a43a4b10466f51e2f49d532c1aa4da2913cbbb150bbe0a/coverage-7.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:af6c538498ce66c10d3fd541c2a8d5b03da5850355add34e6cba564210cb9e72", size = 254321, upload-time = "2026-08-06T13:47:49.757Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/9c44447218435d5766b911534f9d798144a5560f85e9a54ebe5f3f5d19f9/coverage-7.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1d10025d96ea89fc2f73714dbc4cbd433fe012c1ac9e23f895d7728b238b6e52", size = 258390, upload-time = "2026-08-06T13:47:51.248Z" }, + { url = "https://files.pythonhosted.org/packages/de/36/c1e127616fb3fa18a9ff71e76c417f2fd7424332a4870015ac224ef4c039/coverage-7.15.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d802e1947603162ded419bff83ac7489820355d2b856dfb09206574e3a37ac0c", size = 253894, upload-time = "2026-08-06T13:47:52.816Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b9/fdb92c8ae7a8bb9b850cc253b7b3b9c8526f68130002048b5671cd510d09/coverage-7.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2de40895718f91951b86712b4c5b694acaf9a0a49be13874896f599a1eed3f4", size = 255763, upload-time = "2026-08-06T13:47:54.296Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/a7d51b2587c7bdb76e71b0896d2565bf7d60436b5122fc83e511adb1f7cd/coverage-7.15.4-cp312-cp312-win32.whl", hash = "sha256:5c3431b2161279b7db5c2a1aa58ae02e5cb8c3c42d93a5094be3f5537bd5b11b", size = 224597, upload-time = "2026-08-06T13:47:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/49/b9/5c5f80cc55f5acaaca6dee677626bfcec8c87204a7809b438b08e84f4571/coverage-7.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6befeab5fb2b51c958ca4ac6c5d141a1e8240f4f76e46350f1911963deda49cd", size = 225135, upload-time = "2026-08-06T13:47:57.52Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/2a4561f89ff6bf7c925c287d0f2cce8bdf139c3a33735c87e3203401cf94/coverage-7.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:67bc345491ab55b837277d76f5775d057e8c7f1ac44d890d8c2c82adde258c6f", size = 224515, upload-time = "2026-08-06T13:47:58.977Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/651a9310859673aaa3b3203f1aa1641ca60fcf2494683e1c9474c7172780/coverage-7.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c705b28feb2775dc82a25f1d473a370bc37ff93f5177f4e29ce2425f560f6921", size = 222565, upload-time = "2026-08-06T13:48:00.796Z" }, + { url = "https://files.pythonhosted.org/packages/82/f9/4dcf700137e8af550670f4d74d1b63828ce93e1e2b05e5f10710eb2ea987/coverage-7.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ff205ab5e3ecc670f6a4dd19d9cbf12ede53dd41cfc1e15716ec961ea6d314e", size = 222936, upload-time = "2026-08-06T13:48:02.391Z" }, + { url = "https://files.pythonhosted.org/packages/07/4a/612ff1e780b3fbfd637486f542f84adc5503873d8b5d279dec1ffeef9414/coverage-7.15.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5172326e861a38b48b48befca15e0f477a26b283337a33a739c8fed229934e36", size = 253926, upload-time = "2026-08-06T13:48:04.382Z" }, + { url = "https://files.pythonhosted.org/packages/b0/04/d1cff1c2ead4708a6a79c01d3736b6a25bd38a36678398f72a8dd33dfad9/coverage-7.15.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12b59c90084e3234fb11184886bf4a40f4f16a8c8f867be2e087b81f8e8868d4", size = 256523, upload-time = "2026-08-06T13:48:05.996Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/d34e13fb4b293cbdb9665838cf5522077b8ad14ef947550631a4bced36a5/coverage-7.15.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349062d66f00b40fa2c1c222438bad25fabf755631b5d82937fe985c8008615c", size = 257759, upload-time = "2026-08-06T13:48:08.036Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e7/2c5fe7636fdb0732fe0f09f308a5b066864078b7fc61f6678e8478554f2e/coverage-7.15.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4256ced708e598e05209bc1a8ab4074e04a51dba4c62fb45926a229af675ace7", size = 259890, upload-time = "2026-08-06T13:48:09.834Z" }, + { url = "https://files.pythonhosted.org/packages/92/28/9689f0858dfff59c2ea688938ab9fa2925631235df67126a42b6c5c70ae1/coverage-7.15.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d80f974b20782d9612c8b4c9beeca867074c7cf4079d1419843fa25a26428b25", size = 254121, upload-time = "2026-08-06T13:48:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/785077c230c157243eb5aa9a26c3be260ecd02001bead54a3cada3df8e03/coverage-7.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e179f19bfe1d31f8eeeaa12990194d761c4f62f0759661000bca6cd8729f40b", size = 255891, upload-time = "2026-08-06T13:48:13.209Z" }, + { url = "https://files.pythonhosted.org/packages/d4/90/e20371b17b40f912f21305c2db2f30efa3de306f7320fc916804872c85a4/coverage-7.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8bc16bb47b7679670eceff71d78bfb7d6e5b143f6c2cd117487ec7c75e0d4b78", size = 253859, upload-time = "2026-08-06T13:48:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/05/49/25371987ee459a5f67c0427fb75c74f9358e65f2c71fe75bf41c1b6c5fcb/coverage-7.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd685005cd2c4200adfc14cf39a603b9320efab3f18a8f7f156d20c9cc3345f", size = 258011, upload-time = "2026-08-06T13:48:16.464Z" }, + { url = "https://files.pythonhosted.org/packages/30/6e/32e67467f6154bf4f1c4f63b05acc5097cba4237d45bbeeea446b52e8ac1/coverage-7.15.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:337399ad2c93b3acd2a937627dae8b3e86b66707cd3d3e856347999aadf1ef8d", size = 253676, upload-time = "2026-08-06T13:48:18.493Z" }, + { url = "https://files.pythonhosted.org/packages/03/c1/8b24192e89286399765155251f99ee9f070a9d637109018ac23d99b99f6f/coverage-7.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:96e257121228ec5cd2bb919276e94ac11074471bc37d68dbae0e8308cce15fff", size = 255453, upload-time = "2026-08-06T13:48:20.057Z" }, + { url = "https://files.pythonhosted.org/packages/16/6f/8b41ebdf67c87854e17c035336a90f1cfbad0c14c2a584301be6ff148718/coverage-7.15.4-cp313-cp313-win32.whl", hash = "sha256:c65a9e0dfc6143491879da4e13b5e30f8be192055de508d737fb14601edbd22c", size = 224605, upload-time = "2026-08-06T13:48:21.655Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e2/2946c7f0b42b152ecb21ff1bdad72e3d301e790c0c487e4a86e8c9f69347/coverage-7.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:2ff8f5e9b8f7a94f0c11c45631eee103dbcb7d63274edd12c56efe1be690b3b4", size = 225148, upload-time = "2026-08-06T13:48:23.376Z" }, + { url = "https://files.pythonhosted.org/packages/9e/83/3f4a69957f48ae7a0aba76c34743f88963d607b19e03f3f8e66f91cae0f9/coverage-7.15.4-cp313-cp313-win_arm64.whl", hash = "sha256:6e0a8a5083b096487d6cfced94cdd514d8f5db6f113610fb36c0620edb1028cf", size = 224536, upload-time = "2026-08-06T13:48:25.117Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ac/748cf29eeb2d6be34a3176ce26a4f49e38085ee08e8935f05f6f26ed7e0f/coverage-7.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:770e9325ab5ea6d56f77e59b29ecfe0ac20b57a82a601876f90494a4dda0386f", size = 222608, upload-time = "2026-08-06T13:48:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/0b/02/1abbf5c984677b0aa439cdacaccbf38d248939d8ef8fe1cc7a50d73edb77/coverage-7.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d12b33a3a50a1676b7784dc8d00a0c6d66a9f2add4b85a041c19b6a7e53ef23c", size = 222940, upload-time = "2026-08-06T13:48:28.432Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e1/ff8f9f53d9fcf586125b55d0b1f04ec1c14955fee41e83d5814bee141bb5/coverage-7.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5669c8378ebde86f5def7a25d29586631b58acc27ffde04399f678f3dfc6e082", size = 253985, upload-time = "2026-08-06T13:48:29.995Z" }, + { url = "https://files.pythonhosted.org/packages/a1/26/595759762e514e81be1d7d01ed03444303bcd152226a6529998d253f9201/coverage-7.15.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff97a14362eef486483ed44042ca2027ea257df6ff768e62358ee0c9776925ac", size = 256492, upload-time = "2026-08-06T13:48:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/b79aabac54d482be23b5fcdd4f4662bff24a78edc4ee29201726929936d5/coverage-7.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a325e815318638aed1655d9c06e6d7c2d3d46c09231ce988070428a8762d734", size = 257837, upload-time = "2026-08-06T13:48:33.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/0f/bf7f297885a5bf6fd71e5782404e0ff059ca09e8711ceb3a08544abde45a/coverage-7.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:474223409d88eb20d2d6a0d37ea60e8647a65a90cc008dc1f0410af5f64f1e0d", size = 260152, upload-time = "2026-08-06T13:48:34.75Z" }, + { url = "https://files.pythonhosted.org/packages/fd/f1/296744e854ff8368542343457414380465e9ceefb9192342feb9d3bc461d/coverage-7.15.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f2f62ae3cd189dd2e13aece758c57b3eecbd27be070dbd4cbd10936049e5dbf", size = 253978, upload-time = "2026-08-06T13:48:36.434Z" }, + { url = "https://files.pythonhosted.org/packages/55/b0/bbdb2e9057493e66220a2e149ca2d301ba0e3a58a83bd6b90de9826d16f3/coverage-7.15.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39ece820e29e0a2ba34b3ecb3be83c27e997eed8926f2ba6fe7ce7a0bda5843b", size = 255846, upload-time = "2026-08-06T13:48:38.317Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/38015b2b6d21258713bd17e76b59d033b191efb5703589cffd037dfbca20/coverage-7.15.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f21b56dcace11dfe013014201f577dcd592b2a9b72182d930361b47cf6f73f25", size = 253808, upload-time = "2026-08-06T13:48:39.993Z" }, + { url = "https://files.pythonhosted.org/packages/0b/64/0d515c1e60ee6fbfd1a0e79c07cd87d388a233b7adc37758735677203808/coverage-7.15.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:93a3a0b662abcc10c73a47cbc72cd60f63618d6989fb2d1286e50eacd974f303", size = 258081, upload-time = "2026-08-06T13:48:41.971Z" }, + { url = "https://files.pythonhosted.org/packages/91/71/04d9e7a3642146c6351338aef4ef85ab11dbbb54744c13245caba1aad1c0/coverage-7.15.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:141fae2cabf5569b782c10afc4c850ce10f618c13f8db54765cba99cc839da1f", size = 253624, upload-time = "2026-08-06T13:48:43.731Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a7/6c28b74c81ebff66987b0e2522ba5cffa3e90b0c33cb6a2eb264d4ee8cf1/coverage-7.15.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:81294c7e6ab30c5f74c0353b11b2fd6320e72d9bee6ac73b357caa8b916323a5", size = 255280, upload-time = "2026-08-06T13:48:45.58Z" }, + { url = "https://files.pythonhosted.org/packages/52/af/bc19996a7014b98d7bbb0f0939453c67074af65784a3aa16a789a07381fa/coverage-7.15.4-cp314-cp314-win32.whl", hash = "sha256:7bbd7d6418e0dab31a206af5203bd43ae36edb8e7fba1940b055d3e9249290d7", size = 224768, upload-time = "2026-08-06T13:48:47.525Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/219484e476d6e101ba0a444852579e05f5b75c37c611a42ed1190f73ef62/coverage-7.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:f0204ed122758782970526057093f448051a39db9d810d4e344bb87a3546f425", size = 225259, upload-time = "2026-08-06T13:48:49.513Z" }, + { url = "https://files.pythonhosted.org/packages/b7/66/fa77daf4e383e5f776dac62c2409b6af81910ae6fe326bd5170dba74cc63/coverage-7.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:9e71e7bc71c686a123347ae47a0de33a175e797a85bb57b791492adf4eec8ed8", size = 224684, upload-time = "2026-08-06T13:48:51.235Z" }, + { url = "https://files.pythonhosted.org/packages/58/5b/f03bf0ce362bbf3f785fa5219620d00778d4ac6fc9e407734828e9c672f6/coverage-7.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7c922735321eef3f87c280a3d39afff6b646723a2880b862cda4ac7a093b8aa8", size = 223338, upload-time = "2026-08-06T13:48:52.896Z" }, + { url = "https://files.pythonhosted.org/packages/0f/76/e77d0ae22501831cc9f92193e8a957a5caa1dd177f90a6d1d9b106242d92/coverage-7.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f41c17c4668a655ce96d090d8d5ffdc24ef64b5a02f9753884d08483e8a4a41a", size = 223609, upload-time = "2026-08-06T13:48:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/82/1a/b1f089da8d38ac612fa2dd6dc7f4a1a7657d12f3e261d2996edd3a838d0b/coverage-7.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46822e9b6ff1c6a72b518c162c44a8f45a61a1d609c51084bf5b16c023c5037b", size = 264970, upload-time = "2026-08-06T13:48:56.403Z" }, + { url = "https://files.pythonhosted.org/packages/bf/31/e66d98d6e9c7fcc88470f1e234eaf6b1950dc0dfbf797f7282c1c861da24/coverage-7.15.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d6f4955b73b5445271379a59e3792b0d978f42d4a01e0cf7a67d9c33a3bb0a5", size = 267088, upload-time = "2026-08-06T13:48:58.41Z" }, + { url = "https://files.pythonhosted.org/packages/59/a1/ae94eb2c541add426378408379f233591e069040b1e2cdb33df9498a0682/coverage-7.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3fc9e047706fb4a9abb54f719d3aa643e80e5bb3818182c40aee01ac0f0247ba", size = 269508, upload-time = "2026-08-06T13:49:00.42Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c7/88a10694a1c6a213569766aba9f25847b28155d4ac731b13226db216356d/coverage-7.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05e491d4f3165d62d4f5c8fd48dfeabf2ae8f42cbbd484319af33ea851b78982", size = 270629, upload-time = "2026-08-06T13:49:02.234Z" }, + { url = "https://files.pythonhosted.org/packages/b3/34/d8b8232e5e55169933b59aabcef2fedfa4b9d8897361bb80fcbda146505f/coverage-7.15.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:226c66e80ec0598d3b9b4874123df167ccca342aca8714f77cac6829688ee09c", size = 264043, upload-time = "2026-08-06T13:49:04.102Z" }, + { url = "https://files.pythonhosted.org/packages/7e/35/58b009dbf8c471c7224716478b9fed4a7e1af15320e1ed41660978504663/coverage-7.15.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac41cc14bebda0dbfb0628036b7f75706935c95bcc07fefe9a0f93614aa60a57", size = 266963, upload-time = "2026-08-06T13:49:05.821Z" }, + { url = "https://files.pythonhosted.org/packages/62/aa/57fbda1b42c892968273c56b6ee9dc0f1310850859230a507bc7873b1f65/coverage-7.15.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8af623e5cd92080acddd02b38f2f406a2c3a0893c38950b211890361448fbf26", size = 264569, upload-time = "2026-08-06T13:49:07.706Z" }, + { url = "https://files.pythonhosted.org/packages/98/8a/360e6e7f24d477b7e889703af0afa878d15b6d4d8d2a822b2835c169a879/coverage-7.15.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07545711d4f0f32852a18f18ad11f76f0109909d09e78b9008b4cfc67e829429", size = 268299, upload-time = "2026-08-06T13:49:09.587Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/6f701261aee21b6b5fa8f7872229406dc917e125069448292223bf213606/coverage-7.15.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a0865421cfdc53654b342d515e5a233187590882d20b95752150e53f65460017", size = 263413, upload-time = "2026-08-06T13:49:11.604Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0f/6f04036edc260ed425af83e834f627fad48941ce97b50bfe6edd8b6fa623/coverage-7.15.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:460115e32ee40566476db5048f9bec1e842c127ad8e6f8be745aad3ac9cbc839", size = 265725, upload-time = "2026-08-06T13:49:13.38Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ce/d19b5d4d5c49a7bfb925fd74310fee7d28bc99520ac3367ccbc54e662518/coverage-7.15.4-cp314-cp314t-win32.whl", hash = "sha256:cbde877ef9dd7baf272b9bfef2b8a25edd45d9170fc326951dd20eb480335e85", size = 225079, upload-time = "2026-08-06T13:49:15.265Z" }, + { url = "https://files.pythonhosted.org/packages/26/bb/7aa1b3b173faee0679037ca950bbbe1247273656697994d8d13f80f8d4b4/coverage-7.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3da9e92d1c551fd7563833e9ade686efb0c4b7363ab7681a94283958c950bf5e", size = 225911, upload-time = "2026-08-06T13:49:17.279Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/4ea9e47426d80038d9222db3c4534cb6021a74b237d3ff97ffd33b6600dd/coverage-7.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3a54f5a0d85050c73a38f6793090ee83974531e67fe5e57a1da9bee11398aa5e", size = 225219, upload-time = "2026-08-06T13:49:19.293Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" }, ] [package.optional-dependencies] @@ -358,7 +389,8 @@ version = "4.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "korean-lunar-calendar" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pandas" }, { name = "pyluach" }, { name = "toolz" }, @@ -380,11 +412,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.4" +version = "3.32.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/30/03b03951873a1a0ffc7e8ca0e10c15597b59e8d0e39260704cd2ea087bc4/filelock-3.32.4.tar.gz", hash = "sha256:2bde2e4cf732e0153406d8a7bc80620ecf5e621fe0d25e41143c4e3b4733ff30", size = 222126, upload-time = "2026-08-23T17:37:55.363Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, + { url = "https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl", hash = "sha256:22e58ca3b1ae3b98993b762d7338367ae64fe50252bf78d59da3bfebcdf1cedd", size = 99864, upload-time = "2026-08-23T17:37:53.913Z" }, ] [[package]] @@ -398,14 +430,14 @@ wheels = [ [[package]] name = "holidays" -version = "0.99" +version = "0.103" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/69/7626f743128513c919ed058530d62a86902099532a86222260b0cfc70d7c/holidays-0.99.tar.gz", hash = "sha256:9ef8278cdfb67dbd93309ec9b30c30609ab35fd57cb207ce4593f80dc91196f5", size = 931630, upload-time = "2026-06-15T20:39:42.38Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/7f/5f740e8d6702c8524877dfdd3b7cd6825f09d0597e1610cc813a8b47c737/holidays-0.103.tar.gz", hash = "sha256:688a3cf0e10f46627039b778da4c962ecd5d9695610e80893c611dea931b6c91", size = 988261, upload-time = "2026-08-17T19:32:19.717Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/263e875ca954c44dbd29000a840b43bf875fbf4fdacf9430cd8fc92ad45e/holidays-0.99-py3-none-any.whl", hash = "sha256:bc47cefa781dbc6415e782767dea013794146cc629845354b393c53cdee90c64", size = 1503023, upload-time = "2026-06-15T20:39:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/ca/64/69565fc485b56a6d8726d7d1a8ca74b6025faab5d31671d264e26ed5cc85/holidays-0.103-py3-none-any.whl", hash = "sha256:d507f7f64379ed5a782b8a4a83fb99705f8c538c7cf4c70079ceb0a3e0447cc9", size = 1581726, upload-time = "2026-08-17T19:32:17.705Z" }, ] [[package]] @@ -419,20 +451,20 @@ wheels = [ [[package]] name = "idna" -version = "3.18" +version = "3.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] [[package]] name = "imagesize" -version = "2.0.0" +version = "2.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/5e/513ff06670c84e7b9887c1fdf61b2d42b4f574a831f2f1d2222023049d8a/imagesize-2.0.1.tar.gz", hash = "sha256:b2ba6a4dea487a7ebcd53248d3476aca449d30db12a2dde5e0c5ca9624fd77e5", size = 1883774, upload-time = "2026-08-24T12:35:19.13Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, + { url = "https://files.pythonhosted.org/packages/01/f9/575c8d760eae1fc99651b7cc5efd96ad5379ca4d6b53750b0fb4fe983f34/imagesize-2.0.1-py3-none-any.whl", hash = "sha256:ea0c9a0384df69ed86a943a15cde37d0360b82491b3910dc2215e202e62b5b02", size = 14794, upload-time = "2026-08-24T12:35:12.548Z" }, ] [[package]] @@ -476,75 +508,87 @@ wheels = [ [[package]] name = "librt" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, - { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, - { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, - { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, - { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, - { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, - { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, - { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, - { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, - { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, - { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, - { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, - { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, - { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, - { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, - { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, - { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, - { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, - { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, - { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, - { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, - { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, - { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, - { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, - { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, - { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, - { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, - { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, - { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, - { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, - { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, - { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, - { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, - { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, - { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, - { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, - { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, - { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, - { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, - { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, - { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, - { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, - { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, - { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, - { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/9b/356320fbae2ac8467e21c5e73e1389c80468e4998c62cc7d3536cc51b614/librt-0.15.0.tar.gz", hash = "sha256:4e66cbe84437497d951b799d3e1551291b6fb3d643820a7014b3655d57a59162", size = 214338, upload-time = "2026-08-07T10:49:42.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/52/06790ced2ac7117f890c21bda43c39c958ec82aa665c0718e821d33ff939/librt-0.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:823b92cf3c18ecd08afc70c42473888b41b6e8ef5046f3b82c05c154a2fa3d22", size = 148039, upload-time = "2026-08-07T10:46:41.165Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1d/8e150b7fc449a1f33c8a760965cc1f43b14fc1577d9d0b50ab2701420e74/librt-0.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c70bc1b602cf59917e8f0c7a2cbc8bcc6fbc14d5486136b00707a79619121d63", size = 153067, upload-time = "2026-08-07T10:46:42.418Z" }, + { url = "https://files.pythonhosted.org/packages/51/87/a162bc5a66a35599dc619ecb215145f4de7d68e886b479b6d12593139f7c/librt-0.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:814ff83a25b5fce8b9c80c4dd803153fb5c5599fc74db9e022466938368957ef", size = 493087, upload-time = "2026-08-07T10:46:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3a/aeea1fc620cf48060d3065b37614edbf97043c099d0f50782bc8ca61d897/librt-0.15.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:57f5eeb6ad4c180de583b1038e61fe5fbd9796bb69a8a1c1a0c7ddbec4c8c60f", size = 485608, upload-time = "2026-08-07T10:46:45.038Z" }, + { url = "https://files.pythonhosted.org/packages/52/ff/fe571ad416f0856fd0d5578ffc2e6dc531891e586e36b647bcf50569cab8/librt-0.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82909c8f7eb9952656b65d3147afde4cf8e6d5a991eebc86418b5e65843b0ab8", size = 498723, upload-time = "2026-08-07T10:46:46.35Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e1/7a65eb5dedb1f00aebd948cdd8e17add48bf066cab3514e9daf84ab45a6c/librt-0.15.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f779070399f991400fc451719e0ea388eb7de313388bada2c127a35de05f798a", size = 516002, upload-time = "2026-08-07T10:46:47.599Z" }, + { url = "https://files.pythonhosted.org/packages/5f/45/59832b0ebfbd08c2742e6ece372ceb53f18bf1faef5d33c8daf3abebf749/librt-0.15.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bac89069bc496ebdf4f79ebb57bbd10d0b214c8454225deb672d91002bd17e18", size = 508607, upload-time = "2026-08-07T10:46:48.873Z" }, + { url = "https://files.pythonhosted.org/packages/ea/0d/37fa73f3b43ebd8259f91ae9102a15e5a54e65d581e48dea72df3e81d7a4/librt-0.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e0d00c708fb2f5822b152429b1ac80a58dbbbc3f6c232c4d13a3f7fcf2ea5b4c", size = 530422, upload-time = "2026-08-07T10:46:50.45Z" }, + { url = "https://files.pythonhosted.org/packages/26/02/e046c6fe7a5881ac34623242192f484426ba8a75595fd18f22c53a3f530f/librt-0.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c6624fe268625869485553dd7cc1daf30d22558215bb2a4ff16f67a9801a31a", size = 534303, upload-time = "2026-08-07T10:46:51.693Z" }, + { url = "https://files.pythonhosted.org/packages/95/32/d5e6d861ab0366f3edf74f887ab0c9eb9f535aaf01d32b80b4f734daa179/librt-0.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f56b397858a23dacf35ede366ed2212fdc03a6a57a1ad36468ad6e9dc5fac091", size = 536084, upload-time = "2026-08-07T10:46:52.951Z" }, + { url = "https://files.pythonhosted.org/packages/2a/de/d69d725513fe53fc90c6d7a1f86e4428939bad2fb905b17fe4c18d413dde/librt-0.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4388184646efe2054911c5b00a1077d6d1ee86a95b7e8ba96dc7850a809f3f40", size = 514307, upload-time = "2026-08-07T10:46:54.194Z" }, + { url = "https://files.pythonhosted.org/packages/36/93/f8aded0d6682b4f25820fa86e0690f87f01df9fd7bd09ddb04d9167ad021/librt-0.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97335f59082f9fe2ce6c2a9cc6433a0114bbb6cd4d5c09dd76c95c68b9f9a8b0", size = 557686, upload-time = "2026-08-07T10:46:55.443Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/ffeb6bdeb6cd862b4272fddc8ad05f938dd25d020ed517e631813917d80a/librt-0.15.0-cp311-cp311-win32.whl", hash = "sha256:83380ffde38062a2e9bb55d83e74474f6614665528b98a6928720fc006dfffbb", size = 104917, upload-time = "2026-08-07T10:46:56.605Z" }, + { url = "https://files.pythonhosted.org/packages/96/28/7e2313a3ffbf0b4de7ba3da58a09e488507b4bd1ea2b5e69378354a23415/librt-0.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:f75720477ee05d509a310e856cacc8d909adc182f7b91193c207bcc26d7ee6db", size = 125886, upload-time = "2026-08-07T10:46:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/39/9e/04b8c3cde014ef255ee785730425268354543acc38902093a40afa0dc164/librt-0.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:256237037a3ab001ae8d9803b2d43562a4c3aa38739843694349e4d5ebb0fd56", size = 111885, upload-time = "2026-08-07T10:46:58.787Z" }, + { url = "https://files.pythonhosted.org/packages/ba/39/99c25030e782bdfb7a21be8c05254806a2e4bbb05c8d50c2a2130acbfa05/librt-0.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e87bc679f86a99aa3b26e3c78eeb821a247c9a28eae48eaafcc32c3bf4c3bb9e", size = 151021, upload-time = "2026-08-07T10:47:00.057Z" }, + { url = "https://files.pythonhosted.org/packages/14/43/f4b1bd1b2888798a1409808889a25ea1ba49eaabce7d681ed27734c2df9d/librt-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71599e011ac880e8e45d46047d714871894c7d4ab6f25626f8d4f89da21f368d", size = 155267, upload-time = "2026-08-07T10:47:01.311Z" }, + { url = "https://files.pythonhosted.org/packages/0c/db/3ad9c965c72f1e1d6beeec44ec10a54e17be8ae042fbb4baade16cbadced/librt-0.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c802434092b769b1d613ed2e13fac15fbfce1934a74bd10283b03c0fae231cd1", size = 503136, upload-time = "2026-08-07T10:47:02.45Z" }, + { url = "https://files.pythonhosted.org/packages/4b/07/5888a6d76acd62ebce66c61b74d94e9370b9c32929f111e487bb6546f8ed/librt-0.15.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5500eeae393a184d14e1f35645962c27129d20c81afa4069e6ef826ebc2b3aaa", size = 496670, upload-time = "2026-08-07T10:47:03.675Z" }, + { url = "https://files.pythonhosted.org/packages/29/39/ab57cc2f5b276156da02bb7f5a8921bada1cb1993ffec99acf811c602c23/librt-0.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ecfc32dfb46fb7b565bcd6abf9412acf978775a998273d22888a6d7953730dd", size = 513688, upload-time = "2026-08-07T10:47:04.981Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b9/bdbb0b648b5c2befb031f4c6f3b1dd857415e8fb492a25a3c764a6681e6c/librt-0.15.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cc46cfd15022e35084355478c9ac809d90b1152222706ac9a7655ec21df6fa", size = 531904, upload-time = "2026-08-07T10:47:06.211Z" }, + { url = "https://files.pythonhosted.org/packages/93/26/473c2e4b6c104e9e58e27ce95fc8005c8bd4fc36cae4f254371125a92db8/librt-0.15.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5f51401d102c885b9ca509e62c79b1dbff286e1b9b047fde6f763780789356d", size = 524427, upload-time = "2026-08-07T10:47:07.592Z" }, + { url = "https://files.pythonhosted.org/packages/26/60/03b3abb82b41714671b907bf6989b228e31e6a8af52dec82b5b0728dc250/librt-0.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cc30523e3f1a23fb7511cc659834a0d01a1042bb9de359bc1c131cc4ec6c9656", size = 543155, upload-time = "2026-08-07T10:47:08.866Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0e/9bb1f0a4affbd0a1888f4f79dc03ed2a299d9a2c26c59ab2a97dcbf11903/librt-0.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:59fe030d8ae4a57e3fb7756bf35a858de74e04066fc8555c53d0af979132af81", size = 546890, upload-time = "2026-08-07T10:47:10.327Z" }, + { url = "https://files.pythonhosted.org/packages/dc/84/6937a280d461f7de6e031ffb02edc2b7c3c90d49d630565ce8ff27cbc5f2/librt-0.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a6526a2a956bbb1e4ae3568c82e650fc99119c66bb011ea60715744955a2b4d", size = 555163, upload-time = "2026-08-07T10:47:11.798Z" }, + { url = "https://files.pythonhosted.org/packages/bc/95/2a2853c1ee014bf102116e7f897a04beeaeb2461b45b79af98bdfb95f1ef/librt-0.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:85ea21ec6730194d67156b0e0b5430ccb1d61f8b8b907e39b37f9812b74a13f0", size = 535812, upload-time = "2026-08-07T10:47:13.279Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4c/cf9601c1b4c5f09280acd5d83abdb2e68527a2be8257136eb42304218622/librt-0.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e47b8ba865d7ede071a91a7163073bbaeb72541f1ef8a07d512c45c7b5007f2", size = 573688, upload-time = "2026-08-07T10:47:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/47/6d/9ac7cbec46189a7625af4b5acbd25f10d827f4141b2002181848c8418923/librt-0.15.0-cp312-cp312-win32.whl", hash = "sha256:a5207ec414d1c4a2a7231b2086970dc036f94293cdf338190984958a013a42f1", size = 106138, upload-time = "2026-08-07T10:47:15.973Z" }, + { url = "https://files.pythonhosted.org/packages/38/d0/2ae99c83be86ce23f925ac1aeeedc777e97f427c4a8d190c70d0a16e9a87/librt-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:73b30cfa976659b3917c8f6153bdb0591c6a9ec6583599fd24a689b690622022", size = 126974, upload-time = "2026-08-07T10:47:17.049Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ef/dd24f9635c730b86b87587967dda7516b1845e8b17684603d31607fed598/librt-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:a54cf9e0ef47b96af580849db5471142200568ce1e02cbf416addab551369570", size = 112292, upload-time = "2026-08-07T10:47:18.222Z" }, + { url = "https://files.pythonhosted.org/packages/e7/42/467b53a601b406ccd7b97c1fd54b59cb34f9185ad5ce7e9d5c3c4e8961c8/librt-0.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:db13ca398005abcbe538deda87b686d9bd08b7001cf40c4c06b444960ae10a26", size = 151029, upload-time = "2026-08-07T10:47:19.312Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e6/36c2299b7a94b84fdd01220d8a777a71be5be0925bb0dbdf71c0a06a34d9/librt-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa1f1995789dca3698bc550aaceb09a51bd5df0a057ff84ff15296cd1975b801", size = 155194, upload-time = "2026-08-07T10:47:20.398Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/ed5071f9325845e670bd36012757419767fbf56af77ed483077b9e4db541/librt-0.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55456ea87d8df21808446d03817be2f65e20391c1c615d9187440dff28cd08dc", size = 502568, upload-time = "2026-08-07T10:47:21.652Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/6450c67c3615d87704bcbc21323fafc69c799b06a044c447529f725d4b01/librt-0.15.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5a86a5a08c2235316bdb359d5dbb6ce0abfca7fac06363103e2c5af571d92f95", size = 496153, upload-time = "2026-08-07T10:47:22.925Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d6/5f52b722bc75076954b3bfd49be15ea362df4d580c6fb315d0f617100d30/librt-0.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e56b6a368529bed262da40ce13f8fef590db0479819cca84f16a1f01ac356d0b", size = 513336, upload-time = "2026-08-07T10:47:24.213Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e2/c08fd1d36ce63ea5a12b85c5d37f4550b5f86a692167e41e5a74222607ae/librt-0.15.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:234d8d394721fa0d786af15ebf1f3fb7f3ed82fd1cd0cde45c2f247b5d4281d2", size = 531661, upload-time = "2026-08-07T10:47:25.507Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d8/d9482fcbeb177b9eb87bb3899eeb3b42be690313c652f9e146b1d0681fb2/librt-0.15.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d8363d7accb0286ac3a0e633f396e93800dafb8150494505daf9515bbda591f3", size = 524487, upload-time = "2026-08-07T10:47:26.79Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/075171517b41f861753034fbb151b42cfc83bcc853849f24f5e66fd60ccf/librt-0.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0f0ee3644d951f31055ad07d77d92520e84505dd7a432cc4cd501dd70ee06785", size = 543201, upload-time = "2026-08-07T10:47:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/b0/03/42c2330f37eeb475b6affeedd06518f60035f323af3a839335e3fc9fef2d/librt-0.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cfd1a81a648806e6a7717be4cc4d1bb392fa229752bf8444ba365e381e984d6", size = 546467, upload-time = "2026-08-07T10:47:29.396Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/1ad4c5638f7e64d8560328bd25c54b409a661bdb6ff254b38ff90744288d/librt-0.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6cd22c9da0d866558e46a041f1cc0c2bbb26b61b137b2347fa834c332e1d101", size = 555139, upload-time = "2026-08-07T10:47:30.815Z" }, + { url = "https://files.pythonhosted.org/packages/49/41/39fa7d15db1204cd1cbe6514680fbdc243adf754a0885061308f43afc013/librt-0.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6d5225ef8801e4ea5e482fa9b5dfb891dd9ef6f6d870f1f25d449ca2c70ac218", size = 536050, upload-time = "2026-08-07T10:47:32.222Z" }, + { url = "https://files.pythonhosted.org/packages/1e/88/c6dcf0dd8e26dc0c9a499a2abab8646c86dcaf9ecea9524cb46d3686331a/librt-0.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d28a05796b99f749bf8794f17ba9ba1612d0076b802e9cfc62c554634e9ce3b", size = 573700, upload-time = "2026-08-07T10:47:33.527Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9b/ab54c71a7918a7c34fa5327fb61390a77446a07a146fbfb1165250a61035/librt-0.15.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:2067ff438048cead9d223ca5675bae2a25e520a7c3e6c1498bf9c6892d22caab", size = 82194, upload-time = "2026-08-07T10:47:34.835Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b2/4f9a243bb892395f3becb80789ade13771701091f9f07ab8230247953ba8/librt-0.15.0-cp313-cp313-win32.whl", hash = "sha256:1cd3b721f24c206398b9e26da3c3a9c011e6e89d06f318ba8ebefc30f1003890", size = 106231, upload-time = "2026-08-07T10:47:36.251Z" }, + { url = "https://files.pythonhosted.org/packages/bf/af/64aff4885a40b93132382f2c314647d722574605416504379184ef3045ea/librt-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:f395a4a9a03ac062dbe9a9f82e0c720502e590a38feee6a757bc82e9c63afbd8", size = 126996, upload-time = "2026-08-07T10:47:37.453Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/335bccf6c7cb9028cb0b54aead27d9ece3f01f83bc6baa2abace5da655c1/librt-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:0a15cb554761247d84a3ec0cbdf4078d70725384f0e4662c0fa3b26266eb60ad", size = 112188, upload-time = "2026-08-07T10:47:38.729Z" }, + { url = "https://files.pythonhosted.org/packages/a8/93/949053fb462eecc4a9a5ee770a81f4b40be7b79538b245545d4aebc6b58b/librt-0.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f5de7feedc56337a088eb15cd9fafa9938367362221d8cc62c642b7f94821993", size = 149833, upload-time = "2026-08-07T10:47:39.86Z" }, + { url = "https://files.pythonhosted.org/packages/61/ca/8281aa6cd560a3420e4497729f6b704b53be3eeaaef82d5aeadddaf7441f/librt-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c0eb900c0e91f4aebe680845242e614f1864edfd44106380d0752ac29522bf8", size = 154088, upload-time = "2026-08-07T10:47:41.065Z" }, + { url = "https://files.pythonhosted.org/packages/dd/02/1a1662dceaba6a086360891448d5ce9a7d3555976cae59a31a39d744b9c7/librt-0.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8c9a650a188e38bac005048cbe6342e81407782944d01934540ab75e417df21", size = 494215, upload-time = "2026-08-07T10:47:42.388Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/99211619dc656370a3740c33d2b0b6d5a3fb1e73689314f6ed477a397dc4/librt-0.15.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:92bfed8deec93df30286b9fe9e3b1dd17329cc076a192b4ee5ec223841d54953", size = 491173, upload-time = "2026-08-07T10:47:43.683Z" }, + { url = "https://files.pythonhosted.org/packages/d4/aa/5448d0b05f4579b635d3899176817ebf561af0e57bacd425b5b1887264c1/librt-0.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec4b19788f835711a2072f9dbe6b03b3bf32ed1f0fb30cf399bdd59d9f0c33fa", size = 505512, upload-time = "2026-08-07T10:47:45.314Z" }, + { url = "https://files.pythonhosted.org/packages/95/82/01940e40b83c43a546c4a3c896cf34ca272a9690899d55914e4827b3dcce/librt-0.15.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4c7bacb70930f3d0a56f4ecf1be474a1f0d941b01dd73b756f3c256d42cb879", size = 523073, upload-time = "2026-08-07T10:47:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/88/fa/759c0030f3ee371439eb26de34fc745807caf0abb878af7af4b8b7c3dd3d/librt-0.15.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e79f05e4a08b4d880342673312bbc895b56df7765605796f15902eb5367d3ae", size = 515080, upload-time = "2026-08-07T10:47:48.319Z" }, + { url = "https://files.pythonhosted.org/packages/0b/27/894e072228fcb159703c655da69f8cd10dbed489c36e3df7dd032a2483be/librt-0.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a417149c0cba4d50b61e992e5a15e69eaf96746609b461cc4ed168aeef6b79dd", size = 534164, upload-time = "2026-08-07T10:47:49.875Z" }, + { url = "https://files.pythonhosted.org/packages/98/a3/0078e91c1f36f8815db17827de15650b9a3fe56c55fbf998c854b34e40d3/librt-0.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:da7a94d6a3411f579d72aa3e3bc5fbca7ed4549f3dbd7e5de3aa567333374285", size = 540616, upload-time = "2026-08-07T10:47:51.408Z" }, + { url = "https://files.pythonhosted.org/packages/86/33/81a29b796dd52a45e9ef7974c7732926e8f10f15b8d2be505665979f896d/librt-0.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:856f743ae607f2c1380eccb566c0038a9fb3eabf0fc2be2704d76d9f73557239", size = 545890, upload-time = "2026-08-07T10:47:52.818Z" }, + { url = "https://files.pythonhosted.org/packages/05/82/8be1baa1350e5d30cfd70ae79d0a6f4dc5862ef47f7bb2808aabc9bb86e5/librt-0.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:779a6e7c894737e5983e7790a9c78c4000c30e23c9aada08081bdbea53b0fa60", size = 523287, upload-time = "2026-08-07T10:47:54.165Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4f/d1be6a01a35c20ef734e0e44113f87d4af756a9354a89dcfbe3b4f8af5e1/librt-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96bb17dbe8bab3c0954fbebfc69ed395599de75b6bbc35e3270a878e15d4dd65", size = 565868, upload-time = "2026-08-07T10:47:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/67/88/649cfa33f5825927b160610f670bdab012a64d627eddb94fa795ea4292fd/librt-0.15.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:7220697efaa6e5348fc3d18ee7f8563d4bfecd9872b37ffb915bfc1d08840622", size = 81619, upload-time = "2026-08-07T10:47:56.886Z" }, + { url = "https://files.pythonhosted.org/packages/22/31/8e88a8d5e48fc8d1a817787fb6811dfff6499acd6c8683dd83934aa6ede0/librt-0.15.0-cp314-cp314-win32.whl", hash = "sha256:f54598964d357b1c5ab77cf5d92f21e598fe0e23cdbe9618480807f81b4eba15", size = 100138, upload-time = "2026-08-07T10:47:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/80/92/20fd6c4b6a1b1a564b076d55cd3d427d8428217d7638dc25a654cc4791d4/librt-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ff5893a2c23d886aa9ce786de5ac6ddc74aeeaf90743682b74d920e117d2e28", size = 121258, upload-time = "2026-08-07T10:47:59.564Z" }, + { url = "https://files.pythonhosted.org/packages/fc/28/6af430b44d9ebb897b865a3c363b6dcace51357be2347cc0f8f869656a86/librt-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:3722a099730704c9a3d70c879fc0f51daec25fe5f1555672d97bc595abeafb95", size = 106467, upload-time = "2026-08-07T10:48:01.097Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/b42bb798942ced219f6d63b27e07f91237887a8d0bd0921666db79a13790/librt-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:38c0c7d4b6fc06c3324b3f9162c8391bfc4fd9dde53afe1033ce7edb48d5a714", size = 159523, upload-time = "2026-08-07T10:48:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/75/03/1b53cd4ef904e73b1d828a5f90143bf94a2967d7cfff0b9ccf93e12aa9b4/librt-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b2fdd7ead3c995c37940a790690660d0ca006c302db26cc51933f6766866fc3", size = 161638, upload-time = "2026-08-07T10:48:03.725Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c4/9f9c9fba097d49e9e694c2b4dc331df31884645ecbc58a93b4b5fc69d2c5/librt-0.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fde98cf1fc4bac144ce23c2c4c017b924ba714509ea9334977b0b27050c837d", size = 701795, upload-time = "2026-08-07T10:48:05.135Z" }, + { url = "https://files.pythonhosted.org/packages/4c/05/0966840bda0380c8ae167b9043c6230202941cc90ea29c48e096964c765e/librt-0.15.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e3b461183c5fa7681b48560f91515f53a953122fb30c71e07abc67d7ddf58c38", size = 682147, upload-time = "2026-08-07T10:48:06.555Z" }, + { url = "https://files.pythonhosted.org/packages/18/af/1c47ca573c30ea47d195aec26133af522fea1104afaace028d7b32247ea8/librt-0.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4bbcc257e3babea20a91715c361b24554ec4e8f51aa578568afc230799fe1a19", size = 696397, upload-time = "2026-08-07T10:48:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/2e/0f/1aed6223d4f9f9d1171a8596ff100ea4c3f7699fea7a4ba657c3e60daa6c/librt-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b845b8d48088fad0cadc84be4b8fda63203be7e9237b71015b3925443c1f35ab", size = 722542, upload-time = "2026-08-07T10:48:09.569Z" }, + { url = "https://files.pythonhosted.org/packages/c6/22/9e3a929aea456c97d69e6ef3884efea56d4807f97399471cc946baebd8af/librt-0.15.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b30e600e8f337b9bd7f39b86d9fdfedc73cc46e3d0f745931a23a234220bb7e2", size = 729709, upload-time = "2026-08-07T10:48:11.129Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1b/c327ef6018e3a9ca0b8e7c5eddeeb331ba8f9b76c24e126d37d0f6d62faf/librt-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:64b0c8c35aa4c4ed79896359f3e0b285cbe4e610042106500da4811c322cc108", size = 752891, upload-time = "2026-08-07T10:48:12.558Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d1/d5f1ea02c56930087009e39db9b70660a663e76c730b27b925d786718457/librt-0.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0da0d94cb802f32a0524653e7201f2cef72d5f700a5407678f5290483d4fcd08", size = 745301, upload-time = "2026-08-07T10:48:14.55Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3c/5f7c585d15ebb2250c73e7c0ee4e9e47be72c65d520c07ddbcdc62037674/librt-0.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4a6369168d371207339b1e50d4532b06a7121586141f82599505a3f315751d47", size = 747921, upload-time = "2026-08-07T10:48:16.453Z" }, + { url = "https://files.pythonhosted.org/packages/7f/52/1443a446486eba966bcbca1696b472e4f210320ec42f490a47f48fbf0fdc/librt-0.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c434e072557ade9cbc642d052c89d031efe47d5c9614523619d0d74a02378e81", size = 727561, upload-time = "2026-08-07T10:48:18.089Z" }, + { url = "https://files.pythonhosted.org/packages/79/91/2270a9380f11725cf83ce1925a5e32dd1dde2be9bba597f25c10a38644e7/librt-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7eec6a42018bc1d45763b1c162d3d2bf7c3b9a1b0ed30d3e91dcba390efefcc", size = 774417, upload-time = "2026-08-07T10:48:19.611Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/f4b1548d4f5b99186737fe27aec238e9823e8d5d23bf4df007c030689dc5/librt-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:6912fa5e635d74529ac7cdb1bdf6ca3af4453da8d1edbe0110ee1cb4ad407ebf", size = 104381, upload-time = "2026-08-07T10:48:21.048Z" }, + { url = "https://files.pythonhosted.org/packages/80/b6/134afad262def1de04c0843c376d02135f1168af43f22e09a52bd8394727/librt-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8e11699ed745931c395acd3621b07062e0f840efa6935aad87a64ed0995f0915", size = 127034, upload-time = "2026-08-07T10:48:22.561Z" }, + { url = "https://files.pythonhosted.org/packages/99/5f/1b6846b20572bd699c9e9ec321a5f781845bee477df2aa2a43b28bc40119/librt-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5d2a91724463bfed4f573cd7a9fdc856d2e230d0c0e5a61416a93481dccd8605", size = 110827, upload-time = "2026-08-07T10:48:23.804Z" }, ] [[package]] @@ -683,11 +727,11 @@ wheels = [ [[package]] name = "narwhals" -version = "2.22.1" +version = "2.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/62/3c/c4ef2164a71c1a63d7f1ae411c4082c5fa872405106db60a4b7114989ad7/narwhals-2.22.1.tar.gz", hash = "sha256:d62920805a0a43b7ff8b54b0c0d3142d796f8a9301836ada37e573d6a33cbcd9", size = 647493, upload-time = "2026-06-05T12:34:34.051Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/7b/6248dada39781db1ab3ebf08943080df0796098515a87f6f8696d14ec744/narwhals-2.25.0.tar.gz", hash = "sha256:62c036c810662bf7820b7737077176313bc59350eeeefb808510f388c743e4b2", size = 677076, upload-time = "2026-08-20T18:10:15.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/ca/36339329c4604adbcc99c899b7eb1ce1a555c499b6a6860757dc9bfed36d/narwhals-2.22.1-py3-none-any.whl", hash = "sha256:60567d774edf77db53906f89d9fbd164e66e56d66d388e1e6990f17ac33cfb53", size = 454815, upload-time = "2026-06-05T12:34:32.289Z" }, + { url = "https://files.pythonhosted.org/packages/eb/dc/55481808fd70ef1567cf13540ffd4702af3f74b112e35427564b03f79c2d/narwhals-2.25.0-py3-none-any.whl", hash = "sha256:1f0f403e8c7e4463cde9bfe78b12fdd809e3ae3dda6d9b2f802934fb9c7a6a8f", size = 467373, upload-time = "2026-08-20T18:10:13.834Z" }, ] [[package]] @@ -703,6 +747,11 @@ wheels = [ name = "numpy" version = "2.4.6" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, @@ -778,18 +827,102 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, ] +[[package]] +name = "numpy" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, + { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, + { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" }, + { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" }, + { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180, upload-time = "2026-08-09T13:46:03.939Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878, upload-time = "2026-08-09T13:46:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922, upload-time = "2026-08-09T13:46:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168, upload-time = "2026-08-09T13:46:12.275Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501, upload-time = "2026-08-09T13:46:15.322Z" }, + { url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701, upload-time = "2026-08-09T13:46:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065, upload-time = "2026-08-09T13:46:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031, upload-time = "2026-08-09T13:46:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028, upload-time = "2026-08-09T13:46:30.046Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627, upload-time = "2026-08-09T13:46:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414, upload-time = "2026-08-09T13:46:36.036Z" }, + { url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967, upload-time = "2026-08-09T13:46:39.084Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874, upload-time = "2026-08-09T13:46:42.075Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276, upload-time = "2026-08-09T13:46:44.387Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154, upload-time = "2026-08-09T13:46:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909, upload-time = "2026-08-09T13:46:51.442Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685, upload-time = "2026-08-09T13:46:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181, upload-time = "2026-08-09T13:46:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085, upload-time = "2026-08-09T13:47:01.903Z" }, + { url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971, upload-time = "2026-08-09T13:47:04.963Z" }, + { url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306, upload-time = "2026-08-09T13:47:07.976Z" }, +] + [[package]] name = "numpy-typing-compat" version = "20251206.2.4" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/5f/29fd5f29b0a5d96e2def96ecba3112fc330ecd16e8c97c2b332563c5e201/numpy_typing_compat-20251206.2.4.tar.gz", hash = "sha256:59882d23aaff054a2536da80564012cdce33487657be4d79c5925bb8705fcabc", size = 5011, upload-time = "2025-12-06T20:02:04.942Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/63/7c/5c2892e6bc0628a2ccf4e938e1e2db22794657ccb374672d66e20d73839e/numpy_typing_compat-20251206.2.4-py3-none-any.whl", hash = "sha256:a82e723bd20efaa4cf2886709d4264c144f1f2b609bda83d1545113b7e47a5b5", size = 6300, upload-time = "2025-12-06T20:01:57.578Z" }, ] +[[package]] +name = "numpy-typing-compat" +version = "20260602.2.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/db/5cd1d99caea4bf39fd477686ded4b9b70dff3c7673b5d84ef2d96a4f5aab/numpy_typing_compat-20260602.2.5.tar.gz", hash = "sha256:1885a678e9a24564839ed5d1711c0031735fb7de7f0b5ed88d550e5d45a8d4f9", size = 4593, upload-time = "2026-06-02T15:52:39.331Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/a4/9376b38b7387a0296b1f626b966e5503578625c9673777db1b45bf70acb0/numpy_typing_compat-20260602.2.5-py3-none-any.whl", hash = "sha256:21ba7757c8924d359a9ed3ab2163c282a70983ae64498fdba6d1892a6641c8b1", size = 5881, upload-time = "2026-06-02T15:52:34.167Z" }, +] + [[package]] name = "openpyxl" version = "3.1.5" @@ -804,12 +937,13 @@ wheels = [ [[package]] name = "openseries" -version = "2.1.9" +version = "2.1.10" source = { editable = "." } dependencies = [ { name = "exchange-calendars" }, { name = "holidays" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "openpyxl" }, { name = "pandas" }, { name = "plotly" }, @@ -817,7 +951,8 @@ dependencies = [ { name = "python-dateutil" }, { name = "requests" }, { name = "scikit-learn" }, - { name = "scipy" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "scipy", version = "1.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "tzdata" }, ] @@ -830,7 +965,8 @@ dev = [ { name = "pytest-cov" }, { name = "pytest-xdist" }, { name = "ruff" }, - { name = "scipy-stubs" }, + { name = "scipy-stubs", version = "1.17.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "scipy-stubs", version = "1.18.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "types-openpyxl" }, { name = "types-python-dateutil" }, { name = "types-requests" }, @@ -840,7 +976,7 @@ docs = [ { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-typehints", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "sphinx-autodoc-typehints", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-autodoc-typehints", version = "3.13.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-rtd-theme" }, ] @@ -868,7 +1004,7 @@ requires-dist = [ { name = "sphinx", marker = "extra == 'docs'", specifier = ">=9.0.4" }, { name = "sphinx-autobuild", marker = "extra == 'docs'", specifier = ">=2025.8.25" }, { name = "sphinx-autodoc-typehints", marker = "extra == 'docs'", specifier = ">=3.6.0" }, - { name = "sphinx-rtd-theme", marker = "extra == 'docs'", specifier = ">=3.1.0rc1" }, + { name = "sphinx-rtd-theme", marker = "extra == 'docs'", specifier = ">=3.1.0" }, { name = "types-openpyxl", marker = "extra == 'dev'", specifier = ">=3.1.2" }, { name = "types-python-dateutil", marker = "extra == 'dev'", specifier = ">=2.8.2" }, { name = "types-requests", marker = "extra == 'dev'", specifier = ">=2.20.0" }, @@ -880,8 +1016,13 @@ provides-extras = ["dev", "docs"] name = "optype" version = "0.17.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9b/86/e6f1f6f3487492dfcf3b7a2d4e2534d27af6ac05b364b276706906c34865/optype-0.17.1.tar.gz", hash = "sha256:07bfa32b795dea28fba8605a6288d36370d072f25183fb9c29b5a90f4b6f5638", size = 53572, upload-time = "2026-05-17T22:13:28.725Z" } wheels = [ @@ -890,89 +1031,111 @@ wheels = [ [package.optional-dependencies] numpy = [ - { name = "numpy" }, - { name = "numpy-typing-compat" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy-typing-compat", version = "20251206.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, +] + +[[package]] +name = "optype" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "typing-extensions", marker = "python_full_version == '3.12.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/51/51dc9b1009e020f44703933d4d1ee3429c647c026ce7806b37ee2b257998/optype-0.18.0.tar.gz", hash = "sha256:ea10dee61b15ca299ed0d97025d362585c4dfc5481159bb999a1d0d414bbcb04", size = 59967, upload-time = "2026-06-07T22:13:17.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/91/2b064a117cb2593bc3eb04ee994134ca2a6bf2162c92961769947e3258cc/optype-0.18.0-py3-none-any.whl", hash = "sha256:91822ed8516e7a4f225ba53d30f291776c35f31553fb952d7cda98286679c5a6", size = 73410, upload-time = "2026-06-07T22:13:16.324Z" }, +] + +[package.optional-dependencies] +numpy = [ + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy-typing-compat", version = "20260602.2.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] [[package]] name = "packaging" -version = "26.2" +version = "26.3" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] [[package]] name = "pandas" -version = "3.0.3" +version = "3.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495, upload-time = "2026-05-11T18:52:11.558Z" }, - { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250, upload-time = "2026-05-11T18:52:17.005Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558, upload-time = "2026-05-11T18:52:19.865Z" }, - { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611, upload-time = "2026-05-11T18:52:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670, upload-time = "2026-05-11T18:52:25.4Z" }, - { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708, upload-time = "2026-05-11T18:52:28.139Z" }, - { url = "https://files.pythonhosted.org/packages/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea", size = 9907609, upload-time = "2026-05-11T18:52:30.982Z" }, - { url = "https://files.pythonhosted.org/packages/53/85/1b7f563ebc6357c27233a02a96b589bcce1fa9c6eb89fb4f0e56421d277e/pandas-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:5cc09a68b3120e0f54870dede8287a7bb1fa463907e4fcec1ea77cab6179bf7a", size = 9165596, upload-time = "2026-05-11T18:52:33.334Z" }, - { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, - { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, - { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, - { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, - { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, - { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, - { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, - { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, - { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, - { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, - { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, - { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, - { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, - { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, - { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, - { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, - { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, - { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, - { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, - { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, - { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, - { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, - { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, - { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ef/f1fd7431d635bf20015489bf0bd69c17fff1018de773540f651455a3916b/pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282", size = 10397178, upload-time = "2026-07-22T22:17:48.274Z" }, + { url = "https://files.pythonhosted.org/packages/31/b4/0eafac990a431561187694126de01f9b12559549b4d86360c0c4bd870fde/pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298", size = 9990736, upload-time = "2026-07-22T22:17:52.388Z" }, + { url = "https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899", size = 10814438, upload-time = "2026-07-22T22:17:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/d6cc4d7e508bbccf5d6027314a8312bc7ac73d0ec7f195f53838daafab40/pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a", size = 11323634, upload-time = "2026-07-22T22:17:56.858Z" }, + { url = "https://files.pythonhosted.org/packages/70/2b/d5f0a8c90dd0ae04e64ba53b871afb796ec026b615086d382ddc2ade729b/pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928", size = 11850860, upload-time = "2026-07-22T22:17:59.1Z" }, + { url = "https://files.pythonhosted.org/packages/5c/30/183aec2e19adf778a98d29b5729a0a68f4cc4ebf9b9c3b70d0297355bcb1/pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d", size = 12411100, upload-time = "2026-07-22T22:18:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9a/31f4983f191af51ab2a8f2d0c7b33dff3a84da26533f982fff02c2f9e28b/pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a", size = 9968804, upload-time = "2026-07-22T22:18:03.903Z" }, + { url = "https://files.pythonhosted.org/packages/49/97/7886c89a39045c69ad82cbceaf3343810480c8ef49a216319ce8183860a6/pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a", size = 9205447, upload-time = "2026-07-22T22:18:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/1c/54/1dc810ea558d1320b597aa140a514f2fdf1d2ea09c38cf556f13ea712ec9/pandas-3.0.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d", size = 10411717, upload-time = "2026-07-22T22:18:08.307Z" }, + { url = "https://files.pythonhosted.org/packages/68/56/fbe81c09195924d8b7b8d4461a20458fe80a6a5ed6b24f0314da684277e1/pandas-3.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc", size = 9957095, upload-time = "2026-07-22T22:18:10.6Z" }, + { url = "https://files.pythonhosted.org/packages/e0/51/fac252f4a913ed5eabf3c11b880a9e8d5a6c10f0b2129d0462212d238b4d/pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc", size = 10485458, upload-time = "2026-07-22T22:18:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/12/98/e976540c1addf70442be7842a18cf70884a964abbf69442504f4d2939989/pandas-3.0.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d373ce03ffd84010ed9839fa73672a9c8256990532e158440c0085db7d914b34", size = 10998091, upload-time = "2026-07-22T22:18:15.209Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8c/1f29b5be8d3fc47dd7567eb167fabba2085879b31e0287ce7cba6d3d2ff4/pandas-3.0.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a29c53d85ea98c5e792c59ef82ee9fbe6ca902c0d0adb6b23f45ef894cd7bf6", size = 11499501, upload-time = "2026-07-22T22:18:17.689Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e2/bd9c98ad2df7b38bde002adde4cdf353519da51881634323b126c55997f9/pandas-3.0.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5ad3b02ed6bc7d7ae9b70804b2c6aa31827489d150f8e623ce82491b82085d7", size = 12060559, upload-time = "2026-07-22T22:18:20.147Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9a/ffbd852d58bd74a617fe2f8ee6a58a96982271ce41cf981eab22190b4a4b/pandas-3.0.5-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:b2acb4650527eec6822c3dadb2b771277b65e7dae7a267d4bccf65fd1bb3fbce", size = 7197652, upload-time = "2026-07-22T22:18:22.502Z" }, + { url = "https://files.pythonhosted.org/packages/70/b5/d2d3e9ae73362ba4229651b0ee1455cf78073a1ce585f6ff693782ce263e/pandas-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:80a611068e8a3ac23f7398c6c14eb46dc974e5cc9997f653e2dcfd1da74edd41", size = 9831691, upload-time = "2026-07-22T22:18:24.534Z" }, + { url = "https://files.pythonhosted.org/packages/52/51/dea1e89d6a6796b9c43f85a09b484ee03edb8a4c4842e73e200a8c11301c/pandas-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:25ff585b972a18ef1fe9ffa3ac6544d9950508aa76832e5147640b6022821e49", size = 9105796, upload-time = "2026-07-22T22:18:27.064Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/7b95c4a0025227d6f118c4039b423412ac6a982db02864166185d812fbc7/pandas-3.0.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b", size = 10385742, upload-time = "2026-07-22T22:18:29.346Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0c/dc78fd8c4da477b4b5e8ad37295af352190d21ef63a9ee1bc071753074cc/pandas-3.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3", size = 9932067, upload-time = "2026-07-22T22:18:31.833Z" }, + { url = "https://files.pythonhosted.org/packages/3e/71/3592c055cf44df9808550f9368ceda80ff2b224d355ef73fe251dcda1802/pandas-3.0.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b", size = 10466756, upload-time = "2026-07-22T22:18:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/e3/70/4363150359f95b4cb4bcbb34ca23572bb5495749a621a8f3d5a1ddfd293c/pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be", size = 10938525, upload-time = "2026-07-22T22:18:36.81Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d0/317e7a0c67c0e69fa905a0161409397a7dc2d46ff611f6ca4803352c042b/pandas-3.0.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58", size = 11489303, upload-time = "2026-07-22T22:18:39.287Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/36dade89b49e4f9d5cbdbe863772581f98c0c6d78fc39ad4c557f6f2e17e/pandas-3.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee", size = 11989004, upload-time = "2026-07-22T22:18:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ba/18c4ec8a746e177da05a9e7a7963781d8ea195780724f854601b6ebd6b78/pandas-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6", size = 9826896, upload-time = "2026-07-22T22:18:44.539Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/28a57266b753799a87b8bc79e7887ac6fd981b8c6d2978a0b7e7b6bd708c/pandas-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e", size = 9094790, upload-time = "2026-07-22T22:18:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/51/2f/cf6aae281264f4463f0875bcbb15fd2bb6d291cc535187dad1732475e4a9/pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36", size = 10390034, upload-time = "2026-07-22T22:18:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/06/ec/5189518c7a7659c4bdcc6b1eb32c46c6f3c86b0661ffd84143d1112c7732/pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c", size = 9980065, upload-time = "2026-07-22T22:18:52.249Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/598503ce8d7e3c35601e0747ba288c7864baae66380725bc12f13f884dfe/pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0", size = 10545532, upload-time = "2026-07-22T22:18:54.813Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/ceae2adf7034e07e9910299fe412e1819c4f0dd520700a888bcb03625448/pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b", size = 10963120, upload-time = "2026-07-22T22:18:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/66/25/86e0f4451874eb79e688deeebe3c451fec4557f8952005818d800ee8ac7e/pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94", size = 11563178, upload-time = "2026-07-22T22:18:59.729Z" }, + { url = "https://files.pythonhosted.org/packages/f3/45/8643daa3b4147e433adfcccefdd0380d3aad79d86b15d8999730fe1944d5/pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da", size = 12028708, upload-time = "2026-07-22T22:19:02.164Z" }, + { url = "https://files.pythonhosted.org/packages/96/58/ad979ae617615576e8aafd569c9d4b62f1191d896e38f51d66ba06f3b89a/pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92", size = 9951806, upload-time = "2026-07-22T22:19:04.596Z" }, + { url = "https://files.pythonhosted.org/packages/69/32/7ac03886b304049a9d2625ee88f59af760d8a93bd30ed9239bce7b9869a8/pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85", size = 9238297, upload-time = "2026-07-22T22:19:06.836Z" }, + { url = "https://files.pythonhosted.org/packages/be/ed/1d1f2ee5547d5167face2376d11c8b2a4c7bfff5a416ee7a9046891fab1e/pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca", size = 10849690, upload-time = "2026-07-22T22:19:09.391Z" }, + { url = "https://files.pythonhosted.org/packages/57/55/17e17152e98fbb0c4b1e562bc65387a2f20a80db0f4a86bf8d3a0e4248d4/pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da", size = 10509945, upload-time = "2026-07-22T22:19:11.773Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/817d44dbf83facf9556f33576d9af0a241981e7bb5c00606c0bcb5df8dda/pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a", size = 10392197, upload-time = "2026-07-22T22:19:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/f1/da/889f00c0a6f5aa1545add70abbf01502dff87ab577adb855bd631c54d2f2/pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040", size = 10862726, upload-time = "2026-07-22T22:19:16.351Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/f1e934fb3c98fce859c6147c6785816c7b5b9ab7821115c5d8c4de9842b9/pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd", size = 11414864, upload-time = "2026-07-22T22:19:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/fe/be/d448af7d657d82e1888dd8551f79c6d6fb161080b5b9752d84d910ec2319/pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c", size = 11925105, upload-time = "2026-07-22T22:19:21.515Z" }, + { url = "https://files.pythonhosted.org/packages/29/c1/ccb4238212c8c4f496c584f3044d94e0c030ed8e1d68999db46c91c2242f/pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea", size = 10387612, upload-time = "2026-07-22T22:19:24.257Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776, upload-time = "2026-07-22T22:19:26.694Z" }, ] [[package]] name = "pandas-stubs" -version = "3.0.3.260530" +version = "3.0.5.260730" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/aa/c41a8a0ff86fd85dbb3ec0c1f3fa488ca64a8b5f82654ae1b07d84acefe5/pandas_stubs-3.0.3.260530.tar.gz", hash = "sha256:d1efe47b2e5a312c047d7feabec5cb7a55365747983420077e9fcbe9ab74f714", size = 113183, upload-time = "2026-05-30T17:47:40.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/d2/dea4a3a56b7b5f69c5fbca9f14625fcf28e1a39a657e9833d4a10bcac593/pandas_stubs-3.0.5.260730.tar.gz", hash = "sha256:f70a232c57d93a5a2c81f8a53953e10891a5374bc92652277deb325e2e4d0ff3", size = 114631, upload-time = "2026-07-30T14:31:42.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/e0/99ec5b02203c4e9ce878bc63d8caa06ac1f891e4d63bded9a5ced70fcb4f/pandas_stubs-3.0.3.260530-py3-none-any.whl", hash = "sha256:a6277eb1c8cebf48d9b2413fcd2e9a6b4ff479c934a223c29eacbc3058c4cb55", size = 173780, upload-time = "2026-05-30T17:47:39.13Z" }, + { url = "https://files.pythonhosted.org/packages/60/c2/959caec5c46f484b5f8bb6def4b0cf7a45ba6acda26f12b75142d98cc5ae/pandas_stubs-3.0.5.260730-py3-none-any.whl", hash = "sha256:60e90e3e1eda6937e337e243cbe6217e151c11137cd7eddf832af537c7310bfd", size = 174807, upload-time = "2026-07-30T14:31:41.17Z" }, ] [[package]] @@ -986,24 +1149,24 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.10.0" +version = "4.11.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/bb/ebc6636e1ae41314f796ebb7215fd28febb45f9aac72f2b04cb74b5071dc/platformdirs-4.11.4.tar.gz", hash = "sha256:f3373be828247211d0febabea97e238c3dfde8a60b3c90c32756fb52cb21556d", size = 34079, upload-time = "2026-08-24T14:53:49.676Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, + { url = "https://files.pythonhosted.org/packages/28/be/0ff05fcd2938fb58ad9219bd54135968342d214737e012d62d43f06a2dd6/platformdirs-4.11.4-py3-none-any.whl", hash = "sha256:e34ff91a24bcddc6d939b878bdf3f5c437c9c46fe9e212b1bf455fdf1ee57586", size = 23741, upload-time = "2026-08-24T14:53:48.406Z" }, ] [[package]] name = "plotly" -version = "6.8.0" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "narwhals" }, { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/fd/d72c292d78aadb93d1a9bcd76bf3c678271040c7cf10abe5788b33040a39/plotly-6.8.0.tar.gz", hash = "sha256:e088e7ddc68d4f70e3d66659224727a45296d71d2b8284181862d3d8f1f0d88f", size = 6915161, upload-time = "2026-06-03T18:33:40.226Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/9e/8894e8eae20a5b2a44aa568b1d2d80291e8eea6ce7bc75cdc7a152aef0ac/plotly-7.0.0.tar.gz", hash = "sha256:08b21f1244a97e7a1a699833c4bb2678475aa108b3f1989886ed0b038ebfd849", size = 6218668, upload-time = "2026-08-25T17:47:29.088Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/14/abe5ce876ab5b66ee3c691bf537fcd43d037aea55d447aacf74630a8f31e/plotly-6.8.0-py3-none-any.whl", hash = "sha256:13c5c4a0f70b74cab1913eda0de49b826df5931708eb6f9c3010040614700ec8", size = 9902055, upload-time = "2026-06-03T18:33:34.26Z" }, + { url = "https://files.pythonhosted.org/packages/e0/2f/6f492108d9955bac97979d9949c1b35eab30fc630b1f22bbdd2c7cacbab4/plotly-7.0.0-py3-none-any.whl", hash = "sha256:78cbf7bd06d1b05bb3b8ec1b709864695229b55151b6f7530fbf55517ead6fdd", size = 9052859, upload-time = "2026-08-25T17:47:24.689Z" }, ] [[package]] @@ -1017,7 +1180,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.6.0" +version = "4.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -1026,9 +1189,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/89/1f3e8e1fc3e97de0fa963495832f581f025f29471602a309e48808244292/pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441", size = 198670, upload-time = "2026-08-10T22:07:18.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, + { url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" }, ] [[package]] @@ -1150,11 +1313,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.20.0" +version = "2.21.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" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] [[package]] @@ -1168,7 +1331,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.1.0" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1177,9 +1340,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -1223,15 +1386,14 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.2" +version = "1.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, - { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/8f/3c92c45737f654f2488ab3662b7604a55d3d35146d37c9ce80f5c95b95a6/python_discovery-1.5.3.tar.gz", hash = "sha256:e500eb24025fb7c4876c1fdcfbafd9028a10c71b661aee38cb6fb0de594518c1", size = 82477, upload-time = "2026-08-24T14:48:46.396Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, + { url = "https://files.pythonhosted.org/packages/30/12/823d9a321904ccfd2969a24b84fdfd1e6614c707ec569c62879bf1dbc6c5/python_discovery-1.5.3-py3-none-any.whl", hash = "sha256:8305296358f1aa2ed302a25b84be7df84fef8ca47c7dce2da63cb7325333044e", size = 38290, upload-time = "2026-08-24T14:48:45.305Z" }, ] [[package]] @@ -1345,8 +1507,10 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "joblib" }, { name = "narwhals" }, - { name = "numpy" }, - { name = "scipy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "scipy", version = "1.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fa/6f/37092bdb25f712817231799fc5674d8e704066a8a70c1d2d40517e18b4ab/scikit_learn-1.9.0.tar.gz", hash = "sha256:8833266989d3a5110178a9fae30783675460724d0e1efb13b14901d2c660c557", size = 7750767, upload-time = "2026-06-02T11:54:32.706Z" } @@ -1387,8 +1551,13 @@ wheels = [ name = "scipy" version = "1.17.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -1454,18 +1623,102 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] +[[package]] +name = "scipy" +version = "1.18.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/74/66de6258867beb2ef08f35f9f2ac017a52cacd5081714d239ff1a442d458/scipy-1.18.1.tar.gz", hash = "sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307", size = 30781235, upload-time = "2026-08-21T23:28:50.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/f7/240c110c08693826b4513a52f5717d62ec7c7af72f2920821247c03b17b3/scipy-1.18.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:457fd7a2a8edeb044ab6ffbc0aa03ff6cd18491356e5e0c834d76ce621b916d1", size = 31111061, upload-time = "2026-08-21T23:23:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/05/4a/78c6285577c375e7cf27277ea8ee6961224327f1e1a0c44af5f17f23635c/scipy-1.18.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:e708533e8b2ae2497d65346538a7dcc92814410b25b81432eac66de0f2af8265", size = 28733332, upload-time = "2026-08-21T23:23:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f6/a5b82f8abbe14d134691b8b903696f701d25a081353a29dc655c364d9e62/scipy-1.18.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7bbf207c4453ce1ad2e00b17313852b33310b83090c2311bdaf97f93c0380d12", size = 20475078, upload-time = "2026-08-21T23:23:54.138Z" }, + { url = "https://files.pythonhosted.org/packages/23/22/0858a0bbd6b3e825ceb8cd9baf9eaf3b2f2b1d77727eb6be40500bcdc92f/scipy-1.18.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:78c0665edead396b1abb4897c41a5c1d9bf090c8a637a4c20a61678e0a264e66", size = 23108904, upload-time = "2026-08-21T23:23:57.824Z" }, + { url = "https://files.pythonhosted.org/packages/75/9a/2e71719f31eaefe0e3a1706c4a1ded94e664bfd95ffca2b219a671faee01/scipy-1.18.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c085faa2cfa879c5141df483f836f4d691045a078224a670fa570fa01612d89", size = 34025113, upload-time = "2026-08-21T23:24:02.209Z" }, + { url = "https://files.pythonhosted.org/packages/df/64/ff35eb9e54894cf471ff4716abd3c81eb0a0626869217ce3e6ba4ccf17d7/scipy-1.18.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f55fa87b6c612ecd6b058f167c53231b1d14e412efe361d3d6e38b3631c73218", size = 35344199, upload-time = "2026-08-21T23:24:07.844Z" }, + { url = "https://files.pythonhosted.org/packages/d3/af/c5538be1792f7034c12c7db6ee67cace58253c7b87b122d68253eaf5de89/scipy-1.18.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c35d74ce0e193ff740c2f2be2ac913ddc232fe6c1ff40b26cfecb9c670c63314", size = 35639587, upload-time = "2026-08-21T23:24:13.05Z" }, + { url = "https://files.pythonhosted.org/packages/91/4c/075e4f66471bac101141ac739e9e135549be1bae584571bd03a530c056e1/scipy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2924a03db38dc2e848bca2fe9f077dafb891480b91a00a0963a8cf86dfc31c1", size = 37480330, upload-time = "2026-08-21T23:24:19.608Z" }, + { url = "https://files.pythonhosted.org/packages/39/e7/979fd14e75008623df31ba70d6bb144700f68feadcea042021c06a05bf82/scipy-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:5e4d44984abc0020154ea81b247adeddcc3ac5527b975ff798bd1ba0adc513c2", size = 36658278, upload-time = "2026-08-21T23:24:25.463Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/e1525354ff9d7d5feb6d1b31af6d14072e5c91e9607b421fa1ec889660b3/scipy-1.18.1-cp312-cp312-win_arm64.whl", hash = "sha256:d65d448389b8436493abcf629cc94ad0cf32aecaf06e1acca1de53cc795f2f12", size = 24400588, upload-time = "2026-08-21T23:24:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/4540ee0f9c42a9ad7109d0d1a8cc70de54c3572b01c6693a2b1c70e90ceb/scipy-1.18.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:3ab3523da44749156e1f68b464dc56af11ae4cbc5c739a49d05f32b982eca9f3", size = 31089958, upload-time = "2026-08-21T23:24:35.8Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f5/769f36d14922b8071a43e95d24d18b6bdafad10d7f5cf647867e1ac052bc/scipy-1.18.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e6fb6a55cc0ba97b59a1f288fb86dc6fce8bdfc0fffcbfd015e3a954bf2a2d93", size = 28715106, upload-time = "2026-08-21T23:24:40.775Z" }, + { url = "https://files.pythonhosted.org/packages/9a/d7/21d890274f75ea37a8209d5519e72da3da90302e3b9fb8397a0918386a62/scipy-1.18.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ea324d9dd34c38bfb9bec8ca4d1b407db97dbb74029f566b8e322b1b6fe56fe6", size = 20456846, upload-time = "2026-08-21T23:24:45.066Z" }, + { url = "https://files.pythonhosted.org/packages/ec/01/798430ecea2e78ec7c02663d5f71c007bb6abeca931080debd40d7fa55ea/scipy-1.18.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:75b00eb8fb802090aa903f4ea1c7f5a584779f967361e68b7e98e531cc2d7174", size = 23087986, upload-time = "2026-08-21T23:24:49.539Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5f/4634e9d35c68496e4e34cb6946eafab044458e6cedab42b40b6588e475b6/scipy-1.18.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d416b16cccfd70fbf62400e84d0bb2f4e6af519a45557f1692c749b37f14b315", size = 33998146, upload-time = "2026-08-21T23:24:54.714Z" }, + { url = "https://files.pythonhosted.org/packages/41/48/6450ed9243315322bbc19ac57b9b70d66a20bf1d38d124c96bc4bf6af9ea/scipy-1.18.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fdaf5ea890a6183d0565f51a61799d67081bd5b1cf03c5f4b3fd3732108625c9", size = 35312578, upload-time = "2026-08-21T23:25:00.44Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/bf5a4be6a3525676499f6dff307991739ff6fdcad1481b1aeb6745339f58/scipy-1.18.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c825cef2f49e46753726a7181a8e199804a912b29519ada542c6ebc654951899", size = 35612621, upload-time = "2026-08-21T23:25:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4e/3c45c33e00a77996c4b1cb707929f833ba7b1d522ee29f882512c330676d/scipy-1.18.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e3b417bf8c2c7c16e8f58ad91db17783ec911ac16e7b50eb6eab6e809b4f5b07", size = 37457323, upload-time = "2026-08-21T23:25:12.483Z" }, + { url = "https://files.pythonhosted.org/packages/93/0e/e0348fbc0dbab65c114cf78957e7dfeb49f8e8b556b4d930cc12ff195e18/scipy-1.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:559ed65f60c1af5a03f3912605a1b5114f522c7c32fb23c3376ae8f03219fe28", size = 36622841, upload-time = "2026-08-21T23:25:18.722Z" }, + { url = "https://files.pythonhosted.org/packages/50/a8/6a77f5f267c555108f0a864b6db714363dab567a8266422a79a385f9232b/scipy-1.18.1-cp313-cp313-win_arm64.whl", hash = "sha256:cd479fc04dd9401e3b4f49e76518768ef99c4f517a98c284eb091fd725719adf", size = 24399315, upload-time = "2026-08-21T23:25:23.458Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/d8eb4e280ddb56a4ab2c6f02ee49b56b23f6e977cf0802fd6d68dbef14f5/scipy-1.18.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:83de5453a7799afc9048b4616bd085cef126e36412f0ea2f6370c36a2a3a51e7", size = 31090936, upload-time = "2026-08-21T23:25:28.686Z" }, + { url = "https://files.pythonhosted.org/packages/2a/49/59ea385dc3a62ff498ddf3cfff7c2b41b0f9f9d3c4122b3f1dcb6d6327fe/scipy-1.18.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:9554bcc6d715ee87a633a3cc8e7703c6628b100dd29cb8a2efc4c0533c7ff729", size = 28725221, upload-time = "2026-08-21T23:25:33.244Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/6b0c288c50942d78193696c9f15f9a0874f5178aa0ddf40f83d9924b3e8d/scipy-1.18.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:011413b7426b75012840e35649e00fe0a2c3bae89fed433876e3a99251572efc", size = 20466839, upload-time = "2026-08-21T23:25:37.516Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e0/54fd3793c729e3b936782f181b59cbb1205bf250ab605a16cb1ba61cdd5e/scipy-1.18.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:88f0e784020649f88ea48c9f5ddfa403bf9205820667c0914740b392035afb82", size = 23089121, upload-time = "2026-08-21T23:25:42.019Z" }, + { url = "https://files.pythonhosted.org/packages/0b/56/030af62bea3cf878e0028515dff78c123b01633606a879b63f42d2db99cc/scipy-1.18.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d3ab0e8c69a17dd3559eab8cbb88f258e285c94d572c2719033f90f83290c89", size = 34053851, upload-time = "2026-08-21T23:25:47.998Z" }, + { url = "https://files.pythonhosted.org/packages/6b/89/2a844506d49651e9aa1af6ef95b6bd8031cb1d5a4375edec6155037e04cf/scipy-1.18.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac0333bdf38309aa3dcbe7e3fa7ea29e7a2c37c6ea306a757b700ded8e4596ad", size = 35329183, upload-time = "2026-08-21T23:25:53.522Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/c7370c3640e92ac9613cbf26cb3f729f9b12ddf1727b55b94b53b24d6f48/scipy-1.18.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:911de823097db8b63f034299d12662db93344e6ffa0b881cbb57748974b70168", size = 35672551, upload-time = "2026-08-21T23:25:59.387Z" }, + { url = "https://files.pythonhosted.org/packages/24/16/ec8536f351421f8bf60a1120930638f83790f4710b8230446aca3d6159d4/scipy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:95298364e251be3e60249facbeeca03631d3bb7584f85879516ec55ac717b81f", size = 37469416, upload-time = "2026-08-21T23:26:05.432Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/d73da0d28f16c45bb9b0a5691b91610b0275c5ef0eb5e43c87cf2dc1bf31/scipy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:78a0d7c918e74a232394117160e7e3db503377572a45bcef8826e4ab8a35feba", size = 37362755, upload-time = "2026-08-21T23:26:11.366Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/e996e4dc74e10e227b1e14db5eaf6608bb6dd33884a64851c38f18dd4249/scipy-1.18.1-cp314-cp314-win_arm64.whl", hash = "sha256:cbf38d043c1aa4ab306e1ada6ab6eddacc3322a20b7af1b30bc93254b366fe09", size = 25036090, upload-time = "2026-08-21T23:26:15.887Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c9/c00213f92309d753b48903e6a451b87eb52ff5b7a16e789d1568bbf221c4/scipy-1.18.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0fcb3c93519f27bb4f0c4b0f7802cdcaca7fcf93267b75edda2e9f4e8a55cbd7", size = 31485550, upload-time = "2026-08-21T23:26:20.776Z" }, + { url = "https://files.pythonhosted.org/packages/74/b2/e3067c487982d4eeab2938928529410370c06fea84a4d3f4925e7d96647d/scipy-1.18.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ddef79fb382df40104a19bb7151b3b23e57c1778fcf857c71ceecd9bd264513f", size = 29174642, upload-time = "2026-08-21T23:26:25.395Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ab/374c9fe2d1ec014e576c781a4b5d8e1ba340e8f6b4638c16f711d2b194f0/scipy-1.18.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0e82073ecc7acc6436fac4b31674109c7e1d3e596789767eda01258a8c9e8123", size = 20916357, upload-time = "2026-08-21T23:26:30.112Z" }, + { url = "https://files.pythonhosted.org/packages/90/38/223915c88a17317cafbf8ca2a42b11c265a9fb1e804aa665544132b5fe8a/scipy-1.18.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8bcf3c1ba5d6456e2effd30fcbd3459b044d683fcdac79a2e6830f0bdf7de487", size = 23482611, upload-time = "2026-08-21T23:26:34.846Z" }, + { url = "https://files.pythonhosted.org/packages/c4/d1/db0948da8ca57a80b36520ef0a768b967d99f3af65f4b6f1bf6362ad4dd4/scipy-1.18.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cfbf154f2ba187f2ed6cce2639efff7d105f1140573642c0161615b6d91d6a87", size = 34143202, upload-time = "2026-08-21T23:26:40.4Z" }, + { url = "https://files.pythonhosted.org/packages/87/53/39d046cc7574ed6acacb6bd5723e220107ece80bff12faaf3efc4ddeede4/scipy-1.18.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1d33a7836f7ddc1993427966a0823468ec41bcbdb1a9f9942d1d7e57f803ba3", size = 35380876, upload-time = "2026-08-21T23:26:46.1Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/32e0e799d875a85ca57d9bde6c78148afcc0e38276df683d95854eadc8c3/scipy-1.18.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4b8bc363b6d65ee2152bec57568e3c52639bb34c46057b09857a307ed5e21d", size = 35770885, upload-time = "2026-08-21T23:26:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/88/2e/f97a666d362fee68b18f41c9c30ed502ca5c98b549749bfcb52a8b74d1eb/scipy-1.18.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:11c423f1049c5755ad4409af52a9ada1cff96fe9b50795d4af3619f292901239", size = 37525424, upload-time = "2026-08-21T23:26:56.751Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d5/a9e765a84654ebba8479a1fd1b059ced1af72b168a3b2a3a46540ea38d20/scipy-1.18.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c24acac1e18912761c4700239bbc1fd32f615af690f1584d49b35859be51324d", size = 37416961, upload-time = "2026-08-21T23:27:01.546Z" }, + { url = "https://files.pythonhosted.org/packages/ee/16/e79e0d1c63ef698879d85439d37e9fb434e3b804e506a6991038d086ebd9/scipy-1.18.1-cp314-cp314t-win_arm64.whl", hash = "sha256:9f2897bf7737392ad0d5213ea7b6add72a4edf5679b3153106aeb88b6507b3b9", size = 25331848, upload-time = "2026-08-21T23:27:05.884Z" }, +] + [[package]] name = "scipy-stubs" version = "1.17.1.5" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] dependencies = [ - { name = "optype", extra = ["numpy"] }, + { name = "optype", version = "0.17.1", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"], marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/30/7a2e621918d1317ab972f797161131f2635648ad5d92baf0695dd009e4f9/scipy_stubs-1.17.1.5.tar.gz", hash = "sha256:284b1dd1dd46107a614971d170030d310cd88b2ac6b483f85285ee0ff87720bd", size = 399933, upload-time = "2026-05-25T21:34:33.6Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e1/26/d4bc2ba3427a623f79a6c10c8f427c7a55b56eb8b3eddc369319d97f741b/scipy_stubs-1.17.1.5-py3-none-any.whl", hash = "sha256:58ebf054a86c000c72e8982e121c4ead0d3d9ba7a6c38aa5fa71b07f96a427fd", size = 607388, upload-time = "2026-05-25T21:34:32.073Z" }, ] +[[package]] +name = "scipy-stubs" +version = "1.18.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "optype", version = "0.18.0", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"], marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/99/02c608a58cf99774c577f22e457427f87c8e608652c5de95178daa003e1f/scipy_stubs-1.18.1.0.tar.gz", hash = "sha256:87bec0df883cd9cd6b7dc4c74a33362cf550e9cad679643a29a886ab2b438ddc", size = 448822, upload-time = "2026-08-22T09:22:52.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/11/263ced30149fbede00614a11f01de792842d3636b5a9e3abfee34e764a0e/scipy_stubs-1.18.1.0-py3-none-any.whl", hash = "sha256:fc1f00da3eb6bf1adf2138774b5e6a91dcf7c77039ff199577dc47929c10793e", size = 653731, upload-time = "2026-08-22T09:22:51.43Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -1590,7 +1843,7 @@ wheels = [ [[package]] name = "sphinx-autodoc-typehints" -version = "3.11.0" +version = "3.13.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -1603,9 +1856,9 @@ resolution-markers = [ dependencies = [ { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/ac/99f66f906b15718687525fdf3601ca0b50d19c5e88d57cd4275a89355926/sphinx_autodoc_typehints-3.11.0.tar.gz", hash = "sha256:0112b322e2ebd993c0561af3c9e4615481b42dec199d665d6bacc875f3371e96", size = 82518, upload-time = "2026-06-11T18:48:34.225Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/87/71b5530a4657d5dda36742907d6ba77c63db724a9099eda5e114a62016b4/sphinx_autodoc_typehints-3.13.4.tar.gz", hash = "sha256:9429680faa192fec9797edb9575923177f9d2ab277481d561d4cbd4aeb9cd472", size = 92020, upload-time = "2026-08-24T15:37:44.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/55/7aaa2439e77cff66a6f348bb2d9894abf2b7b153595a5b974c5c277e9145/sphinx_autodoc_typehints-3.11.0-py3-none-any.whl", hash = "sha256:4ab73fe735c33168be3f34818034581155416e8e248d32ea1b604e90bea75223", size = 41610, upload-time = "2026-06-11T18:48:33.056Z" }, + { url = "https://files.pythonhosted.org/packages/bc/10/bc3e16cda1241b45f34bbec5e3728d386e45c831798d0e45da57d2023aae/sphinx_autodoc_typehints-3.13.4-py3-none-any.whl", hash = "sha256:05c9fa3bc312cb576a16a4b2e53459c170e540665687049a0d142a83736f5209", size = 45801, upload-time = "2026-08-24T15:37:43.004Z" }, ] [[package]] @@ -1692,15 +1945,15 @@ wheels = [ [[package]] name = "starlette" -version = "1.3.1" +version = "1.6.0" 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/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" }, ] [[package]] @@ -1777,62 +2030,62 @@ wheels = [ [[package]] name = "types-openpyxl" -version = "3.1.5.20260518" +version = "3.1.5.20260807" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/d1/a1e23040a758746ad5bb6b8849a011c3c901775618bc7e1fec3a1a8b7142/types_openpyxl-3.1.5.20260518.tar.gz", hash = "sha256:da9cd644e4e80215a3f60a8c2c2c8e980e941a9b581cffa3876285aa791ca5af", size = 101550, upload-time = "2026-05-18T06:03:57.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/89/e81814aac1c6ec46ee0006b723254ae737faa68c1ac80a7c3b81b3aa9f22/types_openpyxl-3.1.5.20260807.tar.gz", hash = "sha256:1a0a42b125f8023d3ae83cc057e379d301a87f45e60b6160917824fef28ab015", size = 101740, upload-time = "2026-08-07T04:17:25.557Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/0e/d745ce95fc74e34df802010fd0387e33db468179e6ff42b708280ab268c7/types_openpyxl-3.1.5.20260518-py3-none-any.whl", hash = "sha256:e6ca4b116c8b979ed57f3045edcd3d49c25917d6dae99e90358f41322a19d375", size = 165744, upload-time = "2026-05-18T06:03:56.036Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/c7faba803c8a2e822ccc43a091c979cdbdc28e792e1eb0fb5ff172c81ee5/types_openpyxl-3.1.5.20260807-py3-none-any.whl", hash = "sha256:e64e9342cdac8a2d7b09f992d3606c532b75da43874f8107b6b5a122dc9d5681", size = 165826, upload-time = "2026-08-07T04:17:24.341Z" }, ] [[package]] name = "types-python-dateutil" -version = "2.9.0.20260518" +version = "2.9.0.20260807" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/e8/c01bdf0d7c3659428c091fbd693177093639565bcbc86bc20098e6d37cc6/types_python_dateutil-2.9.0.20260518.tar.gz", hash = "sha256:51f02dc03b61c7f6a07df45797d4dfe8a1aa47f0b7db9ad89f6fd3a1a70e1b51", size = 17082, upload-time = "2026-05-18T06:05:24.508Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/4e/b3fa538f9cb38dfece0d6ccf6d3d0d925bdedb144fb9c8129dfc007cd003/types_python_dateutil-2.9.0.20260807.tar.gz", hash = "sha256:e0b8a90d464c8684c66b7b8e4556d9074afdddcc56ca45323f0987134f9e7034", size = 17618, upload-time = "2026-08-07T04:17:13.491Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/22/169273273ca34e9ab0ae2f387ba72ed7e09faaaf834da01d6b89c2bea71a/types_python_dateutil-2.9.0.20260518-py3-none-any.whl", hash = "sha256:d6a9c5bd0de61460c8fdef8ab2b400f956a1a1075cce08d4e2b4434e478c50b8", size = 18431, upload-time = "2026-05-18T06:05:23.641Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5e/3715867caea2f4cea56ccb04c851cde23ed063449c3b004c7a047f20dd48/types_python_dateutil-2.9.0.20260807-py3-none-any.whl", hash = "sha256:54aa3707350ed7a9cc0776fd2f6739679d6967d11b40150985e81edcb86df4db", size = 18486, upload-time = "2026-08-07T04:17:12.504Z" }, ] [[package]] name = "types-requests" -version = "2.33.0.20260518" +version = "2.33.0.20260712" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/db/51/703318f7b7be8bee126ec13bf615050f932d0179b8784420f3a0199cc769/types_requests-2.33.0.20260712.tar.gz", hash = "sha256:2141b67ab534a5c5cd2dac5034f2a35f42e699c5bf185eee608c5246a069d7fb", size = 25084, upload-time = "2026-07-12T05:14:20.455Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/62/e7/010c87f559e216d83f9dc51e939633fd0d0ead3377340181ab0e223cd3b5/types_requests-2.33.0.20260712-py3-none-any.whl", hash = "sha256:de027e28c171d3da529689cbfa023b0b4eab188c8dfa22fd834eebd2cee6e7bb", size = 21392, upload-time = "2026-07-12T05:14:19.616Z" }, ] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.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" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] name = "typing-inspection" -version = "0.4.2" +version = "0.4.4" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, ] [[package]] name = "tzdata" -version = "2026.2" +version = "2026.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] [[package]] @@ -1846,20 +2099,20 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.49.0" +version = "0.52.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/0f/3f86e61397dd33bf2ccf28188c40db6a740658aeebbbf6e7dbc101a1f487/uvicorn-0.52.4.tar.gz", hash = "sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86", size = 100627, upload-time = "2026-08-19T06:27:41.821Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/f1/79/4a20b54ab0491485ccd8c077db2d39187c7f12b3e15485d38a7be37c81b4/uvicorn-0.52.4-py3-none-any.whl", hash = "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1", size = 79871, upload-time = "2026-08-19T06:27:40.36Z" }, ] [[package]] name = "virtualenv" -version = "21.5.1" +version = "21.7.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -1867,9 +2120,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/60/fc54e876e34f94dd0cf0185aaecfd4bfa906653f003d9b2fb21428642fca/virtualenv-21.7.5.tar.gz", hash = "sha256:a73c4246fba3c8901ff9717399f466e00eeca5a3834981f1a6ebb4f1e94de2f8", size = 5346743, upload-time = "2026-08-25T05:39:16.14Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d8/401141bf45637be916c86d325bd821c5838c7eff83294b934cd94e774e4f/virtualenv-21.7.5-py3-none-any.whl", hash = "sha256:e36ca889510ab6cb0b1dca93c59e5431dd4422a3c88f487358d470c90af8c07a", size = 5324697, upload-time = "2026-08-25T05:39:14.229Z" }, ] [[package]] @@ -1967,59 +2220,115 @@ wheels = [ [[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" }, +version = "17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/72/fba934cb3dff7a85d811820efffcd141ddd52b5a2a01637f64551373ff4d/websockets-17.1.tar.gz", hash = "sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a", size = 187520, upload-time = "2026-08-26T17:25:33.063Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/ad/66a74d42fb537bd44056483eae6cbb7ebb10b742c300a0bf8cee427556d4/websockets-17.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:88b882764ef65147a7a5ae13168dedbe225a04e2ff4858fe543f2c402f093e9c", size = 216984, upload-time = "2026-08-26T14:55:20.747Z" }, + { url = "https://files.pythonhosted.org/packages/70/1b/344ab22cea729e872f759b926441f7b822ab6cd106db527736afc066927f/websockets-17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98a5b2589a56a4b4f098b0a958099a4356ab904a7844f1da3841efca469af7e9", size = 214667, upload-time = "2026-08-26T14:55:22.298Z" }, + { url = "https://files.pythonhosted.org/packages/2e/42/bace574b6ae80e1a8d6935b8c5f03fb67236233ec572e976fe826ff719cf/websockets-17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:020e271205f8ab3406d7a59cd00de6dec722315924411c421bd00642f18bad86", size = 214944, upload-time = "2026-08-26T14:55:23.618Z" }, + { url = "https://files.pythonhosted.org/packages/ee/87/08e35ca4a0ffafb500a16ff461bf9561ad2b755362adb5d077d4dba9affc/websockets-17.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:65be6bda2b537fefa4b3a5ccd6ab386533ce39dd8fe62433ec90901fdc81752d", size = 224004, upload-time = "2026-08-26T14:55:24.748Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/00ae2e147eaa086fe8bdddd36f57216ce72b9a9dfc0b17c717005ebdacaf/websockets-17.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c84bdef916556cbe1d5a43b423398be4dd3cba6522b463e53d848578b920695", size = 224278, upload-time = "2026-08-26T14:55:26.016Z" }, + { url = "https://files.pythonhosted.org/packages/98/e2/7aeb4e00defa68826f449392922a382ce7fdf542fe52190558dc1714e284/websockets-17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47a62d6045c6eaa0d8f97bc2fb68b8cf90077a0cbfd4e83d6f2d2145611ee134", size = 225511, upload-time = "2026-08-26T14:55:27.183Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e4/655be3d93c3edbe1a51606073b5454ea6b1b32d87aa26253a6df952417b7/websockets-17.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34879e19bb0a3c44f9317679435aea5327fac993933a704cbf353bf1234b10c7", size = 228802, upload-time = "2026-08-26T14:55:28.431Z" }, + { url = "https://files.pythonhosted.org/packages/e4/33/98549a2afa9d68fe1b5a8e0a61cd461a43ea1ab7209bce675eea67c79190/websockets-17.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d72879819f5145a342d0030c418702496c65a4b913ef81f5ae944dd91dd50f6", size = 226075, upload-time = "2026-08-26T14:55:29.695Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6b/cbc27e014d6c292b9b2709cfd32781a2b61eb30cd4c9130e7c57e41a204a/websockets-17.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f25e099fdfe3b09f953d84698f729a1f7d1e99101b2787d7a28ed77b323750", size = 224846, upload-time = "2026-08-26T14:55:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/10/a6/e57925f7a423d90f24559e85bac21a7f0b44c0cf4a5c0babc0759ca54bab/websockets-17.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:469355ab1af100b9380f1afb09985019f4a4b94fa1dd0e9396db4361626d7ab8", size = 222136, upload-time = "2026-08-26T14:55:32.376Z" }, + { url = "https://files.pythonhosted.org/packages/32/07/b9de0400addb542ba7c819022abc3afa46cd7e518068881bceadac69d995/websockets-17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00679b7468b4c2b12b0757118174e8eabac56bb2f579a928a104d9554a56e098", size = 225000, upload-time = "2026-08-26T14:55:33.527Z" }, + { url = "https://files.pythonhosted.org/packages/4c/bb/af2828a1d7f2beb792af6ba56d7b02d56262070266b95d2af9ef391fbfb0/websockets-17.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a9fe648abd1d9b89aebfa30407bfdd08a0271ec5dc7d44a4c6ccd1ce22cf562a", size = 223592, upload-time = "2026-08-26T14:55:34.636Z" }, + { url = "https://files.pythonhosted.org/packages/fe/51/7379f254730c1dc7d8e4dd8d686868d8f7be55bdc94c6d3a44538840f639/websockets-17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f47aafd92aa28b941180e6da8a42b0f711851b14b81a5b6bb28dbbb1fa35152c", size = 224360, upload-time = "2026-08-26T14:55:35.777Z" }, + { url = "https://files.pythonhosted.org/packages/88/c6/fcd91320dd71dda7046df9bc60f60c70ee15c052dee21e31ed6221dc8b5d/websockets-17.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c89406fa3dcd4aa8662c6406cc5c0de1790e9614d2c3aaf03ca53a8a8ccf3405", size = 225404, upload-time = "2026-08-26T14:55:36.85Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b2/655a4f939388079f80f1b3f8a1b9d40783e70a376e7423988cc9a590a09f/websockets-17.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b3b451fd2723ad3191a209afe6f3f4bc86c83e9a85bdc255353b91803ee6aa66", size = 222982, upload-time = "2026-08-26T14:55:38.006Z" }, + { url = "https://files.pythonhosted.org/packages/87/75/37c84c4371c6aa668910d7841036c4397ea10554c07030603ccd5e44b02a/websockets-17.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:054c28db2dcec0e857e3b705d8c28012613e555b38c765d6a4f75340a4fc06a0", size = 224017, upload-time = "2026-08-26T14:55:39.39Z" }, + { url = "https://files.pythonhosted.org/packages/5e/20/8a9a94323bfcfe03bde3f9d98926bea4855856359702fe3ca0d07051ef5d/websockets-17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f8e822efd54137d8cc8310eb64635ab827a4a6c72ff08691f38aa624776d8ecb", size = 224252, upload-time = "2026-08-26T14:55:40.498Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dd/aa66e6500188cd40306abeb92c9a738ca6dd7029d8d8532c538055ab5daf/websockets-17.1-cp311-cp311-win32.whl", hash = "sha256:dcb8d5f7edef7a399d322cf28d4c4e6f98dab64d301c8f50581a1080e5198142", size = 217484, upload-time = "2026-08-26T14:55:41.763Z" }, + { url = "https://files.pythonhosted.org/packages/01/a2/cdf3b551f0b9177023afd3a45d3b431a0d4064951008c4321a8b42ac2288/websockets-17.1-cp311-cp311-win_amd64.whl", hash = "sha256:b1bc819c6db90e8f91a38250a1ab4c058261871aa52d2fe36382eddedf146dee", size = 217779, upload-time = "2026-08-26T14:55:42.942Z" }, + { url = "https://files.pythonhosted.org/packages/e0/13/51253dbed7d16a4bb87b05110ad3bf12165f410e915f6da1edd4186d8dc1/websockets-17.1-cp311-cp311-win_arm64.whl", hash = "sha256:edadce7a22052056fd4384543019856b34850363c9d387929f677ae01d79709c", size = 217710, upload-time = "2026-08-26T14:55:44.016Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0d/098f23c4c858e5de9459ffc554fa07d5493fbcfca7f040b5800cf1cecc35/websockets-17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3", size = 217015, upload-time = "2026-08-26T14:55:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/13/86/bc1317b1a4d8c4688e2a7e564b5e004dab44c2534d7ca05de6ae9a863fca/websockets-17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:581fa678ef46f4277cc8491312468e582f8ad609dbab907ba6096a08c6a0ff98", size = 214692, upload-time = "2026-08-26T14:55:46.366Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e7/df821761772beaa48c211ee0e234930b35c1473778470773823f56d3911b/websockets-17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87f0d5e77548b0c40c8464cdb6108792e7e53f487c6400028a4ec28a8afbe5ab", size = 214959, upload-time = "2026-08-26T14:55:47.885Z" }, + { url = "https://files.pythonhosted.org/packages/3e/92/c3fb72f11764812fc648bf3838d224972427b348e8b3989d9e0a9df87da3/websockets-17.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:882af300d2c6a092b93767d5de03c7bb56dfb06314140c8e872d3f48e09f7b74", size = 224278, upload-time = "2026-08-26T14:55:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/fb/05/9f82d090c8d2d861604147ef6dfb938a90b039f9358d5193f1df62558593/websockets-17.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c863507ada5805517ca6dff1c524dcd42942efe6304dacf06700878398d21a6", size = 224557, upload-time = "2026-08-26T14:55:50.348Z" }, + { url = "https://files.pythonhosted.org/packages/8a/50/5cbf677b865290fe36819ff00615826e7edc1df38786f770123ff39a933d/websockets-17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d41ef69d5416fbc1d98cf96c37be6192d10fd101c3e0f8b3ddc36e09432b3c08", size = 225791, upload-time = "2026-08-26T14:55:51.75Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1c/eb8a032285243381b09a221ae384c972d5000453ad136add4d1595cec798/websockets-17.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5aefe78e6a3077fe22b5e64b04666a85a3eb8b934d40e8595a693adcbceb6f11", size = 228574, upload-time = "2026-08-26T14:55:52.922Z" }, + { url = "https://files.pythonhosted.org/packages/69/85/413736251cb3ac04ce84cbd90e893d9a36a9698d4820b323aff3aa187e50/websockets-17.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f64e001bb7fa89b9f32cfa600bf8e9ac8ca26759d9b92ae01453ee303d9cd7b4", size = 226428, upload-time = "2026-08-26T14:55:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2b/a08bcc7fa1ca81a10f84ba32b6e6edd73a913f4b0c2640eed1fd626efacd/websockets-17.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:677014a073bcb1fbaa7e21144786864f16c08f856d66834f611eceb9006cbab8", size = 225184, upload-time = "2026-08-26T14:55:55.943Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8a/3bd2d0cf6b148c8c866d5d9fdcde30c04bfd81fdfac86813e69377eb4448/websockets-17.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0de501b7f2db11e83739ac20e2d33d46da4604b829f506c24be80e7def069391", size = 222430, upload-time = "2026-08-26T14:55:57.103Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c9/8e891ae342668735eabbbc669895e15195e4b45f24a4beeb58af76f414c7/websockets-17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f62114a54117e4948a1e414e89521f7fe1e3c2f83f2a571a06a4fc6718b0900a", size = 225227, upload-time = "2026-08-26T14:55:58.375Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6f/c816f332dca11425e9bda7c07f7573eb5c5f8a735849d02b0d81e8ee20fa/websockets-17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eec113a5b41d124ef42ff56b0d74a6da3fd986400038eab9e58ee42a4024e837", size = 223831, upload-time = "2026-08-26T14:55:59.664Z" }, + { url = "https://files.pythonhosted.org/packages/53/67/5e91d5308ce24fc1ec74f56536c12f4888bad45ff5ea50f3180f8c518c57/websockets-17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5f051f8030a51815dc00e24bd2e5f1435af095c1cc111d747ac6e2a3620d7641", size = 224600, upload-time = "2026-08-26T14:56:00.873Z" }, + { url = "https://files.pythonhosted.org/packages/bb/96/faa298ecf2570d35b0eb37caddf4992178d907e108ed74bfffb6bc092c29/websockets-17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:655a8e28010f09fd6fa317e857afab3af7647f33e41dee88fa421e92086d1090", size = 225707, upload-time = "2026-08-26T14:56:02.001Z" }, + { url = "https://files.pythonhosted.org/packages/0b/12/5710d2482ca5061c1eec5eb46f6313837c760d4115b1795c85b6c08be4e3/websockets-17.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dc2b79afc074d2f3e64b26539350f697fe1b85ea1c49ea24eb588f247b053ce1", size = 223263, upload-time = "2026-08-26T14:56:03.092Z" }, + { url = "https://files.pythonhosted.org/packages/27/47/0c30f4eebfd1d93fae779d268f678d48847fb98516f5200849574eee8820/websockets-17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e4bd7eacb87d8cf3ed70d6392c770a0d92441f05d7d2a3efafb5bc171d5e3067", size = 224244, upload-time = "2026-08-26T14:56:04.321Z" }, + { url = "https://files.pythonhosted.org/packages/41/33/46c256195a1255079ae23d1b1267b2e1843dc5f46a67f973cdf2a3523dff/websockets-17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ccbf3f4a9890d50b3a08ee04029fde30a03bfdeffaa19977628bf17251764e60", size = 224520, upload-time = "2026-08-26T14:56:05.521Z" }, + { url = "https://files.pythonhosted.org/packages/06/9a/aef0792731df4352e5f417369b532b3325fe434765ca90c193f594ae1e67/websockets-17.1-cp312-cp312-win32.whl", hash = "sha256:7e724f843fa6a0614aece65a7c73e51d0f4412ca41dccac13c3caf98e69536bb", size = 217485, upload-time = "2026-08-26T14:56:06.715Z" }, + { url = "https://files.pythonhosted.org/packages/50/23/493ecfdaf32898e5ea24dc900e33e5e317f9662d5d9ab2d44b2e111b4e1c/websockets-17.1-cp312-cp312-win_amd64.whl", hash = "sha256:617243e19a0992095956f406ee9cd3bc4ba92862d83cb1d83bb59ce574412bec", size = 217786, upload-time = "2026-08-26T14:56:08.055Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/91954e2f7876f74ce1213e9b92c65a63b559cc4b942a931ebeb351cd9932/websockets-17.1-cp312-cp312-win_arm64.whl", hash = "sha256:9f4a08ff7cb68c27b18e09223cc6304e01d0f82d5a240d251266dfd2e6e44729", size = 217711, upload-time = "2026-08-26T14:56:09.267Z" }, + { url = "https://files.pythonhosted.org/packages/1d/31/5f6450a7879f4f063ef08897cc385ea3ce3f1fe17f08b11e3fd959abdf27/websockets-17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e", size = 217006, upload-time = "2026-08-26T14:56:10.509Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2a/c1b006fc861695d2aa4e35327b842015ce1d98cf8f99241829b3d6460bfc/websockets-17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc", size = 214690, upload-time = "2026-08-26T14:56:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/46/69/66e5b7d01445e0eeb1d4ab419c30315f2c90cf7a8a8cd4ecc47f894dba54/websockets-17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80", size = 214947, upload-time = "2026-08-26T14:56:12.923Z" }, + { url = "https://files.pythonhosted.org/packages/07/ce/033cafe2d2538562efa876b9149a2c7a0f7787870a4b1bb6e28adc9ceb6b/websockets-17.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce", size = 224329, upload-time = "2026-08-26T14:56:14.212Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/e1c2e8a67f6cc0aa43abe0046fb3b7a020980649e6a843751dc7ce9eb170/websockets-17.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51", size = 224611, upload-time = "2026-08-26T14:56:15.702Z" }, + { url = "https://files.pythonhosted.org/packages/be/de/07c6d48eb3d2069709410c851e7de10ab83d752c4bd09862899627c2729b/websockets-17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c", size = 225848, upload-time = "2026-08-26T14:56:16.962Z" }, + { url = "https://files.pythonhosted.org/packages/f3/dd/3c68572d20509648cc2fb6f50ccf3deeb4b87270f2c8966e99476e278ea3/websockets-17.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3", size = 227290, upload-time = "2026-08-26T14:56:18.204Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4a/8f6651c8a22093539c9215af0c5bbf217b87b382c99d2112039b92d593c2/websockets-17.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435", size = 226476, upload-time = "2026-08-26T14:56:19.459Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/f6fc33cea86b1127fd1297b18c107e81580ab55a73a39f9a934441ef321f/websockets-17.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906", size = 225233, upload-time = "2026-08-26T14:56:20.939Z" }, + { url = "https://files.pythonhosted.org/packages/cb/83/65edaf05f7c9b1dea82f4d252fdc37706a84571646f06119a27b0a16fe19/websockets-17.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c", size = 222488, upload-time = "2026-08-26T14:56:22.208Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/d1169c2f7f1f0032b0d4b0c00f0711a070cd7c735de37bfeb876bc0f9606/websockets-17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780", size = 225295, upload-time = "2026-08-26T14:56:23.445Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f4/64e2a386c3899b917c2933225c9b47887874229d159797f3bf1a11c20d51/websockets-17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c", size = 223891, upload-time = "2026-08-26T14:56:24.647Z" }, + { url = "https://files.pythonhosted.org/packages/26/b3/dfb5c482f7e310a3432fdbb045ddfe6d34114680e89a233d4ff900a32961/websockets-17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad", size = 224661, upload-time = "2026-08-26T14:56:26.027Z" }, + { url = "https://files.pythonhosted.org/packages/a4/cf/94865130a336029f46412adc127c4fbe380f46172b90ce251369e35c4302/websockets-17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c", size = 225766, upload-time = "2026-08-26T14:56:27.455Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/eb8c658f86dfe562ed49a887a27424bfe9e618c26ea6f865b093d075d3a6/websockets-17.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a", size = 223323, upload-time = "2026-08-26T14:56:28.807Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7e/2629609652ece5ca0c7ac235927dd4511b08131e3a5d53439b798fddf002/websockets-17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0", size = 224276, upload-time = "2026-08-26T14:56:29.991Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/8525737fe840b38e5f40956c198fb586a4fac1e07144d41a5b949b989cf8/websockets-17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d", size = 224558, upload-time = "2026-08-26T14:56:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/3a958c6cbcf74b118f601c20a80ac8bd5e8dfec0bcf7345116feaeefb121/websockets-17.1-cp313-cp313-win32.whl", hash = "sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608", size = 217475, upload-time = "2026-08-26T14:56:32.431Z" }, + { url = "https://files.pythonhosted.org/packages/22/36/fb521f0f2994c25509651f169efe5582dddd8713d57a0757ba87859372ef/websockets-17.1-cp313-cp313-win_amd64.whl", hash = "sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f", size = 217784, upload-time = "2026-08-26T14:56:33.608Z" }, + { url = "https://files.pythonhosted.org/packages/68/92/9b8419584681a12a7534b746dfb2737c466efe2455483e2fbf8b941a04ec/websockets-17.1-cp313-cp313-win_arm64.whl", hash = "sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2", size = 217715, upload-time = "2026-08-26T14:56:34.865Z" }, + { url = "https://files.pythonhosted.org/packages/90/0d/500cf5daea09d4669dff3a7d67159094a0bd6c4ef130381404f6edd3eb5f/websockets-17.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0c9982938980e086da59f70d05f9418cd143401a601a0faac10fa48f7bb1cd3e", size = 217048, upload-time = "2026-08-26T14:56:36.03Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/5b12c6168aa269cffbfd24d177cd492b130120403a418c7e89462e27b4ac/websockets-17.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:57b39dc8541cf7ed3f639da82bf7451060483967f9e733da1f8173e4095f0642", size = 214737, upload-time = "2026-08-26T14:56:37.43Z" }, + { url = "https://files.pythonhosted.org/packages/0c/36/e453e5106e4e2416f008ac222837c2f1637a063b08008afcd1088889b631/websockets-17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:96abdecbaae746851b87c3a36cb4a661df93ca3d92f114270f79228bf1d00de6", size = 214955, upload-time = "2026-08-26T14:56:38.71Z" }, + { url = "https://files.pythonhosted.org/packages/dd/30/0204bb86176db02cdfc678ce65ed808a66fab87d250ce61a8790800a60b0/websockets-17.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9fc873e239c5abeb150bc24dbd1a7af23a9254526383ce0a077f5e20adbeb19", size = 224331, upload-time = "2026-08-26T14:56:39.924Z" }, + { url = "https://files.pythonhosted.org/packages/46/c8/d8372256e00c4e3cab1115c45075d1eeedb642a3f2b42bd70c4deae03f06/websockets-17.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f42912fa9eb4cb7c7ec9fde9b3332ba339eb8a8811981043d4029599f3d950b", size = 224685, upload-time = "2026-08-26T14:56:41.169Z" }, + { url = "https://files.pythonhosted.org/packages/12/7d/650355b8f67f908ff99603351d4458d1a0b787d627950a47c38db7e25308/websockets-17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f98bf378d7a5be047a044a1a27c987a8f355e10e3b5754617dbe756248cbc5ce", size = 225927, upload-time = "2026-08-26T14:56:42.359Z" }, + { url = "https://files.pythonhosted.org/packages/34/6c/a9ffa5b903579eed76017870f055d75ecc73988d9d0c9b65a92ba0bf2a27/websockets-17.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d334d11398086bb5559606cb42d51c013ea7c061c7db701521392373d3c087f5", size = 227300, upload-time = "2026-08-26T14:56:43.538Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5d/4551c2269066af7481ee44605a0813770961615b5b5da3e87a8f5cb859ea/websockets-17.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c27336b1a0ac56569493e858497870347854372395f50483725f8cdacc5a45c", size = 226533, upload-time = "2026-08-26T14:56:44.669Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/237a99233e5c445759a613831b3a92e91905afc064dc3bd0ad33c35fd1e2/websockets-17.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67258b00302a5aaf0b267771c7014b13429abd7ea17eebc4c55bd935ff101555", size = 225280, upload-time = "2026-08-26T14:56:45.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b5/e9407a91613d1d1cd932414143a1012096b26674a782fc55a0bd23217ee4/websockets-17.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:455ffeea0879d313205df1e745e5883e1feb7f31ecd26be882f5f0babd3db04f", size = 222540, upload-time = "2026-08-26T14:56:47.053Z" }, + { url = "https://files.pythonhosted.org/packages/db/d2/db76628db0577b783205d9779f64d8e373416b04c62d1546be4b75dc8540/websockets-17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f7233eaf441a345a5943a929fd4b5ea3278f11aed35a9ed0f3106b8cb3ca846a", size = 225354, upload-time = "2026-08-26T14:56:48.32Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4c/2174181c067b89a74ae18e2650c2ac29959f4b796afe876ab3f4d30d642c/websockets-17.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c65da239a5ad553619804c1f9d65c1a0b3005381c6158ee14da2c7444cbd0c78", size = 223867, upload-time = "2026-08-26T14:56:49.579Z" }, + { url = "https://files.pythonhosted.org/packages/df/75/274decb9a8253561b5be3261e02a6676fc8ecdf31e95b722e53d5bfb8fd2/websockets-17.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9fa1ffa08c81a4f809cdab6129f8e55bee4650b9d6d3461019dda73aacd146b6", size = 224652, upload-time = "2026-08-26T14:56:50.885Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e6/49824f1fb4db7656d2f7492b1d8be16147b759d909490e32f4776843ee64/websockets-17.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:406b8107943a43ef4649b1e0cb0cdc052bbf08fe1c8905a623c4af9586e5cebb", size = 225822, upload-time = "2026-08-26T14:56:52.356Z" }, + { url = "https://files.pythonhosted.org/packages/b8/6a/5dc43838c0b02a95f42c47a0de33c5ddd7767a9feeb4d0d8777ac1cfefe4/websockets-17.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4e8ffcb486c8490a34a4cef5e4409d8da5a1cb1681e5bf7d786ce5e84aa8540d", size = 223379, upload-time = "2026-08-26T14:56:53.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/62/585637cf06d6b321232f79c55dc14d65518d12cf87c94c44f5864068810e/websockets-17.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fb88076df585b69c5761c387c0081aa87d7b9eb1b205a6535ca4777e25650d81", size = 224330, upload-time = "2026-08-26T14:56:55.184Z" }, + { url = "https://files.pythonhosted.org/packages/de/68/c3b234a6a1366b6ab5bbfaa4434a1b946e1dc4e8ddd6824bfd93a8835b7f/websockets-17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5d4724255fb8398acd9e583b97eb2279cec20e0bd0f9a94bf75f6056ef9f13da", size = 224622, upload-time = "2026-08-26T14:56:56.393Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d4/84cf3d1376f5d8207f55f43c1c818babd6b89447f5dcd01f18a6d5526796/websockets-17.1-cp314-cp314-win32.whl", hash = "sha256:be3f0129c5654517b2abf07dcb75bb1d9479759a4ccfb569e8293579e9fc029a", size = 217036, upload-time = "2026-08-26T14:56:57.652Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0f/9e7ac63c5d7cb642952200814f584318e65146df008b7d375d5d9c6b2c97/websockets-17.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a4dc6ef83f4559e0d05f313a375cb38f63c986096a9da99fe94fdd779d313e5", size = 217382, upload-time = "2026-08-26T14:56:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/1ae6b91f7f3ac05f5c9f14a72dc2181c115ff370bcd8a7f10f02c174adfd/websockets-17.1-cp314-cp314-win_arm64.whl", hash = "sha256:46c0331c9eaaf73a559f3a9e388466be0df96eb83d40f06f1ca6ab6613b35c82", size = 217268, upload-time = "2026-08-26T14:57:00.654Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f0/f65644d0e0b2b90918a8c41503841cc4072a58f2bf76c09bc36e751fc0dd/websockets-17.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d411ea5ca18ac1b12c0c94be88b60c18ca641ac43bcdfdf1c9f79d46cdbe1603", size = 217379, upload-time = "2026-08-26T14:57:02.181Z" }, + { url = "https://files.pythonhosted.org/packages/ff/35/4c46d1f620ac1a30f92b6eae78ee40a772a93f568647ca7ccdc5ea283cf8/websockets-17.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:07fa3e7c30e2c577928d359b56bf872a3e0cbcc15553eaa0907c1ee86344b56f", size = 214911, upload-time = "2026-08-26T14:57:03.478Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/4587e8406d7c1188e97b9cf466c081e93399380d447f885bfce81626cd37/websockets-17.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de9acef07e3a78e9567fcd26c29011a4da8f050b13004bbf880a0fd82a6eea5", size = 215115, upload-time = "2026-08-26T14:57:04.692Z" }, + { url = "https://files.pythonhosted.org/packages/ec/06/1381c8fff525041025909eb80ace32489194a00ba22a0a8d428030afcc84/websockets-17.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ea0ed9373b880115911d9d39634bccc95b8ce590c9c42e8589f5cacc3ef3cee2", size = 224696, upload-time = "2026-08-26T14:57:05.899Z" }, + { url = "https://files.pythonhosted.org/packages/36/9d/9034e867dc85340be058619751742b895f722326e83100d110063461ca07/websockets-17.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50903d335bfda026c2fa11dd9aed09d8cbee0c451e3a85122a9acb041b7dc69b", size = 224975, upload-time = "2026-08-26T14:57:07.262Z" }, + { url = "https://files.pythonhosted.org/packages/40/eb/ed03aa3cae748ebf6397e5d44028f433f746bad09dc568ff754fda3a3c9b/websockets-17.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a74531ce81af587f906ab42f194032388fcff8fc7938402e5917c9147a39441", size = 226151, upload-time = "2026-08-26T14:57:08.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c9/cc1964a096d16f3b73cb1ee5f14f277f5a3bcac07c6e8f9a1dcded99f4c8/websockets-17.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8fbf28e639544503b7d1c96452a5e5e043e4108d89b1f3fa02910603622d19db", size = 228292, upload-time = "2026-08-26T14:57:09.846Z" }, + { url = "https://files.pythonhosted.org/packages/1a/26/46da6dd0363c2db2e4876fd59a40fd40c1943a82d7018d0a33afbce47d52/websockets-17.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f612dc57f00c07cf4aa2673f7cbceabd654ad2457b7e639f061b794d6e11f9fd", size = 226722, upload-time = "2026-08-26T14:57:11.118Z" }, + { url = "https://files.pythonhosted.org/packages/78/98/ecd8f5e1c5d0e54c08ebc5c66852271112166db68107cb0e17ca1bf25009/websockets-17.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c7ac77401227212dc6e849182feee50d57cf456ec6329ffd6979c94bb136c5c", size = 225451, upload-time = "2026-08-26T14:57:12.601Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/da8d2760db53e17aae763738b6ba834b1fcf16813d3632f3edb6951e1ec8/websockets-17.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32a2a68d989d6e5b74a9d5095415c51189ebae29fceb7cf2b64a1c0318a81256", size = 223003, upload-time = "2026-08-26T14:57:13.875Z" }, + { url = "https://files.pythonhosted.org/packages/a4/40/ea401c141a79c5b1d0021a0dab9d0df2051c108f1620fbb39a6e7c714c3b/websockets-17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:aec00f018d34c67500ff0438dc314b40277be4a1b983cbacbf53ccf7db63e257", size = 225704, upload-time = "2026-08-26T14:57:15.091Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8e/07ab3f44215d89840d5385fdcaaab1fed8caeffa67c6899e15062957c12c/websockets-17.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0014eaff8ad5b3b43feda2279f9d34bf2eaae040720b9fbbb55944b10f40b14d", size = 224192, upload-time = "2026-08-26T14:57:16.3Z" }, + { url = "https://files.pythonhosted.org/packages/58/93/ccf1af0a23e5748d4e22292a377d78d15cf294d7e707bbb11a8990ae6bd5/websockets-17.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:db9d7ee47f3ba531e278be539af39e2c7c7d28fb94897b6cd1120d63b0ef5922", size = 225082, upload-time = "2026-08-26T14:57:17.531Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/e32200f99ce282e728d2929f2c429db353cf3282db7d0eba99eb32c9fec1/websockets-17.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ff3e2ba7a9f0a110b0555452e9b5a03a34e11662544e01beea15f144b48ba7b7", size = 226101, upload-time = "2026-08-26T14:57:18.802Z" }, + { url = "https://files.pythonhosted.org/packages/28/3d/e7a6e9777b29433620167c98f3caaff0d6b08b1239a273ef7f7fd1393349/websockets-17.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6da17fc94bd270f5987b10bee113461ac36a36a98b0481ddcc98056e5a90001a", size = 223794, upload-time = "2026-08-26T14:57:20.313Z" }, + { url = "https://files.pythonhosted.org/packages/48/05/ac569090726dedd6656f3ee28b0c02dfb1ba76e898dceaccc2987a237cef/websockets-17.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:e8dc3fa6d6b7ead3f9de57895f41b116a28787548e066365d9d90f7356bcaad2", size = 224567, upload-time = "2026-08-26T14:57:21.634Z" }, + { url = "https://files.pythonhosted.org/packages/14/50/4ef62941111db6b31193f4fabbb65f845a5177579040cb8fe0d774d25034/websockets-17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b65d5fe48219dc2d5e158de9e6514e75600f379cc7e37108d35f31764c155566", size = 224993, upload-time = "2026-08-26T14:57:22.86Z" }, + { url = "https://files.pythonhosted.org/packages/28/42/2b95ada4ea19bf3a2072b68669ce4f4afb212690b727d31640576287fd68/websockets-17.1-cp314-cp314t-win32.whl", hash = "sha256:2cce251f3e2469b99b6802b55435bcdd07123b41870f54c87b336183af9d7e68", size = 217168, upload-time = "2026-08-26T14:57:24.466Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/67d5ee08dd8060a37d612fd40a625b5376ad19ae48fe1c8ad428c278b817/websockets-17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f6c38cdcaf98a911d7acc25577f2f9e710f3a2fc2bde1563556784320196b51", size = 217508, upload-time = "2026-08-26T14:57:25.983Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/822005d0c674451d2411027b878cdc128a2b7ea5a30d337d9e279da22eba/websockets-17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d1e2f5fa2b6d01f0d85b4f223fea7ed1d504be282a02a81bd2be4817ef7a2f03", size = 217425, upload-time = "2026-08-26T14:57:27.324Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e4/af4abbcf07eac6a725ec6f865611526b2b0c23d482723de551bec667880d/websockets-17.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10ecb38ffc05e1841b619d99c725307a223ef9ad58e7b1ed33311d472dc43918", size = 214602, upload-time = "2026-08-26T14:58:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/4d/fe/819fba7ba35f92b639333da7355041c07dd50048f9c76fba0b8e292a6483/websockets-17.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17aa424ab61620aad21b36b2240efc87b500cc496e7d0e999a5c2ae99395e886", size = 214874, upload-time = "2026-08-26T14:58:26.689Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a7/d370ab794f47fbeea648d17ad08caf0bb50131d6c04b7ad83e6af63c405a/websockets-17.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:764cf7bfa149365f32b7a0fd9fed32debdac29dd06295d5635cde1745b446cd8", size = 215821, upload-time = "2026-08-26T17:25:23.616Z" }, + { url = "https://files.pythonhosted.org/packages/9b/6b/251b00fe634e2a9c2cb5d6390e0e97cec55e3d18dd09b4b976620eed5d7b/websockets-17.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d1b108bd8f5f6a8b90801f6db3b3858d5deca889acfdb8ac497bbb24e4b0edf", size = 215714, upload-time = "2026-08-26T17:25:26.295Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b1/37fe0c96c206b4208a072c3a74add6a72af4b8228be3f5435163c5a6d099/websockets-17.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a62d8c424383c9dc769ff3672018df822603117e32686e567d452ed035b6fb2e", size = 216608, upload-time = "2026-08-26T17:25:28.134Z" }, + { url = "https://files.pythonhosted.org/packages/be/7e/75a0a491b512412e08333b9f8412757af6186fe1c598186261002de1a793/websockets-17.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8196d217eeca52b9235ee1f8a684a09885a5f953d5a31e80ef915bf2c5c94f9d", size = 217870, upload-time = "2026-08-26T17:25:29.745Z" }, + { url = "https://files.pythonhosted.org/packages/41/63/23572870e01836a98346075b9e17a8bc24a6ddd9800a3204ceee58677f3c/websockets-17.1-py3-none-any.whl", hash = "sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23", size = 211134, upload-time = "2026-08-26T17:25:31.397Z" }, ]