diff --git a/.github/workflows/ci-on-pullreq.yaml b/.github/workflows/ci-on-pullreq.yaml index 0c4b413..3f27d11 100644 --- a/.github/workflows/ci-on-pullreq.yaml +++ b/.github/workflows/ci-on-pullreq.yaml @@ -5,6 +5,8 @@ on: [pull_request] jobs: test: runs-on: ${{ matrix.os }} + env: + MPLBACKEND: Agg strategy: fail-fast: false matrix: @@ -23,8 +25,9 @@ jobs: with: python-version: ${{ matrix.python-version }} architecture: x64 + - - name: Install + - name: Install poetry run: | pip install poetry poetry install --with dev,test diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py index ade0632..54315ef 100644 --- a/benchmark/benchmark.py +++ b/benchmark/benchmark.py @@ -65,7 +65,7 @@ def sizeof(o): def benchmark_pfline(df): for le, ru in tqdm.tqdm(zip(LENGTHS, RUNS)): - i = pd.date_range("2020", periods=le, freq="15T") + i = pd.date_range("2020", periods=le, freq="15min") for cols in ["q", "p", "qr", "wp"]: print(f"{le=} {cols=}") data = pd.DataFrame({c: np.linspace(1, 1000, le) for c in cols}, i) @@ -99,7 +99,7 @@ def benchmark_pfline(df): def benchmark_pfstate(df): for le, ru in tqdm.tqdm(zip(LENGTHS, RUNS)): print(f"{le=}") - i = pd.date_range("2020", periods=le, freq="15T") + i = pd.date_range("2020", periods=le, freq="15min") w_offtake = pf.dev.w_offtake(i) offtake = pf.PfLine(w_offtake) unsourced = pf.PfLine(pf.dev.p_marketprices(i)) diff --git a/docs/core/interoperability.rst b/docs/core/interoperability.rst index dc9e9bd..dc1e47a 100644 --- a/docs/core/interoperability.rst +++ b/docs/core/interoperability.rst @@ -19,7 +19,7 @@ In the code examples below, the following imports are assumed and variables are import portfolyo as pf import pandas as pd - idx = pd.date_range("2023", freq="AS", periods=2) + idx = pd.date_range("2023", freq="YS", periods=2) --------- One value @@ -82,7 +82,7 @@ For timeseries, ``pandas.Series`` are used. These can be "unit-agnostic" (i.e., # --- hide: start --- import portfolyo as pf import pandas as pd - idx = pd.date_range("2023", freq="AS", periods=2) + idx = pd.date_range("2023", freq="YS", periods=2) # --- hide: stop --- pd.Series([50, 56.0], idx, dtype="pint[Eur/MWh]") # unit-aware # --- hide: start --- @@ -103,7 +103,7 @@ To pass several timeseries, we can use: # --- hide: start --- import portfolyo as pf import pandas as pd - idx = pd.date_range("2023", freq="AS", periods=2) + idx = pd.date_range("2023", freq="YS", periods=2) # --- hide: stop --- {"p": pd.Series([50, 56], idx), "w": pd.Series([120, 125], idx, dtype="pint[MW]")} # --- hide: start --- @@ -118,7 +118,7 @@ To pass several timeseries, we can use: # --- hide: start --- import portfolyo as pf import pandas as pd - idx = pd.date_range("2023", freq="AS", periods=2) + idx = pd.date_range("2023", freq="YS", periods=2) # --- hide: stop --- pd.DataFrame({"p": [50, 56], "w": [120, 125]}, idx) # --- hide: start --- @@ -139,7 +139,7 @@ Dictionaries are the most versatily of these objects. They can be used to pass a # --- hide: start --- import portfolyo as pf import pandas as pd - idx = pd.date_range("2023", freq="AS", periods=2) + idx = pd.date_range("2023", freq="YS", periods=2) # --- hide: stop --- d1 = {"p": 50} d2 = {"p": 50, "w": 120} @@ -194,7 +194,7 @@ Footnotes .. code-block:: python :emphasize-lines: 3,4 - >>> idx = pandas.date_range("2023", freq="AS", periods=2) + >>> idx = pandas.date_range("2023", freq="YS", periods=2) >>> s_agn = pandas.Series([50, 56], idx) # unit-agnostic >>> s1 = s_agn.astype("pint[Eur/MWh]") # unit-aware @@ -212,7 +212,7 @@ Footnotes .. code-block:: python :emphasize-lines: 4, 7 - >>> idx = pandas.date_range("2023", freq="AS", periods=2) + >>> idx = pandas.date_range("2023", freq="YS", periods=2) >>> s_price = pandas.Series([50, 56], idx, dtype="pint[Eur/MWh]") >>> s_volume = pandas.Series([120, 125], idx, dtype="pint[MW]") >>> df1 = pandas.DataFrame({"p": s_price, "w": s_volume}) diff --git a/docs/core/pfline.rst b/docs/core/pfline.rst index 3cb34a9..fdfbff0 100644 --- a/docs/core/pfline.rst +++ b/docs/core/pfline.rst @@ -80,7 +80,7 @@ The keys (or dataframe column names) must each be one of the following: ``w`` (p import portfolyo as pf import pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) input_dict = {'w': pd.Series([200, 220, 300.0], index)} pf.PfLine(input_dict) # --- hide: start --- @@ -95,7 +95,7 @@ Under the condition that a valid ``pint`` unit is present, we may also provide a # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) # --- hide: stop --- # using the imports and index from the previous example input_series = pd.Series([10, 11.5, 10.8], index, dtype='pint[ctEur/kWh]') @@ -115,7 +115,7 @@ The keys are used as the children names: # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) # --- hide: stop --- pfl1 = pf.PfLine(pd.Series([0.2, 0.22, 0.3], index, dtype='pint[GW]')) pfl2 = pf.PfLine(pd.Series([100, 150, 200.0], index, dtype='pint[MW]')) @@ -177,7 +177,7 @@ The properties ``PfLine.w``, ``.q``, ``.p`` and ``.r`` always return the informa # --- hide: start --- # --- hide: stop --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) input_df = pd.DataFrame({'w':[200, 220, 300], 'p': [100, 150, 200]}, index) pfl = pf.PfLine(input_df) @@ -198,7 +198,7 @@ If we want to extract more than one timeseries, we can use the ``.df`` attribute # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) input_df = pd.DataFrame({'w':[200, 220, 300], 'p': [100, 150, 200]}, index) pfl = pf.PfLine(input_df) # --- hide: stop --- @@ -221,7 +221,7 @@ The ``PfLine.index`` property returns the ``pandas.DatetimeIndex`` that applies # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) input_df = pd.DataFrame({'w':[200, 220, 300], 'p': [100, 150, 200]}, index) pfl = pf.PfLine(input_df) # --- hide: stop --- @@ -236,7 +236,7 @@ For convenience, ``portfolyo`` adds a ``.duration`` and a ``right`` property to # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) input_df = pd.DataFrame({'w':[200, 220, 300], 'p': [100, 150, 200]}, index) pfl = pf.PfLine(input_df) # --- hide: stop --- @@ -258,7 +258,7 @@ Another slicing method is implemented with the ``.slice[]`` property. The improv # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) input_df = pd.DataFrame({'w':[200, 220, 300], 'p': [100, 150, 200]}, index) pfl = pf.PfLine(input_df) # --- hide: stop --- @@ -280,12 +280,12 @@ Portfolio lines can be concatenated with the ``portfolio.concat()`` function. Th # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) input_df = pd.DataFrame({'w':[200, 220, 300], 'p': [100, 150, 200]}, index) pfl = pf.PfLine(input_df) # --- hide: stop --- # continuation of previous code example - index2 = pd.date_range('2025', freq='AS', periods=3) # 2 years' overlap with pfl + index2 = pd.date_range('2025', freq='YS', periods=3) # 2 years' overlap with pfl pfl2 = pf.PfLine(pd.DataFrame({'w':[22, 30, 40], 'p': [15, 20, 21]}, index)) # first two datapoints (until/excl 2026) from pfl, last two datapoints (from/incl 2026) from pfl2 pf.concat([pfl.slice[:'2026'], pfl2.slice['2026':]]) @@ -316,7 +316,7 @@ The data can be shown graphically with the ``.plot()`` method: # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) pfl = pf.PfLine(pd.DataFrame({'w':[200, 220, 300], 'p': [100, 150, 200]}, index)) # --- hide: stop --- # continuation of previous code example @@ -352,7 +352,7 @@ Using the ``.asfreq()`` method, we can quickly and correctly downsample our data # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) pfl = pf.PfLine(pd.DataFrame({'w':[200, 220, 300], 'p': [100, 150, 200]}, index)) # --- hide: stop --- # continuation of previous code example @@ -380,7 +380,7 @@ General remarks: .. exec_code:: import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) pfl = pf.PfLine(pd.Series([2, 2.2, 3], index, dtype='pint[MW]')) pfl_1 = pfl + {'q': 50.0} # standard unit (here: MWh) is assumed pfl_2 = pfl + pf.Q_(50000.0, 'kWh') @@ -435,7 +435,7 @@ Here are several examples. .. exec_code:: import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) vol = pf.PfLine(pd.Series([4, 4.6, 3], index, dtype='pint[MW]')) vol + pf.Q_(10.0, 'GWh') # --- hide: start --- @@ -447,7 +447,7 @@ Here are several examples. # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) # --- hide: stop --- # continuation of previous code example vol_2 = pf.PfLine({ @@ -474,7 +474,7 @@ Here are several examples. # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) vol = pf.PfLine(pd.Series([4, 4.6, 3], index, dtype='pint[MW]')) vol_2 = pf.PfLine({ 'A': pd.Series([4, 4.6, 3], index, dtype='pint[MW]'), @@ -492,7 +492,7 @@ Here are several examples. # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024', freq='AS', periods=3) + index = pd.date_range('2024', freq='YS', periods=3) vol = pf.PfLine(pd.Series([4, 4.6, 3], index, dtype='pint[MW]')) vol_2 = pf.PfLine({ 'A': pd.Series([4, 4.6, 3], index, dtype='pint[MW]'), @@ -737,7 +737,7 @@ Using the ``.hedge_with()`` method, the volume timeseries in a portfolio line is .. exec_code:: import portfolyo as pf, pandas as pd - index = pd.date_range('2024-04-01', '2024-06-01', freq='H', inclusive='left') + index = pd.date_range('2024-04-01', '2024-06-01', freq='h', inclusive='left') offtake = pf.PfLine(pf.dev.w_offtake(index)) # mock offtake volumes prices = pf.PfLine(pf.dev.p_marketprices(index)) # mock market prices # Create hedge @@ -764,7 +764,7 @@ For markets that have a concept of "peak" and "offpeak" periods, the ``.po()`` m # --- hide: start --- import portfolyo as pf, pandas as pd - index = pd.date_range('2024-04-01', '2024-06-01', freq='H', inclusive='left') + index = pd.date_range('2024-04-01', '2024-06-01', freq='h', inclusive='left') offtake = pf.PfLine(pf.dev.w_offtake(index)) # mock offtake volumes # --- hide: stop --- # continuation of previous code example diff --git a/docs/savefig/fig_hedge.png b/docs/savefig/fig_hedge.png index 8efdcad..6d19ad7 100644 Binary files a/docs/savefig/fig_hedge.png and b/docs/savefig/fig_hedge.png differ diff --git a/docs/savefig/fig_offtake.png b/docs/savefig/fig_offtake.png index 820ee35..6d8c20c 100644 Binary files a/docs/savefig/fig_offtake.png and b/docs/savefig/fig_offtake.png differ diff --git a/docs/savefig/fig_plot_pfl.png b/docs/savefig/fig_plot_pfl.png index c870ace..dfaa25f 100644 Binary files a/docs/savefig/fig_plot_pfl.png and b/docs/savefig/fig_plot_pfl.png differ diff --git a/docs/savefig/fig_plot_pfs.png b/docs/savefig/fig_plot_pfs.png index a3022e3..a5a191d 100644 Binary files a/docs/savefig/fig_plot_pfs.png and b/docs/savefig/fig_plot_pfs.png differ diff --git a/docs/specialized_topics/dataprep.rst b/docs/specialized_topics/dataprep.rst index 21912e3..908e597 100644 --- a/docs/specialized_topics/dataprep.rst +++ b/docs/specialized_topics/dataprep.rst @@ -71,12 +71,12 @@ Frequency The index must have a frequency (``fr.index.freq``) and it must be a valid frequency. To check validity of the frequency, one may use ``portfolyo.assert_freq_valid()``. The following abbreviations are used by ``pandas`` and throughout this package: -* ``15T``: quarterhourly; -* ``H``: hourly; +* ``15min``: quarterhourly; +* ``h``: hourly; * ``D``: daily; * ``MS``: monthly; * ``QS``: quarterly. Also allowed ``QS-FEB``, ``QS-MAR``, etc.; -* ``AS``: yearly. Also allowed ``AS-FEB``, ``AS-MAR``, etc. +* ``YS``: yearly. Also allowed ``YS-FEB``, ``YS-MAR``, etc. If the frequency is not set, we can try to make pandas infer it: diff --git a/docs/specialized_topics/indices.rst b/docs/specialized_topics/indices.rst index 78da2bd..6e863dc 100644 --- a/docs/specialized_topics/indices.rst +++ b/docs/specialized_topics/indices.rst @@ -62,7 +62,7 @@ However, when handling timeseries with shorter-than-daily indices, the offset ca # --- hide: start --- import pandas as pd - i = pd.date_range('2024-01-05 06:00', freq= 'H', periods = 40) + i = pd.date_range('2024-01-05 06:00', freq= 'h', periods = 40) print(repr(i)) It contains 40 hourly values, i.e., it does not contain an integer number of days. We cannot infer, how a day is defined, and in these situations it is therefore assumed, that it is the offset of the first timestamp - '06:00' in this case. diff --git a/docs/tutorial/part1.ipynb b/docs/tutorial/part1.ipynb index 64c4dbb..c8bde76 100644 --- a/docs/tutorial/part1.ipynb +++ b/docs/tutorial/part1.ipynb @@ -240,7 +240,7 @@ "source": [ "(``portfolyo`` ensures that the values are aggregated correctly. In this case, the price (``p``) values are weighted-averaged (weighted with the duration of each datapoint - in this case a uniform 24h). See [Resampling](../specialized_topics/resampling.rst) for more information.)\n", "\n", - "The argument ``\"QS\"`` specifies that we want quarterly values starting from January (same as ``\"QS-JAN\"``). The allowed values, in increasing duration, are following: ``\"15T\"`` (=quarterhourly), ``\"H\"`` (=hourly), ``\"D\"`` (=daily), ``\"MS\"`` (=monthly), ``\"QS\"`` (=quarterly, or ``\"QS-FEB\"``, ``\"QS-MAR\"``, etc.), or ``\"AS\"`` (=yearly, or ``\"AS-FEB\"``, ``\"AS-MAR\"``, etc.).\n" + "The argument ``\"QS\"`` specifies that we want quarterly values starting from January (same as ``\"QS-JAN\"``). The allowed values, in increasing duration, are following: ``\"15min\"`` (=quarterhourly), ``\"h\"`` (=hourly), ``\"D\"`` (=daily), ``\"MS\"`` (=monthly), ``\"QS\"`` (=quarterly, or ``\"QS-FEB\"``, ``\"QS-MAR\"``, etc.), or ``\"YS\"`` (=yearly, or ``\"YS-FEB\"``, ``\"YS-MAR\"``, etc.).\n" ] }, { diff --git a/docs/tutorial/part3.ipynb b/docs/tutorial/part3.ipynb index 1abbd36..2f139e5 100644 --- a/docs/tutorial/part3.ipynb +++ b/docs/tutorial/part3.ipynb @@ -1,968 +1,968 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Tutorial part 3\n", - "\n", - "In [part 1](part1.ipynb) and [part 2](part2.ipynb) we have learnt about portfolio lines. These are timeseries, or collections of timeseries, describing the volumes and/or prices during various delivery periods.\n", - "\n", - "In this part, we'll combine portfolio lines into a \"portfolio state\" (``PfState``) object. As we'll see, some of the methods and properties we know from the ``PfLine`` class also apply here.\n", - "\n", - "\n", - "## Example data\n", - "\n", - "Let's again use the mock functions to get some portfolio lines. (The parameter details here are not important, we just want some more-or-less realistic data). To change things up a bit from the previous tutorial parts, we'll look at about 80 days in the autumn of 2024, in quarterhourly (``\"15T\"``) resolution. And let's localize the data to a specific timezone:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import portfolyo as pf\n", - "import pandas as pd\n", - "\n", - "index = pd.date_range(\n", - " \"2024-09-20\", \"2024-12-10\", freq=\"15T\", inclusive=\"left\", tz=\"Europe/Berlin\"\n", - ")\n", - "# Creating offtake portfolio line.\n", - "ts_offtake = -1 * pf.dev.w_offtake(index, avg=50)\n", - "offtake = pf.PfLine({\"w\": ts_offtake})\n", - "# Creating portfolio line with market prices (here: price-forward curve).\n", - "ts_prices = pf.dev.p_marketprices(index, avg=200)\n", - "prices = pf.PfLine({\"p\": ts_prices})\n", - "\n", - "# Creating portfolio line with sourced volume.\n", - "ts_sourced_power1, ts_sourced_price1 = pf.dev.wp_sourced(\n", - " ts_offtake, \"QS\", 0.3, p_avg=120\n", - ")\n", - "sourced_quarters = pf.PfLine({\"w\": ts_sourced_power1, \"p\": ts_sourced_price1})\n", - "ts_sourced_power2, ts_sourced_price2 = pf.dev.wp_sourced(\n", - " ts_offtake, \"MS\", 0.2, p_avg=150\n", - ")\n", - "sourced_months = pf.PfLine({\"w\": ts_sourced_power2, \"p\": ts_sourced_price2})\n", - "sourced = pf.PfLine(\n", - " {\"quarter_products\": sourced_quarters, \"month_products\": sourced_months}\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now use these portfolio lines to create a portfolio state.\n", - "\n", - "## Portfolio State\n", - "\n", - "The ``PfState`` class is used to hold information about offtake, market prices, and sourcing. Let's create one from the portfolio lines we just created:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PfState object.\n", - ". Start: 2024-09-20 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", - ". End : 2024-12-10 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", - ". Freq : <15 * Minutes> (7780 datapoints)\n", - " w q p r\n", - " MW MWh Eur/MWh Eur\n", - "──────── offtake\n", - " 2024-09-20 00:00:00 +0200 -45.8 -11 \n", - " 2024-09-20 00:15:00 +0200 -44.4 -11 \n", - " .. .. .. .. ..\n", - " 2024-12-09 23:30:00 +0100 -60.5 -15 \n", - " 2024-12-09 23:45:00 +0100 -58.3 -15 \n", - "─●────── pnl_cost\n", - " │ 2024-09-20 00:00:00 +0200 45.8 11 136.54 1 564\n", - " │ 2024-09-20 00:15:00 +0200 44.4 11 135.21 1 500\n", - " │ .. .. .. .. ..\n", - " │ 2024-12-09 23:30:00 +0100 60.5 15 167.89 2 540\n", - " │ 2024-12-09 23:45:00 +0100 58.3 15 167.21 2 436\n", - " ├●───── sourced\n", - " ││ 2024-09-20 00:00:00 +0200 31.3 8 135.03 1 058\n", - " ││ 2024-09-20 00:15:00 +0200 31.3 8 135.03 1 058\n", - " ││ .. .. .. .. ..\n", - " ││ 2024-12-09 23:30:00 +0100 35.7 9 126.09 1 124\n", - " ││ 2024-12-09 23:45:00 +0100 35.7 9 126.09 1 124\n", - " │├───── quarter_products\n", - " ││ 2024-09-20 00:00:00 +0200 16.4 4 102.48 421\n", - " ││ 2024-09-20 00:15:00 +0200 16.4 4 102.48 421\n", - " ││ .. .. .. .. ..\n", - " ││ 2024-12-09 23:30:00 +0100 13.4 3 111.14 372\n", - " ││ 2024-12-09 23:45:00 +0100 13.4 3 111.14 372\n", - " │└───── month_products\n", - " │ 2024-09-20 00:00:00 +0200 14.9 4 170.85 637\n", - " │ 2024-09-20 00:15:00 +0200 14.9 4 170.85 637\n", - " │ .. .. .. .. ..\n", - " │ 2024-12-09 23:30:00 +0100 22.3 6 135.07 752\n", - " │ 2024-12-09 23:45:00 +0100 22.3 6 135.07 752\n", - " └────── unsourced\n", - " 2024-09-20 00:00:00 +0200 14.5 4 139.81 506\n", - " 2024-09-20 00:15:00 +0200 13.0 3 135.64 442\n", - " .. .. .. .. ..\n", - " 2024-12-09 23:30:00 +0100 24.8 6 227.88 1 416\n", - " 2024-12-09 23:45:00 +0100 22.6 6 232.05 1 312" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pfs = pf.PfState(offtake, prices, sourced)\n", - "\n", - "pfs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note from how these portfolio lines were created, that ``offtake`` has negative values. The sign conventions are discussed [here](../core/pfstate.rst#sign-conventions).\n", - "\n", - "This portfolio state contains values for every quarterhour in the specified time period. Let's see what features this class has, starting with two methods we already met when discussing the ``PfLine`` class.\n", - "\n", - "### Plotting\n", - "\n", - "Just as when working with portfolio lines, we can get a quick overview of the portfolio state with the ``.plot()`` method..." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "pfs.plot();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "...and we can copy its data to the clipboard, or save it as an Excel workbook:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "pfs.to_clipboard()\n", - "pfs.to_excel(\"portfolio_state.xlsx\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Looking at the offtake, we can see the daily and weekly cycles, as well as a slow increase over the entire time period.\n", - "\n", - "This graph is a bit too detailed for most purposes, so let's look at the second method we know from ``PfLine``: resampling.\n", - "\n", - "### Resampling\n", - "\n", - "We might prefer to see hourly, daily, or monthly values instead of the quarterhourly values that are in ``pfs``. For this, we can resample the object with the ``.asfreq()`` method. In this code example, we also use the ``.print()`` method, which adds some helpful coloring to the output:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PfState object.\n", - ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", - ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", - ". Freq : (2 datapoints)\n", - " w q p r\n", - " MW MWh Eur/MWh Eur\n", - "\u001b[1m\u001b[37m──────── offtake\n", - " \u001b[1m\u001b[37m \u001b[0m2024-10-01 00:00:00 +0200 -54.7 -40 744 \n", - " \u001b[1m\u001b[37m \u001b[0m2024-11-01 00:00:00 +0100 -59.4 -42 732 \n", - "\u001b[1m\u001b[37m─\u001b[1m\u001b[33m●\u001b[1m\u001b[37m────── pnl_cost\n", - " \u001b[1m\u001b[33m│\u001b[1m\u001b[37m \u001b[0m2024-10-01 00:00:00 +0200 54.7 40 744 160.48 6 538 471\n", - " \u001b[1m\u001b[33m│\u001b[1m\u001b[37m \u001b[0m2024-11-01 00:00:00 +0100 59.4 42 732 182.94 7 817 452\n", - " \u001b[1m\u001b[33m├\u001b[1m\u001b[36m●\u001b[1m\u001b[33m───── sourced\n", - " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m│\u001b[1m\u001b[33m \u001b[0m2024-10-01 00:00:00 +0200 31.2 23 221 132.58 3 078 642\n", - " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m│\u001b[1m\u001b[33m \u001b[0m2024-11-01 00:00:00 +0100 25.9 18 652 133.27 2 485 849\n", - " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m├───── quarter_products\n", - " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m│ \u001b[1m\u001b[36m \u001b[0m2024-10-01 00:00:00 +0200 13.8 10 256 106.54 1 092 646\n", - " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m│ \u001b[1m\u001b[36m \u001b[0m2024-11-01 00:00:00 +0100 13.7 9 897 106.78 1 056 810\n", - " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m└───── month_products\n", - " \u001b[1m\u001b[33m│ \u001b[1m\u001b[36m \u001b[0m2024-10-01 00:00:00 +0200 17.4 12 964 153.19 1 985 996\n", - " \u001b[1m\u001b[33m│ \u001b[1m\u001b[36m \u001b[0m2024-11-01 00:00:00 +0100 12.2 8 755 163.22 1 429 039\n", - " \u001b[1m\u001b[33m└────── unsourced\n", - " \u001b[1m\u001b[33m \u001b[0m2024-10-01 00:00:00 +0200 23.5 17 523 197.44 3 459 829\n", - " \u001b[1m\u001b[33m \u001b[0m2024-11-01 00:00:00 +0100 33.4 24 080 221.41 5 331 603\n" - ] - } - ], - "source": [ - "pfs_monthly = pfs.asfreq(\"MS\")\n", - "pfs_monthly.print()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note how the resampled output only contains those months, that are *entirely* included in the original data. ``pfl`` has data in September and December, but as these are not present in their entirety, they are dropped from ``pfl_monthly``.\n", - "\n", - "### On frequencies and unsourced prices\n", - "\n", - "There is one other important consequence of resampling: the unsourced prices are now *specific for this portfolio*. We can demonstrate this by creating a second portfolio state, using the *same price-forward curve* (but different offtake and sourced volume):" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "pfs2 = pf.PfState(offtake * 1.5, prices, sourced * 2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At this shortest frequency, the unsourced prices are identical (namely, the price-forward curve). Let's create a dataframe with the prices to verify this:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
pfspfs2hpfc
2024-09-20 00:00:00+02:00139.80568207211454139.80568207211454139.80568207211454
2024-09-20 00:15:00+02:00135.63901540544785135.63901540544785135.63901540544785
2024-09-20 00:30:00+02:00131.47234873878116131.47234873878116131.47234873878116
2024-09-20 00:45:00+02:00128.13901540544785128.13901540544785128.13901540544785
2024-09-20 01:00:00+02:00124.80568207211451124.80568207211451124.80568207211451
............
2024-12-09 22:45:00+01:00217.88078474854294217.88078474854294217.88078474854294
2024-12-09 23:00:00+01:00221.2141180818763221.2141180818763221.2141180818763
2024-12-09 23:15:00+01:00224.54745141520962224.54745141520962224.54745141520962
2024-12-09 23:30:00+01:00227.88078474854294227.88078474854294227.88078474854294
2024-12-09 23:45:00+01:00232.0474514152096232.0474514152096232.0474514152096
\n", - "

7780 rows × 3 columns

\n", - "
" - ], - "text/plain": [ - " pfs pfs2 \\\n", - "2024-09-20 00:00:00+02:00 139.80568207211454 139.80568207211454 \n", - "2024-09-20 00:15:00+02:00 135.63901540544785 135.63901540544785 \n", - "2024-09-20 00:30:00+02:00 131.47234873878116 131.47234873878116 \n", - "2024-09-20 00:45:00+02:00 128.13901540544785 128.13901540544785 \n", - "2024-09-20 01:00:00+02:00 124.80568207211451 124.80568207211451 \n", - "... ... ... \n", - "2024-12-09 22:45:00+01:00 217.88078474854294 217.88078474854294 \n", - "2024-12-09 23:00:00+01:00 221.2141180818763 221.2141180818763 \n", - "2024-12-09 23:15:00+01:00 224.54745141520962 224.54745141520962 \n", - "2024-12-09 23:30:00+01:00 227.88078474854294 227.88078474854294 \n", - "2024-12-09 23:45:00+01:00 232.0474514152096 232.0474514152096 \n", - "\n", - " hpfc \n", - "2024-09-20 00:00:00+02:00 139.80568207211454 \n", - "2024-09-20 00:15:00+02:00 135.63901540544785 \n", - "2024-09-20 00:30:00+02:00 131.47234873878116 \n", - "2024-09-20 00:45:00+02:00 128.13901540544785 \n", - "2024-09-20 01:00:00+02:00 124.80568207211451 \n", - "... ... \n", - "2024-12-09 22:45:00+01:00 217.88078474854294 \n", - "2024-12-09 23:00:00+01:00 221.2141180818763 \n", - "2024-12-09 23:15:00+01:00 224.54745141520962 \n", - "2024-12-09 23:30:00+01:00 227.88078474854294 \n", - "2024-12-09 23:45:00+01:00 232.0474514152096 \n", - "\n", - "[7780 rows x 3 columns]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.DataFrame(\n", - " {\"pfs\": pfs.unsourcedprice.p, \"pfs2\": pfs2.unsourcedprice.p, \"hpfc\": prices.p}\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, at every other frequency, they are not equal. When changing the frequency, a *volume-weighted* average is calculated for the unsourced prices - just like with every other price-and-volume timeseries. This makes that the unsourced prices apply only to the unsourced volume profile *of that portfolio*:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
pfspfs2hpfc
2024-10-01 00:00:00+02:00197.4437803760265192.11463549822653194.78888729464586
2024-11-01 00:00:00+01:00221.40989836363784217.79829542112196220.07479863376392
\n", - "
" - ], - "text/plain": [ - " pfs pfs2 \\\n", - "2024-10-01 00:00:00+02:00 197.4437803760265 192.11463549822653 \n", - "2024-11-01 00:00:00+01:00 221.40989836363784 217.79829542112196 \n", - "\n", - " hpfc \n", - "2024-10-01 00:00:00+02:00 194.78888729464586 \n", - "2024-11-01 00:00:00+01:00 220.07479863376392 " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.DataFrame(\n", - " {\n", - " \"pfs\": pfs.asfreq(\"MS\").unsourcedprice.p,\n", - " \"pfs2\": pfs2.asfreq(\"MS\").unsourcedprice.p,\n", - " \"hpfc\": prices.asfreq(\"MS\").p,\n", - " }\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This has important consequences. \n", - "\n", - "For example, when doing a scenario analysis in which the unsourced volume is changed (e.g. \"what happens if the offtake increases by 50%?\"), we cannot expect the results to be correct *unless we are working at the original frequency*. In situations where it is clear that this error looms, a ``UserWarning`` is shown to alert the user (e.g. in the examples further below). For more information on unsourced volume, [see this section](../core/pfstate.rst#Unsourced-price) in the documentation on the `PfState` class.\n", - "\n", - "For this reason, we commonly work with our porfolio states at the frequency of the price-forward curve. Downsampling is only done to see the aggregated values for verification or reporting." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Components\n", - "\n", - "Now, let's look at the portfolio state ``pfs_monthly`` in a bit more detail, to learn more about the ``PfState`` class.\n", - "\n", - "The portfolio state is presented to us as a tree structure, with several branches. Each branch is a portfolio line. E.g, ``offtake`` and ``sourced`` are the portfolio lines we specified when creating the object. Also, the branch ``pnl_cost`` is the sum of ``sourced`` and ``unsourced``, with ``sourced`` being the sum of ``quarter_products`` and ``month_products``. \n", - "\n", - "The unsourced volume is found by comparing the offtake to what is already sourced. This volume is valued at the market prices in the forward curve.\n", - "\n", - "These portfolio lines can be obtained from the portfolio state by accessing them as attributes. E.g. ``.offtakevolume``, ``.sourced``, ``.unsourced``, or ``.pnl_cost``. The latter is the best estimate for what it will cost to procure the offtake:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PfLine object with price and volume information.\n", - ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", - ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", - ". Freq : (2 datapoints)\n", - ". Children: 'sourced' (price and volume), 'unsourced' (price and volume)\n", - " w q p r\n", - " MW MWh Eur/MWh Eur\n", - "\n", - "2024-10-01 00:00:00 +0200 54.7 40 744 160.48 6 538 471\n", - "2024-11-01 00:00:00 +0100 59.4 42 732 182.94 7 817 452" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pfs_monthly.pnl_cost" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that this portfolio line has children, and as a reminder, we can \"drill into\" the object to get these nested portfolio line, e.g. with ``pfl_monthly.pnl_cost[\"sourced\"]``.\n", - "\n", - "There are some other components that are not explicitly shown:\n", - "\n", - "* We may be interested in how much of the offtake has already been sourced or unsourced. These fractions are available at the ``.sourcedfraction`` and ``.unsourcedfraction`` properties.\n", - "\n", - "* You may have noticed that ``unsourced`` is the inverse from what traders would call the \"open positions\" or \"portfolio positions\": if our portfolio is short, the unsourced volume is positive. For those that prefer this other perspective, it is available at ``.netposition``.\n", - "\n", - "### Export\n", - "\n", - "Just as with portfolio lines, we can create an excel file that contains all the information in a portfolio state with its ``.to_excel()`` method, and we can copy it to the clipboard with the ``.to_clipboard()`` method.\n", - "\n", - "### MtM\n", - "\n", - "We can evaluate the value of our sourcing contracts against the current forward curve (\"mark-to-market\") with the ``.mtm_of_sourced()`` method." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyses with portfolio states\n", - "\n", - "We'll now look at how we can do \"what-if\" analyses with portfolio state. The original portfolio state we will consider as the reference and store it in an appropriately named variable:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "ref = pfs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The monthly procurement prices of this portfolio are what interest us the most. As a reminder, we can find the procurement volumes and costs with:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PfLine object with price and volume information.\n", - ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", - ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", - ". Freq : (2 datapoints)\n", - ". Children: 'sourced' (price and volume), 'unsourced' (price and volume)\n", - " w q p r\n", - " MW MWh Eur/MWh Eur\n", - "\n", - "2024-10-01 00:00:00 +0200 54.7 40 744 160.48 6 538 471\n", - "2024-11-01 00:00:00 +0100 59.4 42 732 182.94 7 817 452" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_ref = ref.asfreq(\"MS\").pnl_cost\n", - "cost_ref" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "(Or we could go one step further and focus on only the prices with ``ref.asfreq(\"MS\").pnl_cost.p``.)\n", - "\n", - "### Change in offtake\n", - "\n", - "Now, what would happen if the offtake were to increase by 25%? Qualitatively, this is not hard. An increase in the offtake increases the unsourced volume. And because the market prices are higher than what we pay for the sourced volume, this means that the procurement price will go up. \n", - "\n", - "How much? Let's see. First, we create a new portfolio state, from the reference, by setting the offtake to the new value. We can do this with the ``.set_offtake()`` method. After that, we can again see what the procurement volumes and costs are. (Note the ``UserWarning`` which was mentioned [above](#on-frequencies-and-unsourced-pricesE).)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\users\\ruud.wijtvliet\\ruud\\python\\dev\\portfolyo\\portfolyo\\core\\pfstate\\pfstate.py:199: UserWarning: This operation changes the unsourced volume. This causes inaccuracies in its price if the portfolio state has a frequency that is longer than the spot market.\n", - " warnings.warn(\n" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tutorial part 3\n", + "\n", + "In [part 1](part1.ipynb) and [part 2](part2.ipynb) we have learnt about portfolio lines. These are timeseries, or collections of timeseries, describing the volumes and/or prices during various delivery periods.\n", + "\n", + "In this part, we'll combine portfolio lines into a \"portfolio state\" (``PfState``) object. As we'll see, some of the methods and properties we know from the ``PfLine`` class also apply here.\n", + "\n", + "\n", + "## Example data\n", + "\n", + "Let's again use the mock functions to get some portfolio lines. (The parameter details here are not important, we just want some more-or-less realistic data). To change things up a bit from the previous tutorial parts, we'll look at about 80 days in the autumn of 2024, in quarterhourly (``\"15min\"``) resolution. And let's localize the data to a specific timezone:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import portfolyo as pf\n", + "import pandas as pd\n", + "\n", + "index = pd.date_range(\n", + " \"2024-09-20\", \"2024-12-10\", freq=\"15min\", inclusive=\"left\", tz=\"Europe/Berlin\"\n", + ")\n", + "# Creating offtake portfolio line.\n", + "ts_offtake = -1 * pf.dev.w_offtake(index, avg=50)\n", + "offtake = pf.PfLine({\"w\": ts_offtake})\n", + "# Creating portfolio line with market prices (here: price-forward curve).\n", + "ts_prices = pf.dev.p_marketprices(index, avg=200)\n", + "prices = pf.PfLine({\"p\": ts_prices})\n", + "\n", + "# Creating portfolio line with sourced volume.\n", + "ts_sourced_power1, ts_sourced_price1 = pf.dev.wp_sourced(\n", + " ts_offtake, \"QS\", 0.3, p_avg=120\n", + ")\n", + "sourced_quarters = pf.PfLine({\"w\": ts_sourced_power1, \"p\": ts_sourced_price1})\n", + "ts_sourced_power2, ts_sourced_price2 = pf.dev.wp_sourced(\n", + " ts_offtake, \"MS\", 0.2, p_avg=150\n", + ")\n", + "sourced_months = pf.PfLine({\"w\": ts_sourced_power2, \"p\": ts_sourced_price2})\n", + "sourced = pf.PfLine(\n", + " {\"quarter_products\": sourced_quarters, \"month_products\": sourced_months}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now use these portfolio lines to create a portfolio state.\n", + "\n", + "## Portfolio State\n", + "\n", + "The ``PfState`` class is used to hold information about offtake, market prices, and sourcing. Let's create one from the portfolio lines we just created:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PfState object.\n", + ". Start: 2024-09-20 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", + ". End : 2024-12-10 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", + ". Freq : <15 * Minutes> (7780 datapoints)\n", + " w q p r\n", + " MW MWh Eur/MWh Eur\n", + "──────── offtake\n", + " 2024-09-20 00:00:00 +0200 -45.8 -11 \n", + " 2024-09-20 00:15:00 +0200 -44.4 -11 \n", + " .. .. .. .. ..\n", + " 2024-12-09 23:30:00 +0100 -60.5 -15 \n", + " 2024-12-09 23:45:00 +0100 -58.3 -15 \n", + "─●────── pnl_cost\n", + " │ 2024-09-20 00:00:00 +0200 45.8 11 136.54 1 564\n", + " │ 2024-09-20 00:15:00 +0200 44.4 11 135.21 1 500\n", + " │ .. .. .. .. ..\n", + " │ 2024-12-09 23:30:00 +0100 60.5 15 167.89 2 540\n", + " │ 2024-12-09 23:45:00 +0100 58.3 15 167.21 2 436\n", + " ├●───── sourced\n", + " ││ 2024-09-20 00:00:00 +0200 31.3 8 135.03 1 058\n", + " ││ 2024-09-20 00:15:00 +0200 31.3 8 135.03 1 058\n", + " ││ .. .. .. .. ..\n", + " ││ 2024-12-09 23:30:00 +0100 35.7 9 126.09 1 124\n", + " ││ 2024-12-09 23:45:00 +0100 35.7 9 126.09 1 124\n", + " │├───── quarter_products\n", + " ││ 2024-09-20 00:00:00 +0200 16.4 4 102.48 421\n", + " ││ 2024-09-20 00:15:00 +0200 16.4 4 102.48 421\n", + " ││ .. .. .. .. ..\n", + " ││ 2024-12-09 23:30:00 +0100 13.4 3 111.14 372\n", + " ││ 2024-12-09 23:45:00 +0100 13.4 3 111.14 372\n", + " │└───── month_products\n", + " │ 2024-09-20 00:00:00 +0200 14.9 4 170.85 637\n", + " │ 2024-09-20 00:15:00 +0200 14.9 4 170.85 637\n", + " │ .. .. .. .. ..\n", + " │ 2024-12-09 23:30:00 +0100 22.3 6 135.07 752\n", + " │ 2024-12-09 23:45:00 +0100 22.3 6 135.07 752\n", + " └────── unsourced\n", + " 2024-09-20 00:00:00 +0200 14.5 4 139.81 506\n", + " 2024-09-20 00:15:00 +0200 13.0 3 135.64 442\n", + " .. .. .. .. ..\n", + " 2024-12-09 23:30:00 +0100 24.8 6 227.88 1 416\n", + " 2024-12-09 23:45:00 +0100 22.6 6 232.05 1 312" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pfs = pf.PfState(offtake, prices, sourced)\n", + "\n", + "pfs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note from how these portfolio lines were created, that ``offtake`` has negative values. The sign conventions are discussed [here](../core/pfstate.rst#sign-conventions).\n", + "\n", + "This portfolio state contains values for every quarterhour in the specified time period. Let's see what features this class has, starting with two methods we already met when discussing the ``PfLine`` class.\n", + "\n", + "### Plotting\n", + "\n", + "Just as when working with portfolio lines, we can get a quick overview of the portfolio state with the ``.plot()`` method..." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "pfs.plot();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "...and we can copy its data to the clipboard, or save it as an Excel workbook:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "pfs.to_clipboard()\n", + "pfs.to_excel(\"portfolio_state.xlsx\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looking at the offtake, we can see the daily and weekly cycles, as well as a slow increase over the entire time period.\n", + "\n", + "This graph is a bit too detailed for most purposes, so let's look at the second method we know from ``PfLine``: resampling.\n", + "\n", + "### Resampling\n", + "\n", + "We might prefer to see hourly, daily, or monthly values instead of the quarterhourly values that are in ``pfs``. For this, we can resample the object with the ``.asfreq()`` method. In this code example, we also use the ``.print()`` method, which adds some helpful coloring to the output:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PfState object.\n", + ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", + ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", + ". Freq : (2 datapoints)\n", + " w q p r\n", + " MW MWh Eur/MWh Eur\n", + "\u001b[1m\u001b[37m──────── offtake\n", + " \u001b[1m\u001b[37m \u001b[0m2024-10-01 00:00:00 +0200 -54.7 -40 744 \n", + " \u001b[1m\u001b[37m \u001b[0m2024-11-01 00:00:00 +0100 -59.4 -42 732 \n", + "\u001b[1m\u001b[37m─\u001b[1m\u001b[33m●\u001b[1m\u001b[37m────── pnl_cost\n", + " \u001b[1m\u001b[33m│\u001b[1m\u001b[37m \u001b[0m2024-10-01 00:00:00 +0200 54.7 40 744 160.48 6 538 471\n", + " \u001b[1m\u001b[33m│\u001b[1m\u001b[37m \u001b[0m2024-11-01 00:00:00 +0100 59.4 42 732 182.94 7 817 452\n", + " \u001b[1m\u001b[33m├\u001b[1m\u001b[36m●\u001b[1m\u001b[33m───── sourced\n", + " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m│\u001b[1m\u001b[33m \u001b[0m2024-10-01 00:00:00 +0200 31.2 23 221 132.58 3 078 642\n", + " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m│\u001b[1m\u001b[33m \u001b[0m2024-11-01 00:00:00 +0100 25.9 18 652 133.27 2 485 849\n", + " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m├───── quarter_products\n", + " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m│ \u001b[1m\u001b[36m \u001b[0m2024-10-01 00:00:00 +0200 13.8 10 256 106.54 1 092 646\n", + " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m│ \u001b[1m\u001b[36m \u001b[0m2024-11-01 00:00:00 +0100 13.7 9 897 106.78 1 056 810\n", + " \u001b[1m\u001b[33m│\u001b[1m\u001b[36m└───── month_products\n", + " \u001b[1m\u001b[33m│ \u001b[1m\u001b[36m \u001b[0m2024-10-01 00:00:00 +0200 17.4 12 964 153.19 1 985 996\n", + " \u001b[1m\u001b[33m│ \u001b[1m\u001b[36m \u001b[0m2024-11-01 00:00:00 +0100 12.2 8 755 163.22 1 429 039\n", + " \u001b[1m\u001b[33m└────── unsourced\n", + " \u001b[1m\u001b[33m \u001b[0m2024-10-01 00:00:00 +0200 23.5 17 523 197.44 3 459 829\n", + " \u001b[1m\u001b[33m \u001b[0m2024-11-01 00:00:00 +0100 33.4 24 080 221.41 5 331 603\n" + ] + } + ], + "source": [ + "pfs_monthly = pfs.asfreq(\"MS\")\n", + "pfs_monthly.print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note how the resampled output only contains those months, that are *entirely* included in the original data. ``pfl`` has data in September and December, but as these are not present in their entirety, they are dropped from ``pfl_monthly``.\n", + "\n", + "### On frequencies and unsourced prices\n", + "\n", + "There is one other important consequence of resampling: the unsourced prices are now *specific for this portfolio*. We can demonstrate this by creating a second portfolio state, using the *same price-forward curve* (but different offtake and sourced volume):" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "pfs2 = pf.PfState(offtake * 1.5, prices, sourced * 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At this shortest frequency, the unsourced prices are identical (namely, the price-forward curve). Let's create a dataframe with the prices to verify this:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
pfspfs2hpfc
2024-09-20 00:00:00+02:00139.80568207211454139.80568207211454139.80568207211454
2024-09-20 00:15:00+02:00135.63901540544785135.63901540544785135.63901540544785
2024-09-20 00:30:00+02:00131.47234873878116131.47234873878116131.47234873878116
2024-09-20 00:45:00+02:00128.13901540544785128.13901540544785128.13901540544785
2024-09-20 01:00:00+02:00124.80568207211451124.80568207211451124.80568207211451
............
2024-12-09 22:45:00+01:00217.88078474854294217.88078474854294217.88078474854294
2024-12-09 23:00:00+01:00221.2141180818763221.2141180818763221.2141180818763
2024-12-09 23:15:00+01:00224.54745141520962224.54745141520962224.54745141520962
2024-12-09 23:30:00+01:00227.88078474854294227.88078474854294227.88078474854294
2024-12-09 23:45:00+01:00232.0474514152096232.0474514152096232.0474514152096
\n", + "

7780 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " pfs pfs2 \\\n", + "2024-09-20 00:00:00+02:00 139.80568207211454 139.80568207211454 \n", + "2024-09-20 00:15:00+02:00 135.63901540544785 135.63901540544785 \n", + "2024-09-20 00:30:00+02:00 131.47234873878116 131.47234873878116 \n", + "2024-09-20 00:45:00+02:00 128.13901540544785 128.13901540544785 \n", + "2024-09-20 01:00:00+02:00 124.80568207211451 124.80568207211451 \n", + "... ... ... \n", + "2024-12-09 22:45:00+01:00 217.88078474854294 217.88078474854294 \n", + "2024-12-09 23:00:00+01:00 221.2141180818763 221.2141180818763 \n", + "2024-12-09 23:15:00+01:00 224.54745141520962 224.54745141520962 \n", + "2024-12-09 23:30:00+01:00 227.88078474854294 227.88078474854294 \n", + "2024-12-09 23:45:00+01:00 232.0474514152096 232.0474514152096 \n", + "\n", + " hpfc \n", + "2024-09-20 00:00:00+02:00 139.80568207211454 \n", + "2024-09-20 00:15:00+02:00 135.63901540544785 \n", + "2024-09-20 00:30:00+02:00 131.47234873878116 \n", + "2024-09-20 00:45:00+02:00 128.13901540544785 \n", + "2024-09-20 01:00:00+02:00 124.80568207211451 \n", + "... ... \n", + "2024-12-09 22:45:00+01:00 217.88078474854294 \n", + "2024-12-09 23:00:00+01:00 221.2141180818763 \n", + "2024-12-09 23:15:00+01:00 224.54745141520962 \n", + "2024-12-09 23:30:00+01:00 227.88078474854294 \n", + "2024-12-09 23:45:00+01:00 232.0474514152096 \n", + "\n", + "[7780 rows x 3 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.DataFrame(\n", + " {\"pfs\": pfs.unsourcedprice.p, \"pfs2\": pfs2.unsourcedprice.p, \"hpfc\": prices.p}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, at every other frequency, they are not equal. When changing the frequency, a *volume-weighted* average is calculated for the unsourced prices - just like with every other price-and-volume timeseries. This makes that the unsourced prices apply only to the unsourced volume profile *of that portfolio*:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
pfspfs2hpfc
2024-10-01 00:00:00+02:00197.4437803760265192.11463549822653194.78888729464586
2024-11-01 00:00:00+01:00221.40989836363784217.79829542112196220.07479863376392
\n", + "
" + ], + "text/plain": [ + " pfs pfs2 \\\n", + "2024-10-01 00:00:00+02:00 197.4437803760265 192.11463549822653 \n", + "2024-11-01 00:00:00+01:00 221.40989836363784 217.79829542112196 \n", + "\n", + " hpfc \n", + "2024-10-01 00:00:00+02:00 194.78888729464586 \n", + "2024-11-01 00:00:00+01:00 220.07479863376392 " + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.DataFrame(\n", + " {\n", + " \"pfs\": pfs.asfreq(\"MS\").unsourcedprice.p,\n", + " \"pfs2\": pfs2.asfreq(\"MS\").unsourcedprice.p,\n", + " \"hpfc\": prices.asfreq(\"MS\").p,\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This has important consequences. \n", + "\n", + "For example, when doing a scenario analysis in which the unsourced volume is changed (e.g. \"what happens if the offtake increases by 50%?\"), we cannot expect the results to be correct *unless we are working at the original frequency*. In situations where it is clear that this error looms, a ``UserWarning`` is shown to alert the user (e.g. in the examples further below). For more information on unsourced volume, [see this section](../core/pfstate.rst#Unsourced-price) in the documentation on the `PfState` class.\n", + "\n", + "For this reason, we commonly work with our porfolio states at the frequency of the price-forward curve. Downsampling is only done to see the aggregated values for verification or reporting." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Components\n", + "\n", + "Now, let's look at the portfolio state ``pfs_monthly`` in a bit more detail, to learn more about the ``PfState`` class.\n", + "\n", + "The portfolio state is presented to us as a tree structure, with several branches. Each branch is a portfolio line. E.g, ``offtake`` and ``sourced`` are the portfolio lines we specified when creating the object. Also, the branch ``pnl_cost`` is the sum of ``sourced`` and ``unsourced``, with ``sourced`` being the sum of ``quarter_products`` and ``month_products``. \n", + "\n", + "The unsourced volume is found by comparing the offtake to what is already sourced. This volume is valued at the market prices in the forward curve.\n", + "\n", + "These portfolio lines can be obtained from the portfolio state by accessing them as attributes. E.g. ``.offtakevolume``, ``.sourced``, ``.unsourced``, or ``.pnl_cost``. The latter is the best estimate for what it will cost to procure the offtake:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PfLine object with price and volume information.\n", + ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", + ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", + ". Freq : (2 datapoints)\n", + ". Children: 'sourced' (price and volume), 'unsourced' (price and volume)\n", + " w q p r\n", + " MW MWh Eur/MWh Eur\n", + "\n", + "2024-10-01 00:00:00 +0200 54.7 40 744 160.48 6 538 471\n", + "2024-11-01 00:00:00 +0100 59.4 42 732 182.94 7 817 452" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pfs_monthly.pnl_cost" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that this portfolio line has children, and as a reminder, we can \"drill into\" the object to get these nested portfolio line, e.g. with ``pfl_monthly.pnl_cost[\"sourced\"]``.\n", + "\n", + "There are some other components that are not explicitly shown:\n", + "\n", + "* We may be interested in how much of the offtake has already been sourced or unsourced. These fractions are available at the ``.sourcedfraction`` and ``.unsourcedfraction`` properties.\n", + "\n", + "* You may have noticed that ``unsourced`` is the inverse from what traders would call the \"open positions\" or \"portfolio positions\": if our portfolio is short, the unsourced volume is positive. For those that prefer this other perspective, it is available at ``.netposition``.\n", + "\n", + "### Export\n", + "\n", + "Just as with portfolio lines, we can create an excel file that contains all the information in a portfolio state with its ``.to_excel()`` method, and we can copy it to the clipboard with the ``.to_clipboard()`` method.\n", + "\n", + "### MtM\n", + "\n", + "We can evaluate the value of our sourcing contracts against the current forward curve (\"mark-to-market\") with the ``.mtm_of_sourced()`` method." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analyses with portfolio states\n", + "\n", + "We'll now look at how we can do \"what-if\" analyses with portfolio state. The original portfolio state we will consider as the reference and store it in an appropriately named variable:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "ref = pfs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The monthly procurement prices of this portfolio are what interest us the most. As a reminder, we can find the procurement volumes and costs with:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PfLine object with price and volume information.\n", + ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", + ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", + ". Freq : (2 datapoints)\n", + ". Children: 'sourced' (price and volume), 'unsourced' (price and volume)\n", + " w q p r\n", + " MW MWh Eur/MWh Eur\n", + "\n", + "2024-10-01 00:00:00 +0200 54.7 40 744 160.48 6 538 471\n", + "2024-11-01 00:00:00 +0100 59.4 42 732 182.94 7 817 452" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cost_ref = ref.asfreq(\"MS\").pnl_cost\n", + "cost_ref" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "(Or we could go one step further and focus on only the prices with ``ref.asfreq(\"MS\").pnl_cost.p``.)\n", + "\n", + "### Change in offtake\n", + "\n", + "Now, what would happen if the offtake were to increase by 25%? Qualitatively, this is not hard. An increase in the offtake increases the unsourced volume. And because the market prices are higher than what we pay for the sourced volume, this means that the procurement price will go up. \n", + "\n", + "How much? Let's see. First, we create a new portfolio state, from the reference, by setting the offtake to the new value. We can do this with the ``.set_offtake()`` method. After that, we can again see what the procurement volumes and costs are. (Note the ``UserWarning`` which was mentioned [above](#on-frequencies-and-unsourced-pricesE).)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\users\\ruud.wijtvliet\\ruud\\python\\dev\\portfolyo\\portfolyo\\core\\pfstate\\pfstate.py:199: UserWarning: This operation changes the unsourced volume. This causes inaccuracies in its price if the portfolio state has a frequency that is longer than the spot market.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "PfLine object with price and volume information.\n", + ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", + ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", + ". Freq : (2 datapoints)\n", + ". Children: 'sourced' (price and volume), 'unsourced' (price and volume)\n", + " w q p r\n", + " MW MWh Eur/MWh Eur\n", + "\n", + "2024-10-01 00:00:00 +0200 68.4 50 930 168.64 8 588 719\n", + "2024-11-01 00:00:00 +0100 74.2 53 416 191.54 10 231 182" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "higherofftake = ref.offtakevolume * 1.25\n", + "pfs_higherofftake = ref.set_offtakevolume(higherofftake)\n", + "cost_higherofftake = pfs_higherofftake.asfreq(\"MS\").pnl_cost\n", + "cost_higherofftake" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Comparing these two ``cost`` portfolio lines, we see that indeed the values for ``w`` and ``q`` have increased to 125% of the original values. Also, the procurement prices have increased. We can quickly calculate by how much:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2024-10-01 00:00:00+02:00 8.160879013401626\n", + "2024-11-01 00:00:00+01:00 8.599884206454362\n", + "Freq: MS, Name: p, dtype: pint[Eur/MWh]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cost_higherofftake.p - cost_ref.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We could similarly create a portfolio states for situations with a market price drop of 40%. Or one which combines both effects:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\users\\ruud.wijtvliet\\ruud\\python\\dev\\portfolyo\\portfolyo\\core\\pfstate\\pfstate.py:199: UserWarning: This operation changes the unsourced volume. This causes inaccuracies in its price if the portfolio state has a frequency that is longer than the spot market.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "lowerprices = ref.unsourcedprice * 0.6\n", + "pfs_lowerprices = ref.set_unsourcedprice(lowerprices)\n", + "pfs_lowerprices_higherofftake = ref.set_offtakevolume(higherofftake).set_unsourcedprice(\n", + " lowerprices\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hedging\n", + "\n", + "Hedging can reduce the sensitivity of our portfolio to changes in the market price. Given the current market price curve, we can calculate how much we'd need to source to obtain a fully hedged portfolio:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "needed = ref.hedge_of_unsourced(\"val\", \"MS\") # value hedge with month products" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's say we procure exactly that volume. We can add it to the sourced volume in our portfolio state:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\users\\ruud.wijtvliet\\ruud\\python\\dev\\portfolyo\\portfolyo\\core\\pfstate\\pfstate.py:209: UserWarning: This operation changes the unsourced volume. This causes inaccuracies in its price if the portfolio state has a frequency that is longer than the spot market.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "hedged = ref.add_sourced(pf.PfLine({\"newvolume\": needed}))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "(We could have obtained the same result with the ``ref.source_unsourced()`` method.)\n", + "\n", + "The portfolio is now hedged at the month level. We can verify this by looking at the unsourced volume. In case of a volume hedge, the unsourced volume (``q`` and ``w``) is 0, even if its monetary value (``r``) is not; in case of a value hedge, it is the reverse:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PfLine object with price and volume information.\n", + ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", + ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", + ". Freq : (2 datapoints)\n", + " w q p r\n", + " MW MWh Eur/MWh Eur\n", + "\n", + "2024-10-01 00:00:00 +0200 -0.2 -173 0.00 -0\n", + "2024-11-01 00:00:00 +0100 -0.2 -134 0.00 -0" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hedged.asfreq(\"MS\").unsourced" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because the market prices have not changed, the best-estimate procurement prices (at month level and longer) are also unchanged from before. (This is verified in the \"before\" columns of the dataframe further below.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Market price change\n", + "\n", + "A hedged profile is less impacted by market price changes. To see that this is indeed the case, let's look at a scenario with an increase in the forward price curve by 40 Eur/MWh, for both portfolio states:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "newprices = prices + pf.Q_(40.0, \"Eur/MWh\")\n", + "ref_higherprices = ref.set_unsourcedprice(newprices)\n", + "hedged_higherprices = hedged.set_unsourcedprice(newprices)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The reference portfolio has gotten a lot more expensive, whereas the procurement price for the hedged portfolio has not moved significantly:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
refhedged
beforeafterbeforeafter
unitEur/MWhEur/MWhEur/MWhEur/MWh
2024-10-01 00:00:00+02:00160.478106177.681366160.478106160.307936
2024-11-01 00:00:00+01:00182.939602205.480086182.939602182.814260
\n", + "
" + ], + "text/plain": [ + " ref hedged \n", + " before after before after\n", + "unit Eur/MWh Eur/MWh Eur/MWh Eur/MWh\n", + "2024-10-01 00:00:00+02:00 160.478106 177.681366 160.478106 160.307936\n", + "2024-11-01 00:00:00+01:00 182.939602 205.480086 182.939602 182.814260" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.DataFrame(\n", + " {\n", + " (\"ref\", \"before\"): ref.pnl_cost.asfreq(\"MS\").p,\n", + " (\"ref\", \"after\"): ref_higherprices.pnl_cost.asfreq(\"MS\").p,\n", + " (\"hedged\", \"before\"): hedged.pnl_cost.asfreq(\"MS\").p,\n", + " (\"hedged\", \"after\"): hedged_higherprices.pnl_cost.asfreq(\"MS\").p,\n", + " }\n", + ").pint.dequantify()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the observant reader: it may seem that the portfolio was not fully hedged after all, as a small change in the procurement price is seen. The reason is that each strategy (i.e., volume or value hedge) fully protects only against a specific price change (i.e., absolute or relative). A volume hedge does not *fully* hedge against an absolute price change such as the one we see here." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tutorial is continued [in part 4](part4.ipynb)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.13 ('pf38')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "642a4be8010ca5d45039b988c1d8379a91572488c4d23a0b88e966c6713c7e45" + } + } }, - { - "data": { - "text/plain": [ - "PfLine object with price and volume information.\n", - ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", - ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", - ". Freq : (2 datapoints)\n", - ". Children: 'sourced' (price and volume), 'unsourced' (price and volume)\n", - " w q p r\n", - " MW MWh Eur/MWh Eur\n", - "\n", - "2024-10-01 00:00:00 +0200 68.4 50 930 168.64 8 588 719\n", - "2024-11-01 00:00:00 +0100 74.2 53 416 191.54 10 231 182" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "higherofftake = ref.offtakevolume * 1.25\n", - "pfs_higherofftake = ref.set_offtakevolume(higherofftake)\n", - "cost_higherofftake = pfs_higherofftake.asfreq(\"MS\").pnl_cost\n", - "cost_higherofftake" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Comparing these two ``cost`` portfolio lines, we see that indeed the values for ``w`` and ``q`` have increased to 125% of the original values. Also, the procurement prices have increased. We can quickly calculate by how much:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2024-10-01 00:00:00+02:00 8.160879013401626\n", - "2024-11-01 00:00:00+01:00 8.599884206454362\n", - "Freq: MS, Name: p, dtype: pint[Eur/MWh]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cost_higherofftake.p - cost_ref.p" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We could similarly create a portfolio states for situations with a market price drop of 40%. Or one which combines both effects:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\users\\ruud.wijtvliet\\ruud\\python\\dev\\portfolyo\\portfolyo\\core\\pfstate\\pfstate.py:199: UserWarning: This operation changes the unsourced volume. This causes inaccuracies in its price if the portfolio state has a frequency that is longer than the spot market.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "lowerprices = ref.unsourcedprice * 0.6\n", - "pfs_lowerprices = ref.set_unsourcedprice(lowerprices)\n", - "pfs_lowerprices_higherofftake = ref.set_offtakevolume(higherofftake).set_unsourcedprice(\n", - " lowerprices\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Hedging\n", - "\n", - "Hedging can reduce the sensitivity of our portfolio to changes in the market price. Given the current market price curve, we can calculate how much we'd need to source to obtain a fully hedged portfolio:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "needed = ref.hedge_of_unsourced(\"val\", \"MS\") # value hedge with month products" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's say we procure exactly that volume. We can add it to the sourced volume in our portfolio state:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\users\\ruud.wijtvliet\\ruud\\python\\dev\\portfolyo\\portfolyo\\core\\pfstate\\pfstate.py:209: UserWarning: This operation changes the unsourced volume. This causes inaccuracies in its price if the portfolio state has a frequency that is longer than the spot market.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "hedged = ref.add_sourced(pf.PfLine({\"newvolume\": needed}))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "(We could have obtained the same result with the ``ref.source_unsourced()`` method.)\n", - "\n", - "The portfolio is now hedged at the month level. We can verify this by looking at the unsourced volume. In case of a volume hedge, the unsourced volume (``q`` and ``w``) is 0, even if its monetary value (``r``) is not; in case of a value hedge, it is the reverse:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PfLine object with price and volume information.\n", - ". Start: 2024-10-01 00:00:00+02:00 (incl) . Timezone : Europe/Berlin \n", - ". End : 2024-12-01 00:00:00+01:00 (excl) . Start-of-day: 00:00:00 \n", - ". Freq : (2 datapoints)\n", - " w q p r\n", - " MW MWh Eur/MWh Eur\n", - "\n", - "2024-10-01 00:00:00 +0200 -0.2 -173 0.00 -0\n", - "2024-11-01 00:00:00 +0100 -0.2 -134 0.00 -0" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "hedged.asfreq(\"MS\").unsourced" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Because the market prices have not changed, the best-estimate procurement prices (at month level and longer) are also unchanged from before. (This is verified in the \"before\" columns of the dataframe further below.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Market price change\n", - "\n", - "A hedged profile is less impacted by market price changes. To see that this is indeed the case, let's look at a scenario with an increase in the forward price curve by 40 Eur/MWh, for both portfolio states:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "newprices = prices + pf.Q_(40.0, \"Eur/MWh\")\n", - "ref_higherprices = ref.set_unsourcedprice(newprices)\n", - "hedged_higherprices = hedged.set_unsourcedprice(newprices)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The reference portfolio has gotten a lot more expensive, whereas the procurement price for the hedged portfolio has not moved significantly:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
refhedged
beforeafterbeforeafter
unitEur/MWhEur/MWhEur/MWhEur/MWh
2024-10-01 00:00:00+02:00160.478106177.681366160.478106160.307936
2024-11-01 00:00:00+01:00182.939602205.480086182.939602182.814260
\n", - "
" - ], - "text/plain": [ - " ref hedged \n", - " before after before after\n", - "unit Eur/MWh Eur/MWh Eur/MWh Eur/MWh\n", - "2024-10-01 00:00:00+02:00 160.478106 177.681366 160.478106 160.307936\n", - "2024-11-01 00:00:00+01:00 182.939602 205.480086 182.939602 182.814260" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.DataFrame(\n", - " {\n", - " (\"ref\", \"before\"): ref.pnl_cost.asfreq(\"MS\").p,\n", - " (\"ref\", \"after\"): ref_higherprices.pnl_cost.asfreq(\"MS\").p,\n", - " (\"hedged\", \"before\"): hedged.pnl_cost.asfreq(\"MS\").p,\n", - " (\"hedged\", \"after\"): hedged_higherprices.pnl_cost.asfreq(\"MS\").p,\n", - " }\n", - ").pint.dequantify()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the observant reader: it may seem that the portfolio was not fully hedged after all, as a small change in the procurement price is seen. The reason is that each strategy (i.e., volume or value hedge) fully protects only against a specific price change (i.e., absolute or relative). A volume hedge does not *fully* hedge against an absolute price change such as the one we see here." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This tutorial is continued [in part 4](part4.ipynb)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.8.13 ('pf38')", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.13" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "642a4be8010ca5d45039b988c1d8379a91572488c4d23a0b88e966c6713c7e45" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/docs/tutorial/part4.ipynb b/docs/tutorial/part4.ipynb index 0a323da..5fb4570 100644 --- a/docs/tutorial/part4.ipynb +++ b/docs/tutorial/part4.ipynb @@ -54,7 +54,7 @@ "import pandas as pd\n", "\n", "index = pd.date_range(\n", - " \"2024-09-01\", \"2024-11-01\", freq=\"15T\", inclusive=\"left\", tz=\"Europe/Berlin\"\n", + " \"2024-09-01\", \"2024-11-01\", freq=\"15min\", inclusive=\"left\", tz=\"Europe/Berlin\"\n", ")\n", "# Creating portfolio line with market prices (here: price-forward curve).\n", "ts_prices = pf.dev.p_marketprices(index, avg=200)\n", @@ -279,4 +279,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/docs/tutorial/quickstart.ipynb b/docs/tutorial/quickstart.ipynb index bf0bda8..9061f14 100644 --- a/docs/tutorial/quickstart.ipynb +++ b/docs/tutorial/quickstart.ipynb @@ -22,7 +22,7 @@ "import portfolyo as pf\n", "import pandas as pd\n", "\n", - "index = pd.date_range(\"2024\", freq=\"H\", periods=8784, tz=\"Europe/Berlin\")\n", + "index = pd.date_range(\"2024\", freq=\"h\", periods=8784, tz=\"Europe/Berlin\")\n", "\n", "# Creating market prices (here: forward price curve) timeseries.\n", "ts_prices = pf.dev.p_marketprices(index, avg=200)\n", diff --git a/poetry.lock b/poetry.lock index 1cfe5b4..bf057a3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -53,32 +53,32 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "attrs" -version = "23.2.0" +version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "babel" -version = "2.15.0" +version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" files = [ - {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, - {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.extras] @@ -207,63 +207,78 @@ files = [ [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, + {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, + {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, + {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, + {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, + {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, + {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, + {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, + {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, + {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, + {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, + {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, + {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, + {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, + {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, ] [package.dependencies] @@ -501,63 +516,83 @@ test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] [[package]] name = "coverage" -version = "7.6.0" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, - {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, - {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, - {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, - {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, - {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, - {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, - {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, - {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, - {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, - {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, - {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, - {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, - {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] @@ -583,33 +618,33 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "debugpy" -version = "1.8.2" +version = "1.8.5" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2"}, - {file = "debugpy-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6"}, - {file = "debugpy-1.8.2-cp310-cp310-win32.whl", hash = "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47"}, - {file = "debugpy-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3"}, - {file = "debugpy-1.8.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a"}, - {file = "debugpy-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634"}, - {file = "debugpy-1.8.2-cp311-cp311-win32.whl", hash = "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad"}, - {file = "debugpy-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa"}, - {file = "debugpy-1.8.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835"}, - {file = "debugpy-1.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3"}, - {file = "debugpy-1.8.2-cp312-cp312-win32.whl", hash = "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e"}, - {file = "debugpy-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859"}, - {file = "debugpy-1.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d"}, - {file = "debugpy-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02"}, - {file = "debugpy-1.8.2-cp38-cp38-win32.whl", hash = "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031"}, - {file = "debugpy-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210"}, - {file = "debugpy-1.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9"}, - {file = "debugpy-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1"}, - {file = "debugpy-1.8.2-cp39-cp39-win32.whl", hash = "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326"}, - {file = "debugpy-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755"}, - {file = "debugpy-1.8.2-py2.py3-none-any.whl", hash = "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca"}, - {file = "debugpy-1.8.2.zip", hash = "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00"}, + {file = "debugpy-1.8.5-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7"}, + {file = "debugpy-1.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a"}, + {file = "debugpy-1.8.5-cp310-cp310-win32.whl", hash = "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed"}, + {file = "debugpy-1.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e"}, + {file = "debugpy-1.8.5-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a"}, + {file = "debugpy-1.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b"}, + {file = "debugpy-1.8.5-cp311-cp311-win32.whl", hash = "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408"}, + {file = "debugpy-1.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3"}, + {file = "debugpy-1.8.5-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156"}, + {file = "debugpy-1.8.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb"}, + {file = "debugpy-1.8.5-cp312-cp312-win32.whl", hash = "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7"}, + {file = "debugpy-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c"}, + {file = "debugpy-1.8.5-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a"}, + {file = "debugpy-1.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226"}, + {file = "debugpy-1.8.5-cp38-cp38-win32.whl", hash = "sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a"}, + {file = "debugpy-1.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf"}, + {file = "debugpy-1.8.5-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c"}, + {file = "debugpy-1.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406"}, + {file = "debugpy-1.8.5-cp39-cp39-win32.whl", hash = "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34"}, + {file = "debugpy-1.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c"}, + {file = "debugpy-1.8.5-py2.py3-none-any.whl", hash = "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44"}, + {file = "debugpy-1.8.5.zip", hash = "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0"}, ] [[package]] @@ -1324,40 +1359,51 @@ files = [ [[package]] name = "matplotlib" -version = "3.9.1" +version = "3.9.2" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.9.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ccd6270066feb9a9d8e0705aa027f1ff39f354c72a87efe8fa07632f30fc6bb"}, - {file = "matplotlib-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:591d3a88903a30a6d23b040c1e44d1afdd0d778758d07110eb7596f811f31842"}, - {file = "matplotlib-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2a59ff4b83d33bca3b5ec58203cc65985367812cb8c257f3e101632be86d92"}, - {file = "matplotlib-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc001516ffcf1a221beb51198b194d9230199d6842c540108e4ce109ac05cc0"}, - {file = "matplotlib-3.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:83c6a792f1465d174c86d06f3ae85a8fe36e6f5964633ae8106312ec0921fdf5"}, - {file = "matplotlib-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:421851f4f57350bcf0811edd754a708d2275533e84f52f6760b740766c6747a7"}, - {file = "matplotlib-3.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b3fce58971b465e01b5c538f9d44915640c20ec5ff31346e963c9e1cd66fa812"}, - {file = "matplotlib-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a973c53ad0668c53e0ed76b27d2eeeae8799836fd0d0caaa4ecc66bf4e6676c0"}, - {file = "matplotlib-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd5acf8f3ef43f7532c2f230249720f5dc5dd40ecafaf1c60ac8200d46d7eb"}, - {file = "matplotlib-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab38a4f3772523179b2f772103d8030215b318fef6360cb40558f585bf3d017f"}, - {file = "matplotlib-3.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2315837485ca6188a4b632c5199900e28d33b481eb083663f6a44cfc8987ded3"}, - {file = "matplotlib-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0c977c5c382f6696caf0bd277ef4f936da7e2aa202ff66cad5f0ac1428ee15b"}, - {file = "matplotlib-3.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:565d572efea2b94f264dd86ef27919515aa6d629252a169b42ce5f570db7f37b"}, - {file = "matplotlib-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d397fd8ccc64af2ec0af1f0efc3bacd745ebfb9d507f3f552e8adb689ed730a"}, - {file = "matplotlib-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26040c8f5121cd1ad712abffcd4b5222a8aec3a0fe40bc8542c94331deb8780d"}, - {file = "matplotlib-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12cb1837cffaac087ad6b44399d5e22b78c729de3cdae4629e252067b705e2b"}, - {file = "matplotlib-3.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0e835c6988edc3d2d08794f73c323cc62483e13df0194719ecb0723b564e0b5c"}, - {file = "matplotlib-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:44a21d922f78ce40435cb35b43dd7d573cf2a30138d5c4b709d19f00e3907fd7"}, - {file = "matplotlib-3.9.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0c584210c755ae921283d21d01f03a49ef46d1afa184134dd0f95b0202ee6f03"}, - {file = "matplotlib-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11fed08f34fa682c2b792942f8902e7aefeed400da71f9e5816bea40a7ce28fe"}, - {file = "matplotlib-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0000354e32efcfd86bda75729716b92f5c2edd5b947200be9881f0a671565c33"}, - {file = "matplotlib-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db17fea0ae3aceb8e9ac69c7e3051bae0b3d083bfec932240f9bf5d0197a049"}, - {file = "matplotlib-3.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:208cbce658b72bf6a8e675058fbbf59f67814057ae78165d8a2f87c45b48d0ff"}, - {file = "matplotlib-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:dc23f48ab630474264276be156d0d7710ac6c5a09648ccdf49fef9200d8cbe80"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3fda72d4d472e2ccd1be0e9ccb6bf0d2eaf635e7f8f51d737ed7e465ac020cb3"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:84b3ba8429935a444f1fdc80ed930babbe06725bcf09fbeb5c8757a2cd74af04"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b918770bf3e07845408716e5bbda17eadfc3fcbd9307dc67f37d6cf834bb3d98"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f1f2e5d29e9435c97ad4c36fb6668e89aee13d48c75893e25cef064675038ac9"}, - {file = "matplotlib-3.9.1.tar.gz", hash = "sha256:de06b19b8db95dd33d0dc17c926c7c9ebed9f572074b6fac4f65068a6814d010"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, + {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, + {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, + {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, + {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, + {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, + {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, + {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, + {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, + {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, + {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, + {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, + {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, ] [package.dependencies] @@ -1589,13 +1635,13 @@ files = [ [[package]] name = "numpydoc" -version = "1.7.0" +version = "1.8.0" description = "Sphinx extension to support docstrings in Numpy format" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "numpydoc-1.7.0-py3-none-any.whl", hash = "sha256:5a56419d931310d79a06cfc2a126d1558700feeb9b4f3d8dcae1a8134be829c9"}, - {file = "numpydoc-1.7.0.tar.gz", hash = "sha256:866e5ae5b6509dcf873fc6381120f5c31acf13b135636c1a81d68c166a95f921"}, + {file = "numpydoc-1.8.0-py3-none-any.whl", hash = "sha256:72024c7fd5e17375dec3608a27c03303e8ad00c81292667955c6fea7a3ccf541"}, + {file = "numpydoc-1.8.0.tar.gz", hash = "sha256:022390ab7464a44f8737f79f8b31ce1d3cfa4b4af79ccaa1aac5e8368db587fb"}, ] [package.dependencies] @@ -1605,7 +1651,7 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] developer = ["pre-commit (>=3.3)", "tomli"] -doc = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pydata-sphinx-theme (>=0.13.3)", "sphinx (>=7)"] +doc = ["intersphinx-registry", "matplotlib (>=3.5)", "numpy (>=1.22)", "pydata-sphinx-theme (>=0.13.3)", "sphinx (>=7)"] test = ["matplotlib", "pytest", "pytest-cov"] [[package]] @@ -1708,12 +1754,12 @@ xml = ["lxml (>=4.9.2)"] [[package]] name = "pandoc" -version = "2.3" +version = "2.4" description = "Pandoc Documents for Python" optional = false python-versions = "*" files = [ - {file = "pandoc-2.3.tar.gz", hash = "sha256:e772c2c6d871146894579828dbaf1efd538eb64fc7e71d4a6b3a11a18baef90d"}, + {file = "pandoc-2.4.tar.gz", hash = "sha256:ecd1f8cbb7f4180c6b5db4a17a7c1a74df519995f5f186ef81ce72a9cbd0dd9a"}, ] [package.dependencies] @@ -1917,13 +1963,13 @@ test = ["codecov", "coveralls", "nbval", "pyarrow", "pytest", "pytest-cov", "pyt [[package]] name = "pip" -version = "24.1.2" +version = "24.2" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" files = [ - {file = "pip-24.1.2-py3-none-any.whl", hash = "sha256:7cd207eed4c60b0f411b444cd1464198fe186671c323b6cd6d433ed80fc9d247"}, - {file = "pip-24.1.2.tar.gz", hash = "sha256:e5458a0b89f2755e0ee8c0c77613fe5273e05f337907874d64f13171a898a7ff"}, + {file = "pip-24.2-py3-none-any.whl", hash = "sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2"}, + {file = "pip-24.2.tar.gz", hash = "sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8"}, ] [[package]] @@ -2013,13 +2059,13 @@ files = [ [[package]] name = "pre-commit" -version = "3.7.1" +version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, - {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, + {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, + {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, ] [package.dependencies] @@ -2085,13 +2131,13 @@ files = [ [[package]] name = "pure-eval" -version = "0.2.2" +version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" files = [ - {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, - {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, ] [package.extras] @@ -2259,158 +2305,182 @@ files = [ [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "pyzmq" -version = "26.0.3" +version = "26.1.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.7" files = [ - {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"}, - {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"}, - {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"}, - {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"}, - {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"}, - {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"}, - {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"}, - {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"}, - {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"}, - {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"}, - {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"}, - {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"}, - {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"}, - {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"}, - {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"}, - {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"}, - {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"}, - {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"}, - {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"}, - {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"}, - {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"}, - {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"}, - {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"}, - {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"}, - {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"}, - {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"}, - {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"}, - {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"}, + {file = "pyzmq-26.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:263cf1e36862310bf5becfbc488e18d5d698941858860c5a8c079d1511b3b18e"}, + {file = "pyzmq-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d5c8b17f6e8f29138678834cf8518049e740385eb2dbf736e8f07fc6587ec682"}, + {file = "pyzmq-26.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a95c2358fcfdef3374cb8baf57f1064d73246d55e41683aaffb6cfe6862917"}, + {file = "pyzmq-26.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99de52b8fbdb2a8f5301ae5fc0f9e6b3ba30d1d5fc0421956967edcc6914242"}, + {file = "pyzmq-26.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bcbfbab4e1895d58ab7da1b5ce9a327764f0366911ba5b95406c9104bceacb0"}, + {file = "pyzmq-26.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77ce6a332c7e362cb59b63f5edf730e83590d0ab4e59c2aa5bd79419a42e3449"}, + {file = "pyzmq-26.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba0a31d00e8616149a5ab440d058ec2da621e05d744914774c4dde6837e1f545"}, + {file = "pyzmq-26.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8b88641384e84a258b740801cd4dbc45c75f148ee674bec3149999adda4a8598"}, + {file = "pyzmq-26.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2fa76ebcebe555cce90f16246edc3ad83ab65bb7b3d4ce408cf6bc67740c4f88"}, + {file = "pyzmq-26.1.0-cp310-cp310-win32.whl", hash = "sha256:fbf558551cf415586e91160d69ca6416f3fce0b86175b64e4293644a7416b81b"}, + {file = "pyzmq-26.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a7b8aab50e5a288c9724d260feae25eda69582be84e97c012c80e1a5e7e03fb2"}, + {file = "pyzmq-26.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:08f74904cb066e1178c1ec706dfdb5c6c680cd7a8ed9efebeac923d84c1f13b1"}, + {file = "pyzmq-26.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:46d6800b45015f96b9d92ece229d92f2aef137d82906577d55fadeb9cf5fcb71"}, + {file = "pyzmq-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5bc2431167adc50ba42ea3e5e5f5cd70d93e18ab7b2f95e724dd8e1bd2c38120"}, + {file = "pyzmq-26.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3bb34bebaa1b78e562931a1687ff663d298013f78f972a534f36c523311a84d"}, + {file = "pyzmq-26.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3f6329340cef1c7ba9611bd038f2d523cea79f09f9c8f6b0553caba59ec562"}, + {file = "pyzmq-26.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:471880c4c14e5a056a96cd224f5e71211997d40b4bf5e9fdded55dafab1f98f2"}, + {file = "pyzmq-26.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ce6f2b66799971cbae5d6547acefa7231458289e0ad481d0be0740535da38d8b"}, + {file = "pyzmq-26.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a1f6ea5b1d6cdbb8cfa0536f0d470f12b4b41ad83625012e575f0e3ecfe97f0"}, + {file = "pyzmq-26.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b45e6445ac95ecb7d728604bae6538f40ccf4449b132b5428c09918523abc96d"}, + {file = "pyzmq-26.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:94c4262626424683feea0f3c34951d39d49d354722db2745c42aa6bb50ecd93b"}, + {file = "pyzmq-26.1.0-cp311-cp311-win32.whl", hash = "sha256:a0f0ab9df66eb34d58205913f4540e2ad17a175b05d81b0b7197bc57d000e829"}, + {file = "pyzmq-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8efb782f5a6c450589dbab4cb0f66f3a9026286333fe8f3a084399149af52f29"}, + {file = "pyzmq-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f133d05aaf623519f45e16ab77526e1e70d4e1308e084c2fb4cedb1a0c764bbb"}, + {file = "pyzmq-26.1.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:3d3146b1c3dcc8a1539e7cc094700b2be1e605a76f7c8f0979b6d3bde5ad4072"}, + {file = "pyzmq-26.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d9270fbf038bf34ffca4855bcda6e082e2c7f906b9eb8d9a8ce82691166060f7"}, + {file = "pyzmq-26.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:995301f6740a421afc863a713fe62c0aaf564708d4aa057dfdf0f0f56525294b"}, + {file = "pyzmq-26.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7eca8b89e56fb8c6c26dd3e09bd41b24789022acf1cf13358e96f1cafd8cae3"}, + {file = "pyzmq-26.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d4feb2e83dfe9ace6374a847e98ee9d1246ebadcc0cb765482e272c34e5820"}, + {file = "pyzmq-26.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d4fafc2eb5d83f4647331267808c7e0c5722c25a729a614dc2b90479cafa78bd"}, + {file = "pyzmq-26.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:58c33dc0e185dd97a9ac0288b3188d1be12b756eda67490e6ed6a75cf9491d79"}, + {file = "pyzmq-26.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:68a0a1d83d33d8367ddddb3e6bb4afbb0f92bd1dac2c72cd5e5ddc86bdafd3eb"}, + {file = "pyzmq-26.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ae7c57e22ad881af78075e0cea10a4c778e67234adc65c404391b417a4dda83"}, + {file = "pyzmq-26.1.0-cp312-cp312-win32.whl", hash = "sha256:347e84fc88cc4cb646597f6d3a7ea0998f887ee8dc31c08587e9c3fd7b5ccef3"}, + {file = "pyzmq-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:9f136a6e964830230912f75b5a116a21fe8e34128dcfd82285aa0ef07cb2c7bd"}, + {file = "pyzmq-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4b7a989c8f5a72ab1b2bbfa58105578753ae77b71ba33e7383a31ff75a504c4"}, + {file = "pyzmq-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d416f2088ac8f12daacffbc2e8918ef4d6be8568e9d7155c83b7cebed49d2322"}, + {file = "pyzmq-26.1.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:ecb6c88d7946166d783a635efc89f9a1ff11c33d680a20df9657b6902a1d133b"}, + {file = "pyzmq-26.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:471312a7375571857a089342beccc1a63584315188560c7c0da7e0a23afd8a5c"}, + {file = "pyzmq-26.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e6cea102ffa16b737d11932c426f1dc14b5938cf7bc12e17269559c458ac334"}, + {file = "pyzmq-26.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec7248673ffc7104b54e4957cee38b2f3075a13442348c8d651777bf41aa45ee"}, + {file = "pyzmq-26.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0614aed6f87d550b5cecb03d795f4ddbb1544b78d02a4bd5eecf644ec98a39f6"}, + {file = "pyzmq-26.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:e8746ce968be22a8a1801bf4a23e565f9687088580c3ed07af5846580dd97f76"}, + {file = "pyzmq-26.1.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:7688653574392d2eaeef75ddcd0b2de5b232d8730af29af56c5adf1df9ef8d6f"}, + {file = "pyzmq-26.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8d4dac7d97f15c653a5fedcafa82626bd6cee1450ccdaf84ffed7ea14f2b07a4"}, + {file = "pyzmq-26.1.0-cp313-cp313-win32.whl", hash = "sha256:ccb42ca0a4a46232d716779421bbebbcad23c08d37c980f02cc3a6bd115ad277"}, + {file = "pyzmq-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e1e5d0a25aea8b691a00d6b54b28ac514c8cc0d8646d05f7ca6cb64b97358250"}, + {file = "pyzmq-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:fc82269d24860cfa859b676d18850cbb8e312dcd7eada09e7d5b007e2f3d9eb1"}, + {file = "pyzmq-26.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:416ac51cabd54f587995c2b05421324700b22e98d3d0aa2cfaec985524d16f1d"}, + {file = "pyzmq-26.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:ff832cce719edd11266ca32bc74a626b814fff236824aa1aeaad399b69fe6eae"}, + {file = "pyzmq-26.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:393daac1bcf81b2a23e696b7b638eedc965e9e3d2112961a072b6cd8179ad2eb"}, + {file = "pyzmq-26.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9869fa984c8670c8ab899a719eb7b516860a29bc26300a84d24d8c1b71eae3ec"}, + {file = "pyzmq-26.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b3b8e36fd4c32c0825b4461372949ecd1585d326802b1321f8b6dc1d7e9318c"}, + {file = "pyzmq-26.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3ee647d84b83509b7271457bb428cc347037f437ead4b0b6e43b5eba35fec0aa"}, + {file = "pyzmq-26.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:45cb1a70eb00405ce3893041099655265fabcd9c4e1e50c330026e82257892c1"}, + {file = "pyzmq-26.1.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:5cca7b4adb86d7470e0fc96037771981d740f0b4cb99776d5cb59cd0e6684a73"}, + {file = "pyzmq-26.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:91d1a20bdaf3b25f3173ff44e54b1cfbc05f94c9e8133314eb2962a89e05d6e3"}, + {file = "pyzmq-26.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c0665d85535192098420428c779361b8823d3d7ec4848c6af3abb93bc5c915bf"}, + {file = "pyzmq-26.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:96d7c1d35ee4a495df56c50c83df7af1c9688cce2e9e0edffdbf50889c167595"}, + {file = "pyzmq-26.1.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b281b5ff5fcc9dcbfe941ac5c7fcd4b6c065adad12d850f95c9d6f23c2652384"}, + {file = "pyzmq-26.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5384c527a9a004445c5074f1e20db83086c8ff1682a626676229aafd9cf9f7d1"}, + {file = "pyzmq-26.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:754c99a9840839375ee251b38ac5964c0f369306eddb56804a073b6efdc0cd88"}, + {file = "pyzmq-26.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9bdfcb74b469b592972ed881bad57d22e2c0acc89f5e8c146782d0d90fb9f4bf"}, + {file = "pyzmq-26.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bd13f0231f4788db619347b971ca5f319c5b7ebee151afc7c14632068c6261d3"}, + {file = "pyzmq-26.1.0-cp37-cp37m-win32.whl", hash = "sha256:c5668dac86a869349828db5fc928ee3f58d450dce2c85607067d581f745e4fb1"}, + {file = "pyzmq-26.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad875277844cfaeca7fe299ddf8c8d8bfe271c3dc1caf14d454faa5cdbf2fa7a"}, + {file = "pyzmq-26.1.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:65c6e03cc0222eaf6aad57ff4ecc0a070451e23232bb48db4322cc45602cede0"}, + {file = "pyzmq-26.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:038ae4ffb63e3991f386e7fda85a9baab7d6617fe85b74a8f9cab190d73adb2b"}, + {file = "pyzmq-26.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bdeb2c61611293f64ac1073f4bf6723b67d291905308a7de9bb2ca87464e3273"}, + {file = "pyzmq-26.1.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:61dfa5ee9d7df297c859ac82b1226d8fefaf9c5113dc25c2c00ecad6feeeb04f"}, + {file = "pyzmq-26.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3292d384537b9918010769b82ab3e79fca8b23d74f56fc69a679106a3e2c2cf"}, + {file = "pyzmq-26.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f9499c70c19ff0fbe1007043acb5ad15c1dec7d8e84ab429bca8c87138e8f85c"}, + {file = "pyzmq-26.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d3dd5523ed258ad58fed7e364c92a9360d1af8a9371e0822bd0146bdf017ef4c"}, + {file = "pyzmq-26.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baba2fd199b098c5544ef2536b2499d2e2155392973ad32687024bd8572a7d1c"}, + {file = "pyzmq-26.1.0-cp38-cp38-win32.whl", hash = "sha256:ddbb2b386128d8eca92bd9ca74e80f73fe263bcca7aa419f5b4cbc1661e19741"}, + {file = "pyzmq-26.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:79e45a4096ec8388cdeb04a9fa5e9371583bcb826964d55b8b66cbffe7b33c86"}, + {file = "pyzmq-26.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:add52c78a12196bc0fda2de087ba6c876ea677cbda2e3eba63546b26e8bf177b"}, + {file = "pyzmq-26.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c03bd7f3339ff47de7ea9ac94a2b34580a8d4df69b50128bb6669e1191a895"}, + {file = "pyzmq-26.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dcc37d9d708784726fafc9c5e1232de655a009dbf97946f117aefa38d5985a0f"}, + {file = "pyzmq-26.1.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a6ed52f0b9bf8dcc64cc82cce0607a3dfed1dbb7e8c6f282adfccc7be9781de"}, + {file = "pyzmq-26.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451e16ae8bea3d95649317b463c9f95cd9022641ec884e3d63fc67841ae86dfe"}, + {file = "pyzmq-26.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:906e532c814e1d579138177a00ae835cd6becbf104d45ed9093a3aaf658f6a6a"}, + {file = "pyzmq-26.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05bacc4f94af468cc82808ae3293390278d5f3375bb20fef21e2034bb9a505b6"}, + {file = "pyzmq-26.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:57bb2acba798dc3740e913ffadd56b1fcef96f111e66f09e2a8db3050f1f12c8"}, + {file = "pyzmq-26.1.0-cp39-cp39-win32.whl", hash = "sha256:f774841bb0e8588505002962c02da420bcfb4c5056e87a139c6e45e745c0e2e2"}, + {file = "pyzmq-26.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:359c533bedc62c56415a1f5fcfd8279bc93453afdb0803307375ecf81c962402"}, + {file = "pyzmq-26.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:7907419d150b19962138ecec81a17d4892ea440c184949dc29b358bc730caf69"}, + {file = "pyzmq-26.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b24079a14c9596846bf7516fe75d1e2188d4a528364494859106a33d8b48be38"}, + {file = "pyzmq-26.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59d0acd2976e1064f1b398a00e2c3e77ed0a157529779e23087d4c2fb8aaa416"}, + {file = "pyzmq-26.1.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:911c43a4117915203c4cc8755e0f888e16c4676a82f61caee2f21b0c00e5b894"}, + {file = "pyzmq-26.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10163e586cc609f5f85c9b233195554d77b1e9a0801388907441aaeb22841c5"}, + {file = "pyzmq-26.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:28a8b2abb76042f5fd7bd720f7fea48c0fd3e82e9de0a1bf2c0de3812ce44a42"}, + {file = "pyzmq-26.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bef24d3e4ae2c985034439f449e3f9e06bf579974ce0e53d8a507a1577d5b2ab"}, + {file = "pyzmq-26.1.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2cd0f4d314f4a2518e8970b6f299ae18cff7c44d4a1fc06fc713f791c3a9e3ea"}, + {file = "pyzmq-26.1.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fa25a620eed2a419acc2cf10135b995f8f0ce78ad00534d729aa761e4adcef8a"}, + {file = "pyzmq-26.1.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef3b048822dca6d231d8a8ba21069844ae38f5d83889b9b690bf17d2acc7d099"}, + {file = "pyzmq-26.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:9a6847c92d9851b59b9f33f968c68e9e441f9a0f8fc972c5580c5cd7cbc6ee24"}, + {file = "pyzmq-26.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9b9305004d7e4e6a824f4f19b6d8f32b3578aad6f19fc1122aaf320cbe3dc83"}, + {file = "pyzmq-26.1.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:63c1d3a65acb2f9c92dce03c4e1758cc552f1ae5c78d79a44e3bb88d2fa71f3a"}, + {file = "pyzmq-26.1.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d36b8fffe8b248a1b961c86fbdfa0129dfce878731d169ede7fa2631447331be"}, + {file = "pyzmq-26.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67976d12ebfd61a3bc7d77b71a9589b4d61d0422282596cf58c62c3866916544"}, + {file = "pyzmq-26.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:998444debc8816b5d8d15f966e42751032d0f4c55300c48cc337f2b3e4f17d03"}, + {file = "pyzmq-26.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5c88b2f13bcf55fee78ea83567b9fe079ba1a4bef8b35c376043440040f7edb"}, + {file = "pyzmq-26.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d906d43e1592be4b25a587b7d96527cb67277542a5611e8ea9e996182fae410"}, + {file = "pyzmq-26.1.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b0c9942430d731c786545da6be96d824a41a51742e3e374fedd9018ea43106"}, + {file = "pyzmq-26.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:314d11564c00b77f6224d12eb3ddebe926c301e86b648a1835c5b28176c83eab"}, + {file = "pyzmq-26.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:093a1a3cae2496233f14b57f4b485da01b4ff764582c854c0f42c6dd2be37f3d"}, + {file = "pyzmq-26.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3c397b1b450f749a7e974d74c06d69bd22dd362142f370ef2bd32a684d6b480c"}, + {file = "pyzmq-26.1.0.tar.gz", hash = "sha256:6c5aeea71f018ebd3b9115c7cb13863dd850e98ca6b9258509de1246461a7e7f"}, ] [package.dependencies] @@ -2454,126 +2524,131 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rpds-py" -version = "0.19.0" +version = "0.20.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.19.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:fb37bd599f031f1a6fb9e58ec62864ccf3ad549cf14bac527dbfa97123edcca4"}, - {file = "rpds_py-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3384d278df99ec2c6acf701d067147320b864ef6727405d6470838476e44d9e8"}, - {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54548e0be3ac117595408fd4ca0ac9278fde89829b0b518be92863b17ff67a2"}, - {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8eb488ef928cdbc05a27245e52de73c0d7c72a34240ef4d9893fdf65a8c1a955"}, - {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5da93debdfe27b2bfc69eefb592e1831d957b9535e0943a0ee8b97996de21b5"}, - {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79e205c70afddd41f6ee79a8656aec738492a550247a7af697d5bd1aee14f766"}, - {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:959179efb3e4a27610e8d54d667c02a9feaa86bbabaf63efa7faa4dfa780d4f1"}, - {file = "rpds_py-0.19.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a6e605bb9edcf010f54f8b6a590dd23a4b40a8cb141255eec2a03db249bc915b"}, - {file = "rpds_py-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9133d75dc119a61d1a0ded38fb9ba40a00ef41697cc07adb6ae098c875195a3f"}, - {file = "rpds_py-0.19.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd36b712d35e757e28bf2f40a71e8f8a2d43c8b026d881aa0c617b450d6865c9"}, - {file = "rpds_py-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:354f3a91718489912f2e0fc331c24eaaf6a4565c080e00fbedb6015857c00582"}, - {file = "rpds_py-0.19.0-cp310-none-win32.whl", hash = "sha256:ebcbf356bf5c51afc3290e491d3722b26aaf5b6af3c1c7f6a1b757828a46e336"}, - {file = "rpds_py-0.19.0-cp310-none-win_amd64.whl", hash = "sha256:75a6076289b2df6c8ecb9d13ff79ae0cad1d5fb40af377a5021016d58cd691ec"}, - {file = "rpds_py-0.19.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6d45080095e585f8c5097897313def60caa2046da202cdb17a01f147fb263b81"}, - {file = "rpds_py-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5c9581019c96f865483d031691a5ff1cc455feb4d84fc6920a5ffc48a794d8a"}, - {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1540d807364c84516417115c38f0119dfec5ea5c0dd9a25332dea60b1d26fc4d"}, - {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e65489222b410f79711dc3d2d5003d2757e30874096b2008d50329ea4d0f88c"}, - {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9da6f400eeb8c36f72ef6646ea530d6d175a4f77ff2ed8dfd6352842274c1d8b"}, - {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37f46bb11858717e0efa7893c0f7055c43b44c103e40e69442db5061cb26ed34"}, - {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:071d4adc734de562bd11d43bd134330fb6249769b2f66b9310dab7460f4bf714"}, - {file = "rpds_py-0.19.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9625367c8955e4319049113ea4f8fee0c6c1145192d57946c6ffcd8fe8bf48dd"}, - {file = "rpds_py-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e19509145275d46bc4d1e16af0b57a12d227c8253655a46bbd5ec317e941279d"}, - {file = "rpds_py-0.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d438e4c020d8c39961deaf58f6913b1bf8832d9b6f62ec35bd93e97807e9cbc"}, - {file = "rpds_py-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90bf55d9d139e5d127193170f38c584ed3c79e16638890d2e36f23aa1630b952"}, - {file = "rpds_py-0.19.0-cp311-none-win32.whl", hash = "sha256:8d6ad132b1bc13d05ffe5b85e7a01a3998bf3a6302ba594b28d61b8c2cf13aaf"}, - {file = "rpds_py-0.19.0-cp311-none-win_amd64.whl", hash = "sha256:7ec72df7354e6b7f6eb2a17fa6901350018c3a9ad78e48d7b2b54d0412539a67"}, - {file = "rpds_py-0.19.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5095a7c838a8647c32aa37c3a460d2c48debff7fc26e1136aee60100a8cd8f68"}, - {file = "rpds_py-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f2f78ef14077e08856e788fa482107aa602636c16c25bdf59c22ea525a785e9"}, - {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7cc6cb44f8636fbf4a934ca72f3e786ba3c9f9ba4f4d74611e7da80684e48d2"}, - {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf902878b4af334a09de7a45badbff0389e7cf8dc2e4dcf5f07125d0b7c2656d"}, - {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:688aa6b8aa724db1596514751ffb767766e02e5c4a87486ab36b8e1ebc1aedac"}, - {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57dbc9167d48e355e2569346b5aa4077f29bf86389c924df25c0a8b9124461fb"}, - {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4cf5a9497874822341c2ebe0d5850fed392034caadc0bad134ab6822c0925b"}, - {file = "rpds_py-0.19.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8a790d235b9d39c70a466200d506bb33a98e2ee374a9b4eec7a8ac64c2c261fa"}, - {file = "rpds_py-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d16089dfa58719c98a1c06f2daceba6d8e3fb9b5d7931af4a990a3c486241cb"}, - {file = "rpds_py-0.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bc9128e74fe94650367fe23f37074f121b9f796cabbd2f928f13e9661837296d"}, - {file = "rpds_py-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c8f77e661ffd96ff104bebf7d0f3255b02aa5d5b28326f5408d6284c4a8b3248"}, - {file = "rpds_py-0.19.0-cp312-none-win32.whl", hash = "sha256:5f83689a38e76969327e9b682be5521d87a0c9e5a2e187d2bc6be4765f0d4600"}, - {file = "rpds_py-0.19.0-cp312-none-win_amd64.whl", hash = "sha256:06925c50f86da0596b9c3c64c3837b2481337b83ef3519e5db2701df695453a4"}, - {file = "rpds_py-0.19.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:52e466bea6f8f3a44b1234570244b1cff45150f59a4acae3fcc5fd700c2993ca"}, - {file = "rpds_py-0.19.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e21cc693045fda7f745c790cb687958161ce172ffe3c5719ca1764e752237d16"}, - {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b31f059878eb1f5da8b2fd82480cc18bed8dcd7fb8fe68370e2e6285fa86da6"}, - {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dd46f309e953927dd018567d6a9e2fb84783963650171f6c5fe7e5c41fd5666"}, - {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a01a4490e170376cd79258b7f755fa13b1a6c3667e872c8e35051ae857a92b"}, - {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcf426a8c38eb57f7bf28932e68425ba86def6e756a5b8cb4731d8e62e4e0223"}, - {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68eea5df6347d3f1378ce992d86b2af16ad7ff4dcb4a19ccdc23dea901b87fb"}, - {file = "rpds_py-0.19.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dab8d921b55a28287733263c0e4c7db11b3ee22aee158a4de09f13c93283c62d"}, - {file = "rpds_py-0.19.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6fe87efd7f47266dfc42fe76dae89060038f1d9cb911f89ae7e5084148d1cc08"}, - {file = "rpds_py-0.19.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:535d4b52524a961d220875688159277f0e9eeeda0ac45e766092bfb54437543f"}, - {file = "rpds_py-0.19.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8b1a94b8afc154fbe36978a511a1f155f9bd97664e4f1f7a374d72e180ceb0ae"}, - {file = "rpds_py-0.19.0-cp38-none-win32.whl", hash = "sha256:7c98298a15d6b90c8f6e3caa6457f4f022423caa5fa1a1ca7a5e9e512bdb77a4"}, - {file = "rpds_py-0.19.0-cp38-none-win_amd64.whl", hash = "sha256:b0da31853ab6e58a11db3205729133ce0df26e6804e93079dee095be3d681dc1"}, - {file = "rpds_py-0.19.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5039e3cef7b3e7a060de468a4a60a60a1f31786da94c6cb054e7a3c75906111c"}, - {file = "rpds_py-0.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab1932ca6cb8c7499a4d87cb21ccc0d3326f172cfb6a64021a889b591bb3045c"}, - {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2afd2164a1e85226fcb6a1da77a5c8896c18bfe08e82e8ceced5181c42d2179"}, - {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1c30841f5040de47a0046c243fc1b44ddc87d1b12435a43b8edff7e7cb1e0d0"}, - {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f757f359f30ec7dcebca662a6bd46d1098f8b9fb1fcd661a9e13f2e8ce343ba1"}, - {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15e65395a59d2e0e96caf8ee5389ffb4604e980479c32742936ddd7ade914b22"}, - {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb0f6eb3a320f24b94d177e62f4074ff438f2ad9d27e75a46221904ef21a7b05"}, - {file = "rpds_py-0.19.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b228e693a2559888790936e20f5f88b6e9f8162c681830eda303bad7517b4d5a"}, - {file = "rpds_py-0.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2575efaa5d949c9f4e2cdbe7d805d02122c16065bfb8d95c129372d65a291a0b"}, - {file = "rpds_py-0.19.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5c872814b77a4e84afa293a1bee08c14daed1068b2bb1cc312edbf020bbbca2b"}, - {file = "rpds_py-0.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850720e1b383df199b8433a20e02b25b72f0fded28bc03c5bd79e2ce7ef050be"}, - {file = "rpds_py-0.19.0-cp39-none-win32.whl", hash = "sha256:ce84a7efa5af9f54c0aa7692c45861c1667080814286cacb9958c07fc50294fb"}, - {file = "rpds_py-0.19.0-cp39-none-win_amd64.whl", hash = "sha256:1c26da90b8d06227d7769f34915913911222d24ce08c0ab2d60b354e2d9c7aff"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:75969cf900d7be665ccb1622a9aba225cf386bbc9c3bcfeeab9f62b5048f4a07"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8445f23f13339da640d1be8e44e5baf4af97e396882ebbf1692aecd67f67c479"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5a7c1062ef8aea3eda149f08120f10795835fc1c8bc6ad948fb9652a113ca55"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:462b0c18fbb48fdbf980914a02ee38c423a25fcc4cf40f66bacc95a2d2d73bc8"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3208f9aea18991ac7f2b39721e947bbd752a1abbe79ad90d9b6a84a74d44409b"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3444fe52b82f122d8a99bf66777aed6b858d392b12f4c317da19f8234db4533"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb4bac7185a9f0168d38c01d7a00addece9822a52870eee26b8d5b61409213"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b130bd4163c93798a6b9bb96be64a7c43e1cec81126ffa7ffaa106e1fc5cef5"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a707b158b4410aefb6b054715545bbb21aaa5d5d0080217290131c49c2124a6e"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dc9ac4659456bde7c567107556ab065801622396b435a3ff213daef27b495388"}, - {file = "rpds_py-0.19.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:81ea573aa46d3b6b3d890cd3c0ad82105985e6058a4baed03cf92518081eec8c"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f148c3f47f7f29a79c38cc5d020edcb5ca780020fab94dbc21f9af95c463581"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0906357f90784a66e89ae3eadc2654f36c580a7d65cf63e6a616e4aec3a81be"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f629ecc2db6a4736b5ba95a8347b0089240d69ad14ac364f557d52ad68cf94b0"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6feacd1d178c30e5bc37184526e56740342fd2aa6371a28367bad7908d454fc"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b6068ee374fdfab63689be0963333aa83b0815ead5d8648389a8ded593378"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78d57546bad81e0da13263e4c9ce30e96dcbe720dbff5ada08d2600a3502e526"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b6683a37338818646af718c9ca2a07f89787551057fae57c4ec0446dc6224b"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e8481b946792415adc07410420d6fc65a352b45d347b78fec45d8f8f0d7496f0"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:bec35eb20792ea64c3c57891bc3ca0bedb2884fbac2c8249d9b731447ecde4fa"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:aa5476c3e3a402c37779e95f7b4048db2cb5b0ed0b9d006983965e93f40fe05a"}, - {file = "rpds_py-0.19.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:19d02c45f2507b489fd4df7b827940f1420480b3e2e471e952af4d44a1ea8e34"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3e2fd14c5d49ee1da322672375963f19f32b3d5953f0615b175ff7b9d38daed"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:93a91c2640645303e874eada51f4f33351b84b351a689d470f8108d0e0694210"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b9fc03bf76a94065299d4a2ecd8dfbae4ae8e2e8098bbfa6ab6413ca267709"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a4b07cdf3f84310c08c1de2c12ddadbb7a77568bcb16e95489f9c81074322ed"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba0ed0dc6763d8bd6e5de5cf0d746d28e706a10b615ea382ac0ab17bb7388633"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:474bc83233abdcf2124ed3f66230a1c8435896046caa4b0b5ab6013c640803cc"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329c719d31362355a96b435f4653e3b4b061fcc9eba9f91dd40804ca637d914e"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef9101f3f7b59043a34f1dccbb385ca760467590951952d6701df0da9893ca0c"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:0121803b0f424ee2109d6e1f27db45b166ebaa4b32ff47d6aa225642636cd834"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:8344127403dea42f5970adccf6c5957a71a47f522171fafaf4c6ddb41b61703a"}, - {file = "rpds_py-0.19.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:443cec402ddd650bb2b885113e1dcedb22b1175c6be223b14246a714b61cd521"}, - {file = "rpds_py-0.19.0.tar.gz", hash = "sha256:4fdc9afadbeb393b4bbbad75481e0ea78e4469f2e1d713a90811700830b553a9"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, + {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, + {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, + {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, + {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, + {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, + {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, + {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, + {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, + {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, + {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, + {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, + {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, + {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, ] [[package]] name = "setuptools" -version = "70.3.0" +version = "72.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"}, - {file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"}, + {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, + {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, ] [package.extras] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -2712,49 +2787,49 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.8" +version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, - {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.6" +version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, - {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.5" +version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, - {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] @@ -2788,33 +2863,33 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.7" +version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, - {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] -test = ["pytest"] +test = ["defusedxml (>=0.7.1)", "pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.10" +version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, - {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] @@ -2998,13 +3073,13 @@ files = [ [[package]] name = "wheel" -version = "0.43.0" +version = "0.44.0" description = "A built-package format for Python" optional = false python-versions = ">=3.8" files = [ - {file = "wheel-0.43.0-py3-none-any.whl", hash = "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81"}, - {file = "wheel-0.43.0.tar.gz", hash = "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85"}, + {file = "wheel-0.44.0-py3-none-any.whl", hash = "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f"}, + {file = "wheel-0.44.0.tar.gz", hash = "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49"}, ] [package.extras] @@ -3012,13 +3087,13 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] name = "zipp" -version = "3.19.2" +version = "3.20.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, + {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"}, + {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"}, ] [package.extras] diff --git a/portfolyo/_version.py b/portfolyo/_version.py index 597108b..97491ce 100644 --- a/portfolyo/_version.py +++ b/portfolyo/_version.py @@ -191,7 +191,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. - date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + date = date.strip().replace(" ", "min", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: @@ -375,7 +375,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] - pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + pieces["date"] = date.strip().replace(" ", "min", 1).replace(" ", "", 1) return pieces diff --git a/portfolyo/core/commodity/commodity.py b/portfolyo/core/commodity/commodity.py index 168f886..fafa035 100644 --- a/portfolyo/core/commodity/commodity.py +++ b/portfolyo/core/commodity/commodity.py @@ -21,7 +21,7 @@ def __post_init__(self): power = Commodity( - "15T", + "15min", tools.peakperiod.factory(dt.time(hour=8), dt.time(hour=20), [1, 2, 3, 4, 5]), 0, ) diff --git a/portfolyo/core/pfline/classes.py b/portfolyo/core/pfline/classes.py index 7e0b2bc..290e9e2 100644 --- a/portfolyo/core/pfline/classes.py +++ b/portfolyo/core/pfline/classes.py @@ -153,7 +153,7 @@ def po( ---------- peak_fn : PeakFunction Function that returns boolean Series indicating if timestamps in index lie in peak period. - freq : {'MS' (months, default), 'QS' (quarters), 'AS' (years)} + freq : {'MS' (months, default), 'QS' (quarters), 'YS' (years)} Frequency of resulting dataframe. Returns @@ -187,7 +187,7 @@ def hedge_with( To hedge with peak and offpeak products: function that returns boolean Series indicating if timestamps in index lie in peak period. If None, hedge with base products. - freq : {'D' (days), 'MS' (months, default), 'QS' (quarters), 'AS' (years)} + freq : {'D' (days), 'MS' (months, default), 'QS' (quarters), 'YS' (years)} Frequency of hedging products. E.g. 'QS' to hedge with quarter products. See also diff --git a/portfolyo/core/pfline/flat_methods.py b/portfolyo/core/pfline/flat_methods.py index ed8a117..ebceb0b 100644 --- a/portfolyo/core/pfline/flat_methods.py +++ b/portfolyo/core/pfline/flat_methods.py @@ -56,7 +56,7 @@ def hedge_with( raise ValueError( "Cannot hedge a PfLine that does not contain volume information." ) - if self.index.freq not in ["15T", "H", "D"]: + if self.index.freq not in ["15min", "h", "D"]: raise ValueError( "Can only hedge a PfLine with daily or (quarter)hourly information." ) diff --git a/portfolyo/core/pfstate/pfstate.py b/portfolyo/core/pfstate/pfstate.py index 0a0407a..4d0e578 100644 --- a/portfolyo/core/pfstate/pfstate.py +++ b/portfolyo/core/pfstate/pfstate.py @@ -209,8 +209,8 @@ def asfreq(self, freq: str = "MS") -> PfState: # from ABC Parameters ---------- freq : str, optional - The frequency at which to resample. 'AS' for year, 'QS' for quarter, 'MS' - (default) for month, 'D for day', 'H' for hour, '15T' for quarterhour. + The frequency at which to resample. 'YS' for year, 'QS' for quarter, 'MS' + (default) for month, 'D for day', 'h' for hour, '15min' for quarterhour. Returns ------- @@ -239,7 +239,7 @@ def hedge_of_unsourced( To hedge with peak and offpeak products: function that returns boolean Series indicating if timestamps in index lie in peak period. If None, hedge with base products. - freq : {'D' (days), 'MS' (months, default), 'QS' (quarters), 'AS' (years)} + freq : {'D' (days), 'MS' (months, default), 'QS' (quarters), 'YS' (years)} Frequency of hedging products. E.g. 'QS' to hedge with quarter products. See also @@ -271,7 +271,7 @@ def source_unsourced( To hedge with peak and offpeak products: function that returns boolean Series indicating if timestamps in index lie in peak period. If None, hedge with base products. - freq : {'D' (days), 'MS' (months, default), 'QS' (quarters), 'AS' (years)} + freq : {'D' (days), 'MS' (months, default), 'QS' (quarters), 'YS' (years)} Frequency of hedging products. E.g. 'QS' to hedge with quarter products. See also diff --git a/portfolyo/core/shared/ndframelike.py b/portfolyo/core/shared/ndframelike.py index ad9e512..96de82b 100644 --- a/portfolyo/core/shared/ndframelike.py +++ b/portfolyo/core/shared/ndframelike.py @@ -27,8 +27,8 @@ def asfreq(self, freq: str = "MS") -> NDFrameLike: Parameters ---------- freq : str, optional - The frequency at which to resample. 'AS' for year, 'QS' for quarter, 'MS' - (default) for month, 'D for day', 'H' for hour, '15T' for quarterhour. + The frequency at which to resample. 'YS' for year, 'QS' for quarter, 'MS' + (default) for month, 'D for day', 'h' for hour, '15min' for quarterhour. Returns ------- diff --git a/portfolyo/dev/develop.py b/portfolyo/dev/develop.py index 9a2e6b0..efe7af0 100644 --- a/portfolyo/dev/develop.py +++ b/portfolyo/dev/develop.py @@ -15,7 +15,7 @@ OK_COL_COMBOS = ["w", "q", "p", "pr", "qr", "qp", "wp", "wr"] -INDEX_LEN = {"AS": 4, "QS": 5, "MS": 14, "D": 400, "H": 10_000, "15T": 50_000} +INDEX_LEN = {"YS": 4, "QS": 5, "MS": 14, "D": 400, "h": 10_000, "15min": 50_000} def get_index( @@ -48,7 +48,7 @@ def get_index( start = tools.stamp.create(startdate, tz, start_of_day) i = pd.date_range(start, periods=periods, freq=freq) # tz included in start # Some checks. - if tools.freq.up_or_down(freq, "H") <= 0: + if tools.freq.up_or_down(freq, "h") <= 0: i = _shorten_index_if_necessary(i, start_of_day) return i diff --git a/portfolyo/dev/mockup.py b/portfolyo/dev/mockup.py index 210e69f..096d2bb 100644 --- a/portfolyo/dev/mockup.py +++ b/portfolyo/dev/mockup.py @@ -102,9 +102,9 @@ def p_marketprices( # week angle: Sun0:00..Sun0:00 -> 0..2pi. But: uniform within day. wa = i.map(lambda ts: ts.weekday() + 1) / 7 * np.pi * 2 # peak fraction: -1 (middle of offpeak hours) .. 1 (middle of peak hours) - if i.freq in ["H", "15T"]: + if i.freq in ["h", "15min"]: b = np.array([0.5, 0.8, 1, 0.8, 0.5]) - if i.freq == "15T": # repeat every value 4 times + if i.freq == "15min": # repeat every value 4 times b = np.array([[bb, bb, bb, bb] for bb in b]).flatten() b = b[: len(i)] # slice in case i is very short pa = np.convolve(-1 + 2 * germanpower_peakfn(i), b / sum(b), mode="same") @@ -167,7 +167,7 @@ def calc_wp(sub_s): def group_and_calc(s): return s.resample(freq, group_keys=False).apply(calc_wp) - if sin.index.freq in ["15T", "H"]: + if sin.index.freq in ["15min", "h"]: is_peak = germanpower_peakfn(sin.index) # avoid running on each ts individually df = sin.groupby(is_peak, group_keys=False).apply(group_and_calc) else: diff --git a/portfolyo/tools/ceil.py b/portfolyo/tools/ceil.py index a1cb631..e0eaea4 100644 --- a/portfolyo/tools/ceil.py +++ b/portfolyo/tools/ceil.py @@ -40,11 +40,11 @@ def stamp( Examples -------- - >>> ceil.stamp(pd.Timestamp('2020-04-21 15:42'), 'AS') + >>> ceil.stamp(pd.Timestamp('2020-04-21 15:42'), 'YS') Timestamp('2021-01-01 00:00:00') >>> ceil.stamp(pd.Timestamp('2020-04-21 15:42'), 'MS') Timestamp('2020-05-01 00:00:00') - >>> ceil.stamp(pd.Timestamp('2020-04-21 15:42'), '15T') + >>> ceil.stamp(pd.Timestamp('2020-04-21 15:42'), '15min') Timestamp('2020-04-21 15:45:00') >>> ceil.stamp(pd.Timestamp('2020-04-21 15:42', tz='Europe/Berlin'), 'MS') Timestamp('2020-05-01 00:00:00+0200', tz='Europe/Berlin') diff --git a/portfolyo/tools/duration.py b/portfolyo/tools/duration.py index 6d773c1..fd02fe6 100644 --- a/portfolyo/tools/duration.py +++ b/portfolyo/tools/duration.py @@ -30,8 +30,8 @@ def stamp(ts: pd.Timestamp, freq: str) -> tools_unit.Q_: >>> duration.stamp(pd.Timestamp('2020-03-29', tz='Europe/Berlin'), 'D') 23.0 h """ - if freq in ["15T", "H"]: - h = 1.0 if freq == "H" else 0.25 + if freq in ["15min", "h"]: + h = 1.0 if freq == "h" else 0.25 else: h = (tools_right.stamp(ts, freq) - ts).total_seconds() / 3600 return tools_unit.Q_(h, "h") @@ -50,9 +50,9 @@ def index(i: pd.DatetimeIndex) -> pd.Series: pint-Series With ``i`` as its index, and the corresponding duration as the values. """ - if i.freq in ["15T", "H"]: + if i.freq in ["15min", "h"]: # Speed-up things for fixed-duration frequencies. - h = 1.0 if i.freq == "H" else 0.25 + h = 1.0 if i.freq == "h" else 0.25 else: # Individual calculations for non-fixed-duration frequencies. h = (tools_right.index(i) - i).map(lambda td: td.total_seconds() / 3600) diff --git a/portfolyo/tools/floor.py b/portfolyo/tools/floor.py index 837f64a..2ce3e26 100644 --- a/portfolyo/tools/floor.py +++ b/portfolyo/tools/floor.py @@ -40,11 +40,11 @@ def stamp( Examples -------- - >>> floor.stamp(pd.Timestamp('2020-04-21 15:42'), 'AS') + >>> floor.stamp(pd.Timestamp('2020-04-21 15:42'), 'YS') Timestamp('2020-01-01 00:00:00') >>> floor.stamp(pd.Timestamp('2020-04-21 15:42'), 'MS') Timestamp('2020-04-01 00:00:00') - >>> floor.stamp(pd.Timestamp('2020-04-21 15:42'), '15T') + >>> floor.stamp(pd.Timestamp('2020-04-21 15:42'), '15min') Timestamp('2020-04-21 15:30:00') >>> floor.stamp(pd.Timestamp('2020-04-21 15:42', tz='Europe/Berlin'), 'MS') Timestamp('2020-04-01 00:00:00+0200', tz='Europe/Berlin') diff --git a/portfolyo/tools/freq.py b/portfolyo/tools/freq.py index 679cb2b..5dd1f66 100644 --- a/portfolyo/tools/freq.py +++ b/portfolyo/tools/freq.py @@ -9,7 +9,7 @@ # Allowed frequencies. -ALLOWED_FREQUENCIES_DOCS = "'15T' (=quarterhour), 'H', 'D', 'MS', 'QS' (or 'QS-FEB', 'QS-MAR', etc.), or 'AS' (or 'AS-FEB', 'AS-MAR', etc.)" +ALLOWED_FREQUENCIES_DOCS = "'15min' (=quarterhour), 'h', 'D', 'MS', 'QS' (or 'QS-FEB', 'QS-MAR', etc.), or 'YS' (or 'YS-FEB', 'YS-MAR', etc.)" ALLOWED_CLASSES = [ pd.tseries.offsets.YearBegin, pd.tseries.offsets.QuarterBegin, @@ -20,7 +20,7 @@ ] TO_OFFSET = pd.tseries.frequencies.to_offset SHORTEST_TO_LONGEST = [ - type(TO_OFFSET(freq)) for freq in ["15T", "H", "D", "MS", "QS", "AS"] + type(TO_OFFSET(freq)) for freq in ["15min", "h", "D", "MS", "QS", "YS"] ] quarter_matrix = [ @@ -35,7 +35,7 @@ def assert_freq_valid(freq: str) -> None: Validate if the given frequency string is allowed based on pandas offset objects. Parameters: - freq (str): A string representing a frequency alias (e.g., "AS", "QS", "MS"). + freq (str): A string representing a frequency alias (e.g., "YS", "QS", "MS"). Raises: ValueError: If the frequency is not allowed. @@ -231,8 +231,8 @@ def shortest(*freqs: str) -> str: Examples -------- - >>> freq.shortest('MS', 'H', 'AS', 'D') - 'H' + >>> freq.shortest('MS', 'h', 'YS', 'D') + 'h' """ return _longestshortest(True, *freqs) @@ -251,8 +251,8 @@ def longest(*freqs: str) -> str: Examples -------- - >>> freq.longest('MS', 'H', 'AS', 'D') - 'AS' + >>> freq.longest('MS', 'h', 'YS', 'D') + 'YS' """ return _longestshortest(False, *freqs) @@ -271,7 +271,7 @@ def to_offset(freq: str) -> pd.Timedelta | pd.DateOffset: Examples -------- - >>> freq.to_offset("H") + >>> freq.to_offset("h") Timedelta('0 days 01:00:00') >>> freq.to_offset("MS") @@ -312,9 +312,9 @@ def from_tdelta(tdelta: pd.Timedelta) -> str: One of {ALLOWED_FREQUENCIES_DOCS}. """ if tdelta == pd.Timedelta(minutes=15): - return "15T" + return "15min" elif tdelta == pd.Timedelta(hours=1): - return "H" + return "h" elif pd.Timedelta(hours=23) <= tdelta <= pd.Timedelta(hours=25): return "D" elif pd.Timedelta(days=27) <= tdelta <= pd.Timedelta(days=32): @@ -322,7 +322,7 @@ def from_tdelta(tdelta: pd.Timedelta) -> str: elif pd.Timedelta(days=89) <= tdelta <= pd.Timedelta(days=93): return "QS" elif pd.Timedelta(days=364) <= tdelta <= pd.Timedelta(days=367): - return "AS" + return "YS" else: raise ValueError( f"The timedelta ({tdelta}) doesn't seem to be fit to any of the allowed " diff --git a/portfolyo/tools/hedge.py b/portfolyo/tools/hedge.py index b728bbc..c49dbf0 100644 --- a/portfolyo/tools/hedge.py +++ b/portfolyo/tools/hedge.py @@ -52,7 +52,7 @@ def hedge( peak_fn : PeakFunction, optional (default: None) Function that returns boolean Series indicating if timestamps in index lie in peak period. If None, hedge with base products. - freq : {'D' (days), 'MS' (months), 'QS' (quarters), 'AS' (years)}, optional (default: 'MS') + freq : {'D' (days), 'MS' (months), 'QS' (quarters), 'YS' (years)}, optional (default: 'MS') Frequency of hedging products. E.g. 'QS' to hedge with quarter products. Returns @@ -68,13 +68,13 @@ def hedge( raise ValueError( f"Parameters ``w`` and ``p`` must have same frequency; got {w.index.freq} and {p.index.freq}." ) - if w.index.freq not in ["15T", "H", "D"]: + if w.index.freq not in ["15min", "h", "D"]: raise ValueError("Can only hedge a timeseries with daily (or shorter) values.") - if freq not in ["D", "MS", "QS", "AS"]: + if freq not in ["D", "MS", "QS", "YS"]: raise ValueError( - f"Parameter ``freq`` must be one of 'D', 'MS', 'QS', 'AS'; got '{freq}'." + f"Parameter ``freq`` must be one of 'D', 'MS', 'QS', 'YS'; got '{freq}'." ) - if peak_fn is not None and not (w.index.freq in ["15T", "H"] and freq != "D"): + if peak_fn is not None and not (w.index.freq in ["15min", "h"] and freq != "D"): raise ValueError( "Split into peak and offpeak only possible when (a) hedging with monthly (or " "longer) products, and (b) if timeseries have hourly (or shorter) values." diff --git a/portfolyo/tools/isboundary.py b/portfolyo/tools/isboundary.py index 1fe464a..a84525e 100644 --- a/portfolyo/tools/isboundary.py +++ b/portfolyo/tools/isboundary.py @@ -74,7 +74,7 @@ def is_X_start(i: pd.Timestamp | pd.DatetimeIndex, freq: str) -> bool | np.ndarr return is_month_start(i) elif freq == "QS": return is_quarter_start(i) - elif freq == "AS": + elif freq == "YS": return is_year_start(i) else: raise ValueError(f"Unexpected frequency ``freq``; got {freq}.") @@ -99,9 +99,9 @@ def stamp(ts: pd.Timestamp, freq: str, start_of_day: dt.time = None) -> bool: bool """ start_of_day = start_of_day or dt.time(0, 0) - if freq == "15T": + if freq == "15min": return ts.minute % 15 == 0 - elif freq == "H": + elif freq == "h": return ts.minute == 0 elif freq == "D": return ts.time() == start_of_day @@ -109,7 +109,7 @@ def stamp(ts: pd.Timestamp, freq: str, start_of_day: dt.time = None) -> bool: return (ts.time() == start_of_day) & is_month_start(ts) elif freq == "QS": return (ts.time() == start_of_day) & is_quarter_start(ts) - elif freq == "AS": + elif freq == "YS": return (ts.time() == start_of_day) & is_year_start(ts) else: raise ValueError(f"Unexpected frequency ``freq``; got {freq}.") @@ -148,9 +148,9 @@ def index(i: pd.DatetimeIndex, freq: str) -> pd.Series: values = is_X_start(i, freq) # Comparing shorter-than-daily index to other shorter-than-daily frequency X, - # (i.e., '15T' with 'H') - elif tools_freq.up_or_down(freq, "H") <= 0: - if i.freq == "15T" and freq == "H": + # (i.e., '15min' with 'h') + elif tools_freq.up_or_down(freq, "h") <= 0: + if i.freq == "15min" and freq == "h": values = i.minute == 0 else: raise ValueError( diff --git a/portfolyo/tools/leftandright.py b/portfolyo/tools/leftandright.py index 2049cee..d89e3d3 100644 --- a/portfolyo/tools/leftandright.py +++ b/portfolyo/tools/leftandright.py @@ -53,17 +53,17 @@ def stamps( if right is pd.NaT: if left is pd.NaT: - left = tools_floor.stamp(pd.Timestamp.now(tz=tz), "AS", 1, start_of_day) - right = tools_floor.stamp(left, "AS", 1, left.time()) + left = tools_floor.stamp(pd.Timestamp.now(tz=tz), "YS", 1, start_of_day) + right = tools_floor.stamp(left, "YS", 1, left.time()) # if we land here, we at least know right. if left is pd.NaT: start_of_day = right.time() - if tools_isboundary.stamp(right, "AS", start_of_day): + if tools_isboundary.stamp(right, "YS", start_of_day): back = -1 else: back = 0 - left = tools_floor.stamp(right, "AS", back, start_of_day) + left = tools_floor.stamp(right, "YS", back, start_of_day) # if we land here, we know left and right. zones = [None if ts.tz is None else ts.tz.zone for ts in [left, right]] diff --git a/portfolyo/tools/peakconvert.py b/portfolyo/tools/peakconvert.py index 7729f64..67f32fa 100644 --- a/portfolyo/tools/peakconvert.py +++ b/portfolyo/tools/peakconvert.py @@ -24,11 +24,11 @@ def group_index( groups.append(i.month) elif freq == "QS": groups.append(i.quarter) - elif freq == "AS": + elif freq == "YS": pass else: raise ValueError( - f"Parameter ``freq`` must be one of 'MS', 'QS', 'AS'; got '{freq}'." + f"Parameter ``freq`` must be one of 'MS', 'QS', 'YS'; got '{freq}'." ) # Add grouping due to peak. @@ -48,7 +48,7 @@ def complete_bpoframe( df : DataFrame With at least 2 of following columns: {'base', 'peak', 'offpeak'}. If all 3 are present, no verification is done. Additional columns are dropped. DatetimeIndex - with freq in {'MS', 'QS', 'AS'}. + with freq in {'MS', 'QS', 'YS'}. peak_fn : PeakFunction Function that returns boolean Series indicating if timestamps in index lie in peak period. is_summable : bool, optional (default: False) @@ -233,7 +233,7 @@ def tseries2poframe( def poframe2tseries( df: pd.DataFrame, peak_fn: tools_peakfn.PeakFunction, - freq: str = "H", + freq: str = "h", is_summable: bool = False, ) -> pd.Series: """ @@ -310,7 +310,7 @@ def poframe2tseries( # Timeseries with hourly or quarterhourly frequency. # peak_fn : PeakFunction, optional (default: None) # Function that returns boolean Series indicating if timestamps in index lie in peak period. -# freq : {'MS' (month, default) 'QS' (quarter), 'AS' (year)} +# freq : {'MS' (month, default) 'QS' (quarter), 'YS' (year)} # Target frequency within which peak and offpeak values will be uniform. # is_summable : bool, optional (default: False) # True if data is summable, False if it is averagable. @@ -367,7 +367,7 @@ def poframe2tseries( def poframe2poframe( df: pd.DataFrame, peak_fn: tools_peakfn.PeakFunction, - freq: str = "AS", + freq: str = "YS", is_summable: bool = False, ) -> pd.DataFrame: """ @@ -381,7 +381,7 @@ def poframe2poframe( or-longer frequency. peak_fn : PeakFunction Function that returns boolean Series indicating if timestamps in index lie in peak period. - freq : str, optional (default: 'AS') + freq : str, optional (default: 'YS') Target frequency; monthly-or-longer. is_summable : bool, optional (default: False) True if data is summable, False if it is averagable. @@ -421,6 +421,6 @@ def poframe2poframe( " The result will be uniform at the frequency of the original frame ``df``." ) - upsampled = poframe2tseries(df, peak_fn, "H", is_summable) + upsampled = poframe2tseries(df, peak_fn, "h", is_summable) downsampled = tseries2poframe(upsampled, peak_fn, freq, is_summable) return downsampled diff --git a/portfolyo/tools/peakfn.py b/portfolyo/tools/peakfn.py index 7e933d7..2b327ac 100644 --- a/portfolyo/tools/peakfn.py +++ b/portfolyo/tools/peakfn.py @@ -72,9 +72,9 @@ def factory( if not must_check_time: longest_freq = "D" elif peak_left.minute == 0 and peak_right.minute == 0: - longest_freq = "H" + longest_freq = "h" elif peak_left.minute % 15 == 0 and peak_right.minute % 15 == 0: - longest_freq = "15T" + longest_freq = "15min" else: raise ValueError( f"Input specifies times that are not 'round' quarter-hours; got {peak_left} and {peak_right}." @@ -144,7 +144,7 @@ def peak_duration(i: pd.DatetimeIndex, peak_fn: PeakFunction) -> pd.Series: Series has the original index. """ eval_i = i # index to evaluate if peak or offpeak - for eval_freq in ("D", "H", "15T"): + for eval_freq in ("D", "h", "15min"): if tools_freq.up_or_down(eval_i.freq, eval_freq) > 0: # upsampling necessary eval_i = tools_changefreq.index(eval_i, eval_freq) try: diff --git a/portfolyo/tools/product.py b/portfolyo/tools/product.py index c9fbb0e..ac6597a 100644 --- a/portfolyo/tools/product.py +++ b/portfolyo/tools/product.py @@ -46,7 +46,7 @@ def delivery_period( Left (inclusive) and right (exclusive) timestamp of delivery period. """ ts_trade = ts_trade.replace(hour=23, minute=59) # ensure after start_of_day - if period_type in ["m", "q", "a"]: + if period_type in ["m", "q", "y"]: freq = period_type.upper() + "S" ts_left = tools_floor.stamp(ts_trade, freq, front_count, start_of_day) ts_right = tools_right.stamp(ts_left, freq) @@ -62,6 +62,6 @@ def delivery_period( ts_right = tools_right.stamp(ts_right, "QS") else: raise ValueError( - f"Parameter ``period_type`` must be one of 'd', 'm', 'q', 's', 'a'; got '{period_type}'." + f"Parameter ``period_type`` must be one of 'd', 'm', 'q', 's', 'y'; got '{period_type}'." ) return ts_left, ts_right diff --git a/portfolyo/tools/round.py b/portfolyo/tools/round.py index 64b59d4..49329a4 100644 --- a/portfolyo/tools/round.py +++ b/portfolyo/tools/round.py @@ -81,7 +81,7 @@ def stamp_current( else: part_of_prevday = ts.time() < start_of_day rounded = ts.replace(hour=start_of_day.hour, minute=start_of_day.minute).floor( - "15T" + "15min" ) if part_of_prevday and fn == "floor": rounded -= tools_freq.to_offset("D") @@ -155,9 +155,9 @@ def stamp_current( def _offset(freq: str, future: int): - if freq == "15T": + if freq == "15min": return pd.Timedelta(minutes=future * 15) - elif freq == "H": + elif freq == "h": return pd.Timedelta(hours=future) elif freq == "D": return pd.Timedelta(days=future) @@ -165,7 +165,7 @@ def _offset(freq: str, future: int): return pd.offsets.MonthBegin(future) elif freq == "QS": return pd.offsets.QuarterBegin(future, startingMonth=1) - elif freq == "AS": + elif freq == "YS": return pd.offsets.YearBegin(future) else: raise ValueError( diff --git a/portfolyo/tools/standardize.py b/portfolyo/tools/standardize.py index bc5ea1c..487fcc8 100644 --- a/portfolyo/tools/standardize.py +++ b/portfolyo/tools/standardize.py @@ -182,7 +182,7 @@ def assert_index_standardized(i: pd.DatetimeIndex, __right: bool = False): raise AssertionError("Index must have values; got empty index.") # Check hour and minute. - if tools_freq.up_or_down(freq, "15T") <= 0: # quarterhour + if tools_freq.up_or_down(freq, "15min") <= 0: # quarterhour startminute = 15 if __right else 0 if i[0].minute != startminute: err = ("right-bound", "15 min past the") if __right else ("", "at a full") @@ -203,7 +203,7 @@ def assert_index_standardized(i: pd.DatetimeIndex, __right: bool = False): ) # Check time-of-day. - if tools_freq.up_or_down(freq, "H") <= 0: # hour or shorter + if tools_freq.up_or_down(freq, "h") <= 0: # hour or shorter if not __right: start = i[0] end = tools_right.stamp(i[-1], i.freq) @@ -229,7 +229,7 @@ def assert_index_standardized(i: pd.DatetimeIndex, __right: bool = False): period, not_ok = "month", ~i.is_month_start elif freq == "QS": period, not_ok = "quarter", ~i.is_quarter_start - elif freq == "AS": + elif freq == "YS": period, not_ok = "year", ~i.is_year_start if any(not_ok): raise AssertionError( diff --git a/portfolyo/tools/testing.py b/portfolyo/tools/testing.py index b3a9933..ed5c241 100644 --- a/portfolyo/tools/testing.py +++ b/portfolyo/tools/testing.py @@ -64,9 +64,9 @@ def assert_indices_compatible(left: pd.DatetimeIndex, right: pd.DatetimeIndex): def assert_w_q_compatible(freq: str, w: pd.Series, q: pd.Series): """Assert if timeseries with power- and energy-values are consistent.""" - if freq == "15T": + if freq == "15min": assert_series_equal(q, w * tools_unit.Q_(0.25, "h"), check_names=False) - elif freq == "H": + elif freq == "h": assert_series_equal(q, w * tools_unit.Q_(1.0, "h"), check_names=False) elif freq == "D": assert (q >= w * tools_unit.Q_(22.99, "h")).all() @@ -77,7 +77,7 @@ def assert_w_q_compatible(freq: str, w: pd.Series, q: pd.Series): elif freq == "QS": assert (q >= w * 89 * tools_unit.Q_(24.0, "h")).all() assert (q <= w * 93 * tools_unit.Q_(24.0, "h")).all() - elif freq == "AS": + elif freq == "YS": assert (q >= w * tools_unit.Q_(8759.9, "h")).all() assert (q <= w * tools_unit.Q_(8784.1, "h")).all() else: diff --git a/portfolyo/tools/unit.py b/portfolyo/tools/unit.py index 18c259c..717faba 100644 --- a/portfolyo/tools/unit.py +++ b/portfolyo/tools/unit.py @@ -4,11 +4,13 @@ from pathlib import Path from typing import Tuple, overload -from .types import Series_or_DataFrame + import pandas as pd import pint import pint_pandas +from .types import Series_or_DataFrame + path = Path(__file__).parent / "unitdefinitions.txt" @@ -18,7 +20,7 @@ auto_reduce_dimensions=True, case_sensitive=False, ) -ureg.default_format = "~P" # short by default +ureg.formatter.default_format = "~P" # short by default ureg.setup_matplotlib() # Set for export. @@ -185,23 +187,21 @@ def avoid_frame_of_objects(fr: Series_or_DataFrame) -> Series_or_DataFrame: # fr is now a Series. - if fr.dtype == int: + if pd.api.types.is_integer_dtype(fr): return fr.astype(float) - if fr.dtype == float: + if pd.api.types.is_float_dtype(fr): return fr if hasattr(fr, "pint"): if isinstance(fr.dtype, pint_pandas.PintType): return fr # We may have a series of pint quantities. Convert to pint-series, if possible. - dimensions = {v.dimensionality for v in fr.values} - if len(dimensions) != 1: - raise ValueError( + try: + return fr.astype(f"pint[{fr.iloc[0].units}]") + except pint.DimensionalityError as e: + dimensions = {v.dimensionality for v in fr.values} + raise pint.DimensionalityError( f"Expected a Series with quantities of the same dimension; got {dimensions}." - ) - # Convert all values to same unit. - units = fr.values[0].units - magnitudes = [v.to(units).magnitude for v in fr.values] - return pd.Series(magnitudes, fr.index, dtype=f"pint[{units}]") + ) from e raise TypeError( "Expected int-Series, float-Series, pint-Series, or Series of pint quantities (of equal dimensionality)." ) diff --git a/portfolyo/tools/visualize/categories.py b/portfolyo/tools/visualize/categories.py index 0136710..f31e2c4 100644 --- a/portfolyo/tools/visualize/categories.py +++ b/portfolyo/tools/visualize/categories.py @@ -58,7 +58,7 @@ def short_labels(self, max_count: int = None) -> Iterable[str]: def create_categories(s: pd.Series) -> Iterable[Category]: - if s.index.freq == "AS": + if s.index.freq == "YS": prios = priolist(len(s)) def category(i, ts, y): diff --git a/pyproject.toml b/pyproject.toml index 5642af4..2df8986 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "portfolyo" -version = "0.6.0" +version = "0.6.1" description = "Analyse and manipulate timeseries related to power and gas offtake portfolios" authors = [ "Ruud Wijtvliet ", diff --git a/tests/core/pfline/test_flat.py b/tests/core/pfline/test_flat.py index 5da45e0..f57b1ed 100644 --- a/tests/core/pfline/test_flat.py +++ b/tests/core/pfline/test_flat.py @@ -77,7 +77,7 @@ def test_flatpfline_access(columns: str, available: str, constructor: type): series = {} -for freq in ["MS", "D", "15T"]: +for freq in ["MS", "D", "15min"]: idx = pd.date_range( "2020", "2020-04", freq=freq, inclusive="left", tz="Europe/Berlin" ) @@ -87,8 +87,8 @@ def test_flatpfline_access(columns: str, available: str, constructor: type): series[freq] = {"i": idx, "w": w, "q": q, "p": p, "r": q * p} -@pytest.mark.parametrize("freq_in", ["MS", "D", "15T"]) -@pytest.mark.parametrize("freq_out", ["MS", "D", "15T"]) +@pytest.mark.parametrize("freq_in", ["MS", "D", "15min"]) +@pytest.mark.parametrize("freq_out", ["MS", "D", "15min"]) @pytest.mark.parametrize("columns", ["w", "q", "p", "pw", "wr"]) def test_flatpfline_asfreqcorrect1(freq_in: str, freq_out: str, columns: str): """Test if changing frequency is done correctly (when it's possible), for uniform pflines.""" @@ -101,8 +101,8 @@ def test_flatpfline_asfreqcorrect1(freq_in: str, freq_out: str, columns: str): # . check correct working of attributes .asfreq(). @pytest.mark.only_on_pr @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) -@pytest.mark.parametrize("freq", ["H", "D", "MS", "QS", "AS"]) -@pytest.mark.parametrize("newfreq", ["H", "D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq", ["h", "D", "MS", "QS", "YS"]) +@pytest.mark.parametrize("newfreq", ["h", "D", "MS", "QS", "YS"]) @pytest.mark.parametrize("columns", ["pr", "qr", "pq", "wp", "wr"]) def test_flatpfline_asfreqcorrect2(freq, newfreq, columns, tz): """Test if changing frequency is done correctly (when it's possible).""" @@ -137,8 +137,8 @@ def test_flatpfline_asfreqcorrect2(freq, newfreq, columns, tz): testing.assert_series_equal(df1.apply(np.sum), df2.apply(np.sum)) -@pytest.mark.parametrize("freq", ["15T", "H", "D"]) -@pytest.mark.parametrize("newfreq", ["MS", "QS", "AS"]) +@pytest.mark.parametrize("freq", ["15min", "h", "D"]) +@pytest.mark.parametrize("newfreq", ["MS", "QS", "YS"]) @pytest.mark.parametrize("kind", [Kind.COMPLETE, Kind.VOLUME, Kind.PRICE]) def test_flatpfline_asfreqimpossible(freq, newfreq, kind): """Test if changing frequency raises error if it's impossible.""" diff --git a/tests/core/pfline/test_flat_helper.py b/tests/core/pfline/test_flat_helper.py index 1f80b62..0de8865 100644 --- a/tests/core/pfline/test_flat_helper.py +++ b/tests/core/pfline/test_flat_helper.py @@ -7,16 +7,16 @@ from portfolyo.core.pfline import flat_helper TEST_FREQUENCIES = [ - "AS", - "AS-FEB", - "AS-APR", + "YS", + "YS-FEB", + "YS-APR", "QS", "QS-FEB", "QS-APR", "MS", "D", - "H", - "15T", + "h", + "15min", ] @@ -160,8 +160,8 @@ def test_makedataframe_consistency(tz, freq, columns, inputtype): testing.assert_frame_equal(result, expected) -@pytest.mark.parametrize("freq1", ["15T", "MS", "AS"]) # don't do all - many! -@pytest.mark.parametrize("freq2", ["H", "D", "QS"]) +@pytest.mark.parametrize("freq1", ["15min", "MS", "YS"]) # don't do all - many! +@pytest.mark.parametrize("freq2", ["h", "D", "QS"]) @pytest.mark.parametrize("columns", ["rp", "wp", "pq", "qr", "wr"]) def test_makedataframe_unequalfrequencies(freq1, freq2, columns): """Test if error is raised when creating a dataframe from series with unequal frequencies.""" @@ -187,7 +187,7 @@ def test_makedataframe_unequalfrequencies(freq1, freq2, columns): @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) -@pytest.mark.parametrize("freq", ["15T", "H", "D", "MS"]) +@pytest.mark.parametrize("freq", ["15min", "h", "D", "MS"]) def test_makedataframe_unequalstartofday(freq: str, tz: str): """Test if error is raised for series with unequal starttimes.""" @@ -203,7 +203,7 @@ def test_makedataframe_unequalstartofday(freq: str, tz: str): @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) -@pytest.mark.parametrize("freq", ["15T", "H", "D", "MS"]) +@pytest.mark.parametrize("freq", ["15min", "h", "D", "MS"]) @pytest.mark.parametrize("overlap", [True, False]) def test_makedataframe_unequaltimeperiods(freq: str, overlap: bool, tz: str): """Test if only intersection is kept for overlapping series, and error is raised diff --git a/tests/core/pfline/test_nested_helper.py b/tests/core/pfline/test_nested_helper.py index 3ccf994..ee99d63 100644 --- a/tests/core/pfline/test_nested_helper.py +++ b/tests/core/pfline/test_nested_helper.py @@ -42,8 +42,8 @@ def test_verifydict_kindconsistency(freq, kind1, kind2, kind3): @pytest.mark.only_on_pr -@pytest.mark.parametrize("freq1", ["15T", "D", "MS", "QS"]) # don't do all - many! -@pytest.mark.parametrize("freq2", ["15T", "H", "D", "MS", "QS"]) +@pytest.mark.parametrize("freq1", ["15min", "D", "MS", "QS"]) # don't do all - many! +@pytest.mark.parametrize("freq2", ["15min", "h", "D", "MS", "QS"]) def test_verifydict_frequencyconsistency(freq1, freq2): """Test if error is raised when creating a dictionary from pflines with unequal frequencies.""" @@ -72,7 +72,7 @@ def test_verifydict_frequencyconsistency(freq1, freq2): @pytest.mark.only_on_pr -@pytest.mark.parametrize("freq", ["15T", "H", "D", "MS"]) +@pytest.mark.parametrize("freq", ["15min", "h", "D", "MS"]) @pytest.mark.parametrize("overlap", [True, False]) def test_verifydict_unequaltimeperiods(freq, overlap): """Test if only intersection is kept for overlapping pflines, and error is raised diff --git a/tests/core/pfline/test_pfline_init.py b/tests/core/pfline/test_pfline_init.py index fdea8a6..68f590f 100644 --- a/tests/core/pfline/test_pfline_init.py +++ b/tests/core/pfline/test_pfline_init.py @@ -13,16 +13,16 @@ from portfolyo.core.pfline import classes TEST_FREQUENCIES = [ - "15T", - "H", + "15min", + "h", "D", "MS", "QS", "QS-FEB", "QS-APR", - "AS", - "AS-FEB", - "AS-APR", + "YS", + "YS-FEB", + "YS-APR", ] @@ -269,7 +269,7 @@ def test_init_with_integers(col: str): @pytest.mark.parametrize("inclusive", ["left", "both"]) -@pytest.mark.parametrize("freq", ["15T", "H"]) +@pytest.mark.parametrize("freq", ["15min", "h"]) def test_contain_whole_day(inclusive: str, freq: str): """An index must contain full days. For hourly-or-shorter values, this means that the start time of the first period () must equal the end time of the @@ -287,7 +287,7 @@ def test_contain_whole_day(inclusive: str, freq: str): pfl = dev.get_flatpfline(index) -@pytest.mark.parametrize("freq", ["D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq", ["D", "MS", "QS", "YS"]) def test_equal_sod(freq: str): """In an index with daily-or-longer values, all timestamps (all periods) should start at the same time .""" i = pd.date_range("2024-03-28", freq=freq, periods=10, tz="Europe/Berlin") diff --git a/tests/core/pfline/test_slice.py b/tests/core/pfline/test_slice.py index 87b8f6e..0f5ca1e 100644 --- a/tests/core/pfline/test_slice.py +++ b/tests/core/pfline/test_slice.py @@ -23,7 +23,7 @@ def get_idx( return pd.date_range(ts_start, ts_end, freq=freq, inclusive=inclusive) -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize( "slice_start", ["2021", "2022", "2022-01-02", "2022-05-23 14:34"] ) @@ -41,7 +41,7 @@ def test_flat_slice_start( @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize( "slice_end", [ @@ -62,7 +62,7 @@ def test_flat_slice_end(slice_end: str, freq: str, tz: str, sod: str, inclusive: assert pfl1.slice[: slice_end[0]] == pfl1.loc[: slice_end[1]] -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize( "where", ["2022", "2022-03", "2022-04-21", "2022-05-23 14:34"], @@ -85,7 +85,7 @@ def test_flat_slice_whole(where: str, freq: str, tz: str, sod: str, inclusive: s assert len(left.index.intersection(right.index)) == 0 -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize( "slice_start", [ @@ -108,7 +108,7 @@ def test_nested_slice_start( assert pfl1.slice[slice_start:] == pfl1.loc[slice_start:] -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) @pytest.mark.parametrize( "slice_end", @@ -130,7 +130,7 @@ def test_nested_slice_end(slice_end: str, freq: str, tz: str, sod: str, inclusiv assert pfl1.slice[: slice_end[0]] == pfl1.loc[: slice_end[1]] -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) @pytest.mark.parametrize( "where", @@ -153,7 +153,7 @@ def test_nested_slice_whole(where: str, freq: str, tz: str, sod: str, inclusive: assert len(left.index.intersection(right.index)) == 0 -@pytest.mark.parametrize("freq", ["H", "15T"]) +@pytest.mark.parametrize("freq", ["h", "15min"]) @pytest.mark.parametrize("sod", ["00:00", "06:00"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) @pytest.mark.parametrize("startdate", ["2021", "2022", "2022-01-02"]) @@ -179,7 +179,7 @@ def test__start_less_than_daily(startdate: str, freq: str, tz: str, sod: str): ) def test__end_less_than_daily(enddate: str, tz: str): index = get_idx( - "2020", starttime="00:00", enddate="2024", freq="15T", inclusive="left", tz=tz + "2020", starttime="00:00", enddate="2024", freq="15min", inclusive="left", tz=tz ) pfl1 = dev.get_flatpfline(index) slice_end = f"{enddate[0]} 00:00" diff --git a/tests/core/pfstate/test_pfstate_init.py b/tests/core/pfstate/test_pfstate_init.py index fa59ac5..931b261 100644 --- a/tests/core/pfstate/test_pfstate_init.py +++ b/tests/core/pfstate/test_pfstate_init.py @@ -18,7 +18,7 @@ s_less = dev.get_series(i_less, "") i_more = pd.date_range("2019-12-15", freq="D", periods=100) s_more = dev.get_series(i_more, "") -i_difffreq = pd.date_range("2020", freq="H", periods=24 * 5) +i_difffreq = pd.date_range("2020", freq="h", periods=24 * 5) s_difffreq = dev.get_series(i_difffreq, "") @@ -509,7 +509,7 @@ def test_pfstate_consistency_nosourcing(): @pytest.mark.parametrize("inclusive", ["left", "both"]) -@pytest.mark.parametrize("freq", ["15T", "H"]) +@pytest.mark.parametrize("freq", ["15min", "h"]) def test_contain_whole_day(inclusive: str, freq: str): """An index must contain full days. For hourly-or-shorter values, this means that the start time of the first period () must equal the end time of the diff --git a/tests/core/pfstate/test_slice_state.py b/tests/core/pfstate/test_slice_state.py index a3e0e0c..0e9835a 100644 --- a/tests/core/pfstate/test_slice_state.py +++ b/tests/core/pfstate/test_slice_state.py @@ -23,7 +23,7 @@ def get_idx( return pd.date_range(ts_start, ts_end, freq=freq, inclusive=inclusive) -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize("slice_start", ["2021", "2022", "2022-01-02"]) @pytest.mark.parametrize( "slice_end", @@ -55,7 +55,7 @@ def test_slice_state( assert pfs_to_concat == pfs_to_concat2 -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize( "slice_start", [ @@ -82,7 +82,7 @@ def test_state_slice_start( assert pfs.slice[slice_start:] == pfs.loc[slice_start:] -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize( "slice_end", [ @@ -104,7 +104,7 @@ def test_state_slice_end(slice_end: str, freq: str, sod: str, inclusive: str, tz assert pfs.slice[: slice_end[0]] == pfs.loc[: slice_end[1]] -@pytest.mark.parametrize("freq", ["MS", "AS", "QS", "D"]) +@pytest.mark.parametrize("freq", ["MS", "YS", "QS", "D"]) @pytest.mark.parametrize( "where", ["2022", "2022-03", "2022-04-21", "2022-05-23 14:34"], @@ -127,7 +127,7 @@ def test_state_slice_whole(where: str, freq: str, sod: str, inclusive: str, tz: assert len(left.index.intersection(right.index)) == 0 -@pytest.mark.parametrize("freq", ["H", "15T"]) +@pytest.mark.parametrize("freq", ["h", "15min"]) @pytest.mark.parametrize("sod", ["00:00", "06:00"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) @pytest.mark.parametrize("startdate", ["2021", "2022", "2022-01-02"]) @@ -154,7 +154,7 @@ def test__start_less_than_daily(startdate: str, freq: str, tz: str, sod: str): # @pytest.mark.parametrize("sod", ["00:00", "06:00"]) def test__end_less_than_daily(enddate: str, tz: str): index = get_idx( - "2020", starttime="00:00", enddate="2024", freq="15T", inclusive="left", tz=tz + "2020", starttime="00:00", enddate="2024", freq="15min", inclusive="left", tz=tz ) pfl1 = dev.get_pfstate(index) slice_end = f"{enddate[0]} 00:00" diff --git a/tests/dev/test_develop.py b/tests/dev/test_develop.py index 8e1cd51..51be1b2 100644 --- a/tests/dev/test_develop.py +++ b/tests/dev/test_develop.py @@ -13,12 +13,12 @@ ("freq", "startdate", "start_of_day"), [ (None, None, None), - ("H", "2020", None), + ("h", "2020", None), ("D", "2020", None), ("MS", "2020", None), ("QS", "2020", None), - ("AS", "2020", None), - ("H", "2020-04-21", dt.time(hour=15)), + ("YS", "2020", None), + ("h", "2020-04-21", dt.time(hour=15)), ("D", "2020-04-21", None), ("MS", "2020-04", None), ("QS", "2020-04", None), @@ -39,11 +39,11 @@ def test_index(freq, tz, startdate, start_of_day): ("freq", "startdate", "start_of_day"), [ (None, None, None), - ("H", "2020-04-21", dt.time(hour=15)), + ("h", "2020-04-21", dt.time(hour=15)), ("D", "2020-04-21", None), ("MS", "2020-04", None), ("QS", "2020-04", None), - ("AS", "2020", None), + ("YS", "2020", None), ], ) def test_series(freq, tz, startdate, name, name_has_unit, request_unit, start_of_day): @@ -79,11 +79,11 @@ def test_series(freq, tz, startdate, name, name_has_unit, request_unit, start_of ("freq", "startdate", "start_of_day"), [ (None, None, None), - ("H", "2020-04-21", dt.time(hour=15)), + ("h", "2020-04-21", dt.time(hour=15)), ("D", "2020-04-21", None), ("MS", "2020-04", None), ("QS", "2020-04", None), - ("AS", "2020", None), + ("YS", "2020", None), ], ) def test_dataframe( @@ -109,11 +109,11 @@ def test_dataframe( ("freq", "startdate", "start_of_day"), [ (None, None, None), - ("H", "2020-04-21", dt.time(hour=15)), + ("h", "2020-04-21", dt.time(hour=15)), ("D", "2020-04-21", None), ("MS", "2020-04", None), ("QS", "2020-04", None), - ("AS", "2020", None), + ("YS", "2020", None), ], ) def test_flatnestedpfline(freq, tz, startdate, kind, start_of_day): @@ -134,11 +134,11 @@ def test_flatnestedpfline(freq, tz, startdate, kind, start_of_day): ("freq", "startdate", "start_of_day"), [ (None, None, None), - ("H", "2020-04-21", dt.time(hour=15)), + ("h", "2020-04-21", dt.time(hour=15)), ("D", "2020-04-21", None), ("MS", "2020-04", None), ("QS", "2020-04", None), - ("AS", "2020", None), + ("YS", "2020", None), ], ) def test_pfline(freq, tz, startdate, kind, max_nlevels, start_of_day): diff --git a/tests/tools/prices/test_convert.py.bak b/tests/tools/prices/test_convert.py.bak index 77ad906..520df45 100644 --- a/tests/tools/prices/test_convert.py.bak +++ b/tests/tools/prices/test_convert.py.bak @@ -21,7 +21,7 @@ def get_df_fromexcel(tz, aggfreq) -> pd.DataFrame: @pytest.mark.parametrize("withunit", [True, False]) -@pytest.mark.parametrize("aggfreq", ["MS", "QS", "AS"]) +@pytest.mark.parametrize("aggfreq", ["MS", "QS", "YS"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) def test_pbaseppeakpoffpeak_fromexcel(tz: str, aggfreq: str, withunit: bool): """Test if base, peak and offpeak value for a period can be calculated from the other two.""" @@ -40,7 +40,7 @@ def test_pbaseppeakpoffpeak_fromexcel(tz: str, aggfreq: str, withunit: bool): assert np.isclose(convert.base(p, o, ts_left, aggfreq), b) -@pytest.mark.parametrize("aggfreq", ["MS", "QS", "AS"]) +@pytest.mark.parametrize("aggfreq", ["MS", "QS", "YS"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) def test_completebpoframe_fromexcel(tz: str, aggfreq: str): """Test if a partial bpoframe can be completed to a full bpoframe.""" @@ -52,8 +52,8 @@ def test_completebpoframe_fromexcel(tz: str, aggfreq: str): testing.assert_frame_equal(bpoframe_result, bpoframe) -@pytest.mark.parametrize("aggfreq", ["MS", "QS", "AS"]) -@pytest.mark.parametrize("freq", ["15T", "H"]) +@pytest.mark.parametrize("aggfreq", ["MS", "QS", "YS"]) +@pytest.mark.parametrize("freq", ["15min", "h"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) def test_tseries2singlebpo_fromexcel(tz, freq, aggfreq): """Test if a timeseries can be converted into single base, peak and offpeak values.""" @@ -70,8 +70,8 @@ def test_tseries2singlebpo_fromexcel(tz, freq, aggfreq): assert np.isclose(values_result[key], values_expected[key]) -@pytest.mark.parametrize("aggfreq", ["MS", "QS", "AS"]) -@pytest.mark.parametrize("freq", ["15T", "H"]) +@pytest.mark.parametrize("aggfreq", ["MS", "QS", "YS"]) +@pytest.mark.parametrize("freq", ["15min", "h"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) def test_tseries2bpoframe_fromexcel(tz, freq, aggfreq): """Test if a timeseries (i.e., long) can be converted into a bpo-frame (i.e., wide).""" @@ -83,8 +83,8 @@ def test_tseries2bpoframe_fromexcel(tz, freq, aggfreq): testing.assert_frame_equal(bpoframe_result, bpoframe_expected) -@pytest.mark.parametrize("aggfreq", ["MS", "QS", "AS"]) -@pytest.mark.parametrize("freq", ["15T", "H"]) +@pytest.mark.parametrize("aggfreq", ["MS", "QS", "YS"]) +@pytest.mark.parametrize("freq", ["15min", "h"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) def test_bpoframe2timeseries_fromexcel(tz, freq, aggfreq, drop): """Test if a bpoframe (i.e., wide) can be converted into a timeseries (i.e., long).""" @@ -100,10 +100,10 @@ def test_bpoframe2timeseries_fromexcel(tz, freq, aggfreq, drop): [ ("MS", "MS"), ("MS", "QS"), - ("MS", "AS"), + ("MS", "YS"), ("QS", "QS"), - ("QS", "AS"), - ("AS", "AS"), + ("QS", "YS"), + ("YS", "YS"), ], ) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) @@ -124,13 +124,13 @@ def test_bpoframe2bpoframe_fromexcel(tz, short_aggfreq, long_aggfreq): [ ("MS", "MS"), ("MS", "QS"), - ("MS", "AS"), + ("MS", "YS"), ("QS", "QS"), - ("QS", "AS"), - ("AS", "AS"), + ("QS", "YS"), + ("YS", "YS"), ], ) -@pytest.mark.parametrize("freq", ["15T", "H"]) +@pytest.mark.parametrize("freq", ["15min", "h"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) def test_tseries2tseries_fromexcel(tz, freq, short_aggfreq, long_aggfreq, po): """Test if a (e.g., hourly) timeseries with a short aggregation frequency (e.g., months) diff --git a/tests/tools/test_changefreq.py b/tests/tools/test_changefreq.py index c9c3ff9..c249ede 100644 --- a/tests/tools/test_changefreq.py +++ b/tests/tools/test_changefreq.py @@ -18,9 +18,9 @@ def utc(i): else: return [ts.utcoffset().total_seconds() for ts in i] - if longestfreq == "15T": + if longestfreq == "15min": return lambda i: pd.Index(zip(i.year, i.month, i.day, i.hour, i.minute, utc(i))) - if longestfreq == "H": + if longestfreq == "h": return lambda i: pd.Index(zip(i.year, i.month, i.day, i.hour, utc(i))) if longestfreq == "D": return lambda i: pd.Index(zip(i.year, i.month, i.day)) @@ -28,7 +28,7 @@ def utc(i): return lambda i: pd.Index(zip(i.year, i.month)) if longestfreq == "QS": return lambda i: pd.Index(zip(i.year, i.quarter)) - if longestfreq == "AS": + if longestfreq == "YS": return lambda i: i.year @@ -61,9 +61,9 @@ def day(i): mask = d == 0 return d + mask * numofdaysinprevmonth(i) - if longestfreq == "15T": + if longestfreq == "15min": return lambda i: pd.Index(zip(i.year, i.month, i.day, i.hour, i.minute, utc(i))) - if longestfreq == "H": + if longestfreq == "h": return lambda i: pd.Index(zip(i.year, i.month, i.day, i.hour, utc(i))) if longestfreq == "D": return lambda i: pd.Index(zip(year(i), month(i), day(i))) @@ -71,7 +71,7 @@ def day(i): return lambda i: pd.Index(zip(year(i), month(i))) if longestfreq == "QS": return lambda i: pd.Index(zip(year(i), quarter(i))) - if longestfreq == "AS": + if longestfreq == "YS": return lambda i: year(i) @@ -152,20 +152,20 @@ def idxs_and_mapping( def startdate(freq): - if freq == "15T" or freq == "H" or freq == "D": + if freq == "15min" or freq == "h" or freq == "D": return "2019-12-15" if freq == "MS": return "2019-12-01" if freq == "QS": return "2020-04-01" - if freq == "AS": + if freq == "YS": return "2020-01-01" @pytest.mark.parametrize("starttime", ["00:00", "06:00"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) -@pytest.mark.parametrize("freq_long", ["15T", "H", "D", "MS", "QS", "AS"]) -@pytest.mark.parametrize("freq_shrt", ["15T", "H", "D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq_long", ["15min", "h", "D", "MS", "QS", "YS"]) +@pytest.mark.parametrize("freq_shrt", ["15min", "h", "D", "MS", "QS", "YS"]) def test_downsample_index(freq_shrt: str, tz: str, freq_long: str, starttime: str): """Test downsampling of indices.""" @@ -183,8 +183,8 @@ def test_downsample_index(freq_shrt: str, tz: str, freq_long: str, starttime: st @pytest.mark.parametrize("starttime", ["00:00", "06:00"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) -@pytest.mark.parametrize("freq_long", ["15T", "H", "D", "MS", "QS", "AS"]) -@pytest.mark.parametrize("freq_shrt", ["15T", "H", "D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq_long", ["15min", "h", "D", "MS", "QS", "YS"]) +@pytest.mark.parametrize("freq_shrt", ["15min", "h", "D", "MS", "QS", "YS"]) def test_upsample_index(freq_shrt: str, tz: str, freq_long: str, starttime: str): """Test upsampling of indices.""" @@ -208,8 +208,8 @@ def test_upsample_index(freq_shrt: str, tz: str, freq_long: str, starttime: str) @pytest.mark.parametrize("seriesordf", ["s", "s_unit", "df", "df_unit"]) @pytest.mark.parametrize("complexity", ["ones", "allnumbers"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) -@pytest.mark.parametrize("freq_long", ["15T", "H", "D", "MS", "QS", "AS"]) -@pytest.mark.parametrize("freq_shrt", ["15T", "H", "D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq_long", ["15min", "h", "D", "MS", "QS", "YS"]) +@pytest.mark.parametrize("freq_shrt", ["15min", "h", "D", "MS", "QS", "YS"]) def test_downsample_summable( seriesordf: str, freq_shrt: str, @@ -243,8 +243,8 @@ def test_downsample_summable( @pytest.mark.parametrize("seriesordf", ["s", "s_unit", "df", "df_unit"]) @pytest.mark.parametrize("complexity", ["ones", "allnumbers"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) -@pytest.mark.parametrize("freq_long", ["15T", "H", "D", "MS", "QS", "AS"]) -@pytest.mark.parametrize("freq_shrt", ["15T", "H", "D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq_long", ["15min", "h", "D", "MS", "QS", "YS"]) +@pytest.mark.parametrize("freq_shrt", ["15min", "h", "D", "MS", "QS", "YS"]) def test_downsample_avgable( seriesordf: str, freq_shrt: str, @@ -278,8 +278,8 @@ def test_downsample_avgable( @pytest.mark.parametrize("seriesordf", ["s", "s_unit", "df", "df_unit"]) @pytest.mark.parametrize("complexity", ["ones", "allnumbers"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) -@pytest.mark.parametrize("freq_shrt", ["15T", "H", "D", "MS", "QS", "AS"]) -@pytest.mark.parametrize("freq_long", ["15T", "H", "D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq_shrt", ["15min", "h", "D", "MS", "QS", "YS"]) +@pytest.mark.parametrize("freq_long", ["15min", "h", "D", "MS", "QS", "YS"]) def test_upsample_avgable( seriesordf: str, freq_long: str, @@ -315,8 +315,8 @@ def test_upsample_avgable( @pytest.mark.parametrize("seriesordf", ["s", "s_unit", "df", "df_unit"]) @pytest.mark.parametrize("complexity", ["ones", "allnumbers"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) -@pytest.mark.parametrize("freq_shrt", ["15T", "H", "D", "MS", "QS", "AS"]) -@pytest.mark.parametrize("freq_long", ["15T", "H", "D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq_shrt", ["15min", "h", "D", "MS", "QS", "YS"]) +@pytest.mark.parametrize("freq_long", ["15min", "h", "D", "MS", "QS", "YS"]) def test_upsample_summable( seriesordf: str, freq_long: str, diff --git a/tests/tools/test_changefreq_fromexcel.py.bak b/tests/tools/test_changefreq_fromexcel.py.bak index 1d7d70b..8edc822 100644 --- a/tests/tools/test_changefreq_fromexcel.py.bak +++ b/tests/tools/test_changefreq_fromexcel.py.bak @@ -7,20 +7,20 @@ import pytest from portfolyo import testing, tools -freqs_small_to_large = ["T", "5T", "15T", "30T", "H", "2H", "D", "MS", "QS", "AS"] +freqs_small_to_large = ["min", "5min", "15min", "30min", "h", "2h", "D", "MS", "QS", "YS"] # No timezone. -i1 = pd.date_range("2020-01-15 12:00", "2020-01-18 12:00", freq="H", inclusive="left") +i1 = pd.date_range("2020-01-15 12:00", "2020-01-18 12:00", freq="h", inclusive="left") s1 = pd.Series(range(len(i1)), i1) s1_15t_sum = pd.Series( [v2 for v in range(len(i1)) for v2 in [v / 4.0] * 4], - pd.date_range("2020-01-15 12:00", "2020-01-18 12:00", freq="15T", inclusive="left"), + pd.date_range("2020-01-15 12:00", "2020-01-18 12:00", freq="15min", inclusive="left"), ) s1_15t_avg = pd.Series( [v2 for v in range(len(i1)) for v2 in [v * 1.0] * 4], - pd.date_range("2020-01-15 12:00", "2020-01-18 12:00", freq="15T", inclusive="left"), + pd.date_range("2020-01-15 12:00", "2020-01-18 12:00", freq="15min", inclusive="left"), ) s1_d_sum = pd.Series([564.0, 1140], pd.date_range("2020-01-16", freq="D", periods=2)) @@ -33,7 +33,7 @@ s1_q_sum = s1_q_avg = pd.Series( [], pd.date_range("2020", periods=0, freq="QS"), dtype=float ) s1_a_sum = s1_a_avg = pd.Series( - [], pd.date_range("2020", periods=0, freq="AS"), dtype=float + [], pd.date_range("2020", periods=0, freq="YS"), dtype=float ) # Timezone, no DST. @@ -78,16 +78,16 @@ def withtz(sin): i3_start, i3_end = "2020-03-28 12:00", "2020-03-31 12:00" tz = "Europe/Berlin" -i3 = pd.date_range(i3_start, i3_end, freq="H", inclusive="left", tz=tz) +i3 = pd.date_range(i3_start, i3_end, freq="h", inclusive="left", tz=tz) s3 = pd.Series(range(len(i3)), i3) s3_15t_sum = pd.Series( [v2 for v in range(len(i3)) for v2 in [v / 4.0] * 4], - pd.date_range(i3_start, i3_end, freq="15T", inclusive="left", tz=tz), + pd.date_range(i3_start, i3_end, freq="15min", inclusive="left", tz=tz), ) s3_15t_avg = pd.Series( [v2 for v in range(len(i3)) for v2 in [v * 1.0] * 4], - pd.date_range(i3_start, i3_end, freq="15T", inclusive="left", tz=tz), + pd.date_range(i3_start, i3_end, freq="15min", inclusive="left", tz=tz), ) s3_d_sum = pd.Series( @@ -104,22 +104,22 @@ s3_q_sum = s3_q_avg = pd.Series( [], pd.date_range("2020", periods=0, freq="QS", tz=tz), dtype=float ) s3_a_sum = s3_a_avg = pd.Series( - [], pd.date_range("2020", periods=0, freq="AS", tz=tz), dtype=float + [], pd.date_range("2020", periods=0, freq="YS", tz=tz), dtype=float ) # Timezone, end of DST. i4_start, i4_end = "2020-10-24 12:00", "2020-10-27 12:00" -i4 = pd.date_range(i4_start, i4_end, freq="H", inclusive="left", tz=tz) +i4 = pd.date_range(i4_start, i4_end, freq="h", inclusive="left", tz=tz) s4 = pd.Series(range(len(i4)), i4) s4_15t_sum = pd.Series( [v2 for v in range(len(i4)) for v2 in [v / 4.0] * 4], - pd.date_range(i4_start, i4_end, freq="15T", inclusive="left", tz=tz), + pd.date_range(i4_start, i4_end, freq="15min", inclusive="left", tz=tz), ) s4_15t_avg = pd.Series( [v2 for v in range(len(i4)) for v2 in [v * 1.0] * 4], - pd.date_range(i4_start, i4_end, freq="15T", inclusive="left", tz=tz), + pd.date_range(i4_start, i4_end, freq="15min", inclusive="left", tz=tz), ) s4_d_sum = pd.Series( @@ -136,7 +136,7 @@ s4_q_sum = s4_q_avg = pd.Series( [], pd.date_range("2020", periods=0, freq="QS", tz=tz), dtype=float ) s4_a_sum = s4_a_avg = pd.Series( - [], pd.date_range("2020", periods=0, freq="AS", tz=tz), dtype=float + [], pd.date_range("2020", periods=0, freq="YS", tz=tz), dtype=float ) # Months (= unequal lengths) as starting point. @@ -166,7 +166,7 @@ s5_q_avg = pd.Series( ) s5_a_sum = s5_a_avg = pd.Series( - [], pd.date_range("2020", periods=0, freq="AS"), dtype=float + [], pd.date_range("2020", periods=0, freq="YS"), dtype=float ) # Months (= unequal lengths) as starting point, with DST @@ -206,39 +206,39 @@ s6_q_avg = pd.Series( ) s6_a_sum = s6_a_avg = pd.Series( - [], pd.date_range("2020", periods=0, freq="AS", tz=tz), dtype=float + [], pd.date_range("2020", periods=0, freq="YS", tz=tz), dtype=float ) @pytest.mark.parametrize( ("s", "freq", "expected"), [ - (s1, "15T", s1_15t_sum), + (s1, "15min", s1_15t_sum), (s1, "D", s1_d_sum), (s1, "MS", s1_m_sum), (s1, "QS", s1_q_sum), - (s1, "AS", s1_a_sum), - (s2, "15T", s2_15t_sum), + (s1, "YS", s1_a_sum), + (s2, "15min", s2_15t_sum), (s2, "D", s2_d_sum), (s2, "MS", s2_m_sum), (s2, "QS", s2_q_sum), - (s2, "AS", s2_a_sum), - (s3, "15T", s3_15t_sum), + (s2, "YS", s2_a_sum), + (s3, "15min", s3_15t_sum), (s3, "D", s3_d_sum), (s3, "MS", s3_m_sum), (s3, "QS", s3_q_sum), - (s3, "AS", s3_a_sum), - (s4, "15T", s4_15t_sum), + (s3, "YS", s3_a_sum), + (s4, "15min", s4_15t_sum), (s4, "D", s4_d_sum), (s4, "MS", s4_m_sum), (s4, "QS", s4_q_sum), - (s4, "AS", s4_a_sum), + (s4, "YS", s4_a_sum), (s5, "D", s5_d_sum), (s5, "QS", s5_q_sum), - (s5, "AS", s5_a_sum), + (s5, "YS", s5_a_sum), (s6, "D", s6_d_sum), (s6, "QS", s6_q_sum), - (s6, "AS", s6_a_sum), + (s6, "YS", s6_a_sum), ], ) def test_resample_summable(s, freq, expected): @@ -250,32 +250,32 @@ def test_resample_summable(s, freq, expected): @pytest.mark.parametrize( ("s", "freq", "expected"), [ - (s1, "15T", s1_15t_avg), + (s1, "15min", s1_15t_avg), (s1, "D", s1_d_avg), (s1, "MS", s1_m_avg), (s1, "QS", s1_q_avg), - (s1, "AS", s1_a_avg), - (s2, "15T", s2_15t_avg), + (s1, "YS", s1_a_avg), + (s2, "15min", s2_15t_avg), (s2, "D", s2_d_avg), (s2, "MS", s2_m_avg), (s2, "QS", s2_q_avg), - (s2, "AS", s2_a_avg), - (s3, "15T", s3_15t_avg), + (s2, "YS", s2_a_avg), + (s3, "15min", s3_15t_avg), (s3, "D", s3_d_avg), (s3, "MS", s3_m_avg), (s3, "QS", s3_q_avg), - (s3, "AS", s3_a_avg), - (s4, "15T", s4_15t_avg), + (s3, "YS", s3_a_avg), + (s4, "15min", s4_15t_avg), (s4, "D", s4_d_avg), (s4, "MS", s4_m_avg), (s4, "QS", s4_q_avg), - (s4, "AS", s4_a_avg), + (s4, "YS", s4_a_avg), (s5, "D", s5_d_avg), (s5, "QS", s5_q_avg), - (s5, "AS", s5_a_avg), + (s5, "YS", s5_a_avg), (s6, "D", s6_d_avg), (s6, "QS", s6_q_avg), - (s6, "AS", s6_a_avg), + (s6, "YS", s6_a_avg), ], ) def test_resample_avgable(s, freq, expected): @@ -287,14 +287,14 @@ def test_resample_avgable(s, freq, expected): # Using gas days instead of calendar days. # . no tz -i7 = pd.date_range("2020-01-15 12:00", "2020-01-18 12:00", freq="H", inclusive="left") +i7 = pd.date_range("2020-01-15 12:00", "2020-01-18 12:00", freq="h", inclusive="left") s7 = pd.Series(range(len(i7)), i7) s7_d_sum = pd.Series([708.0, 1284], pd.date_range("2020-01-16", freq="D", periods=2)) s7_d_avg = pd.Series([29.5, 53.5], pd.date_range("2020-01-16", freq="D", periods=2)) # . start of dst i8 = pd.date_range( - "2020-03-26 12:00", "2020-03-30 12:00", freq="H", inclusive="left", tz=tz + "2020-03-26 12:00", "2020-03-30 12:00", freq="h", inclusive="left", tz=tz ) s8 = pd.Series(range(len(i8)), i8) s8_d_sum = pd.Series( @@ -306,7 +306,7 @@ s8_d_avg = pd.Series( # . end of dst i9 = pd.date_range( - "2020-10-22 12:00", "2020-10-26 12:00", freq="H", inclusive="left", tz=tz + "2020-10-22 12:00", "2020-10-26 12:00", freq="h", inclusive="left", tz=tz ) s9 = pd.Series(range(len(i9)), i9) s9_d_sum = pd.Series( @@ -357,7 +357,7 @@ s9_d_avg = pd.Series( # def aggdata(): # # Sample data # i_15T = pd.date_range( -# "2020", "2022", freq="15T", tz="Europe/Berlin", inclusive="left" +# "2020", "2022", freq="15min", tz="Europe/Berlin", inclusive="left" # ) # def value_func(mean, ampl_a, ampl_m, ampl_d): @@ -381,7 +381,7 @@ s9_d_avg = pd.Series( # # Seperate the values in bins for later aggregation. # def isstart_f(freq): -# if freq == "AS": +# if freq == "YS": # return lambda ts: ts.floor("D") == ts and ts.is_year_start # if freq == "QS": # return lambda ts: ts.floor("D") == ts and ts.is_quarter_start @@ -389,7 +389,7 @@ s9_d_avg = pd.Series( # return lambda ts: ts.floor("D") == ts and ts.is_month_start # if freq == "D": # return lambda ts: ts.floor("D") == ts -# if freq == "H": +# if freq == "h": # return lambda ts: ts.minute == 0 # raise ValueError("Invalid value for `freq`.") @@ -401,7 +401,7 @@ s9_d_avg = pd.Series( # "hours": [], # "new": isstart_f(freq), # } -# for freq in ["H", "D", "MS", "QS", "AS"] +# for freq in ["h", "D", "MS", "QS", "YS"] # } # for ts, val, dur in zip(source.index, source.values, source.index.duration): # for freq, dic in agg_data.items(): @@ -413,7 +413,7 @@ s9_d_avg = pd.Series( # dic["values"][-1].append(val) # dic["durations"][-1].append(dur) # dic["hours"][-1].append(dur.magnitude) -# agg_data["15T"] = { +# agg_data["15min"] = { # "values": [[v] for v in source.values], # "durations": [[d] for d in source.index.duration], # "hours": [[d.magnitude] for d in source.index.duration], diff --git a/tests/tools/test_changeyear.py b/tests/tools/test_changeyear.py index 1be874b..3bafda6 100644 --- a/tests/tools/test_changeyear.py +++ b/tests/tools/test_changeyear.py @@ -83,7 +83,9 @@ class TCase: # testcase idx_source: pd.DatetimeIndex # source index idx_target: pd.DatetimeIndex # target index holiday_country: str - expected_mapping: pd.Series # for every day in the target index, get position in source + expected_mapping: ( + pd.Series + ) # for every day in the target index, get position in source def create_testcase_factory(): @@ -110,10 +112,10 @@ def add_testcase(tc: dict, several_years: bool): # Testcase with hourly frequency. idx_s2 = pd.date_range( - str(year_s_start), str(year_s_end), freq="H", inclusive="left", tz=tz + str(year_s_start), str(year_s_end), freq="h", inclusive="left", tz=tz ) idx_t2 = pd.date_range( - str(year_t_start), str(year_t_end), freq="H", inclusive="left", tz=tz + str(year_t_start), str(year_t_end), freq="h", inclusive="left", tz=tz ) count_s = pd.Series(0, idx_s2).resample("D").count() count_t = pd.Series(0, idx_t2).resample("D").count() @@ -121,7 +123,7 @@ def add_testcase(tc: dict, several_years: bool): for count_t_here, m in zip(count_t, mapping): count_s_start = count_s[:m].sum() mapping_hourly.extend(range(count_s_start, count_s_start + count_t_here)) - key = (year_s_start, year_t_start, tz, country, "H", several_years) + key = (year_s_start, year_t_start, tz, country, "h", several_years) testcases[key] = TCase(idx_s2, idx_t2, country, mapping_hourly) # Testcase for identical mapping (days). @@ -129,7 +131,7 @@ def add_testcase(tc: dict, several_years: bool): testcases[key] = TCase(idx_s, idx_s, country, range(len(idx_s))) # Testcase for identical mapping (hours). - key = (year_s_start, year_s_start, tz, country, "H", several_years) + key = (year_s_start, year_s_start, tz, country, "h", several_years) testcases[key] = TCase(idx_s2, idx_s2, country, range(len(idx_s2))) testcases: Dict[Tuple, TCase] = {} @@ -158,7 +160,7 @@ def testcase( # --- -@pytest.mark.parametrize("freq", ["15T", "H", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq", ["15min", "h", "MS", "QS", "YS"]) @pytest.mark.parametrize("tz", ["Europe/Berlin", None]) def test_characterizeindex_error(freq: str, tz: str): """Test if characterization is only possible for daily indices.""" @@ -260,10 +262,10 @@ def test_characterizeindex_holidays( # Distinct frequencies ( pd.date_range("2020", "2021", freq="D", inclusive="left", tz=None), - pd.date_range("2021", "2022", freq="15T", inclusive="left", tz=None), + pd.date_range("2021", "2022", freq="15min", inclusive="left", tz=None), ), ( - pd.date_range("2020", "2021", freq="15T", inclusive="left", tz=None), + pd.date_range("2020", "2021", freq="15min", inclusive="left", tz=None), pd.date_range("2021", "2022", freq="D", inclusive="left", tz=None), ), ( @@ -271,7 +273,7 @@ def test_characterizeindex_holidays( "2020", "2021", freq="D", inclusive="left", tz="Europe/Berlin" ), pd.date_range( - "2021", "2022", freq="H", inclusive="left", tz="Europe/Berlin" + "2021", "2022", freq="h", inclusive="left", tz="Europe/Berlin" ), ), # Too few source data @@ -295,7 +297,7 @@ def test_mapindextoindex_error( @pytest.mark.parametrize("tz", ["Europe/Berlin", None]) @pytest.mark.parametrize("holiday_country", ["DE", None]) @pytest.mark.parametrize("numyears", [1, 3]) -@pytest.mark.parametrize("freq", ["MS", "D", "H"]) +@pytest.mark.parametrize("freq", ["MS", "D", "h"]) @pytest.mark.parametrize("partial", ["partial", "full"]) def test_mapindextoindex_identical( tz: str, holiday_country: str, numyears: int, partial: str, freq: str @@ -327,10 +329,10 @@ def test_mapindextoindex_identical( ("QS", "2020", "2021", 7, 7, [4, 5, 6, 3, 0, 1, 2]), ("QS", "2020", "2021", 4, 7, [0, 1, 2, 3, 0, 1, 2]), ("QS", "2020", "2021", 7, 4, [4, 5, 6, 3]), - ("AS", "2020", "2024", 4, 4, range(4)), - ("AS", "2020", "2024", 7, 7, [4, 5, 6, 0, 1, 2, 3]), - ("AS", "2020", "2024", 4, 7, [0, 1, 2, 3, 0, 1, 2]), - ("AS", "2020", "2024", 7, 4, [4, 5, 6, 0]), + ("YS", "2020", "2024", 4, 4, range(4)), + ("YS", "2020", "2024", 7, 7, [4, 5, 6, 0, 1, 2, 3]), + ("YS", "2020", "2024", 4, 7, [0, 1, 2, 3, 0, 1, 2]), + ("YS", "2020", "2024", 7, 4, [4, 5, 6, 0]), ], ) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) @@ -365,7 +367,7 @@ def test_mapindextoindex_monthlyandlonger( @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) @pytest.mark.parametrize("holiday_country", [None, "DE"]) @pytest.mark.parametrize("partial", ["partial", "full"]) -@pytest.mark.parametrize("freq", ["D", "H"]) +@pytest.mark.parametrize("freq", ["D", "h"]) @pytest.mark.parametrize("several_years", [True, False]) def test_mapindextoindex_daysandhours( year_s: int, @@ -399,7 +401,7 @@ def test_mapindextoindex_daysandhours( @pytest.mark.parametrize("holiday_country", [None, "DE"]) @pytest.mark.parametrize("partial", ["partial", "full"]) @pytest.mark.parametrize("series_or_df", ["series", "df"]) -@pytest.mark.parametrize("freq", ["D", "H"]) +@pytest.mark.parametrize("freq", ["D", "h"]) @pytest.mark.parametrize("several_years", [True, False]) def test_mapframetoindex( year_s: int, @@ -450,7 +452,7 @@ def test_mapframetoindex( @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) @pytest.mark.parametrize("holiday_country", [None, "DE"]) @pytest.mark.parametrize("series_or_df", ["series", "df"]) -@pytest.mark.parametrize("freq", ["D", "H"]) +@pytest.mark.parametrize("freq", ["D", "h"]) @pytest.mark.parametrize("several_years_source", [True, False]) def test_mapframetoyear_oneyear( year_s: int, diff --git a/tests/tools/test_duration.py b/tests/tools/test_duration.py index 43eef6e..7a5d26c 100644 --- a/tests/tools/test_duration.py +++ b/tests/tools/test_duration.py @@ -5,41 +5,41 @@ TESTCASES = [ # ts, freq, expected_hours # First day of X and start of day. - ("2020-01-01", "15T", (0.25, 0.25)), - ("2020-01-01", "H", (1, 1)), + ("2020-01-01", "15min", (0.25, 0.25)), + ("2020-01-01", "h", (1, 1)), ("2020-01-01", "D", (24, 24)), ("2020-01-01", "MS", (744, 696)), - ("2020-01-01", "AS", (8784, 8760)), + ("2020-01-01", "YS", (8784, 8760)), # First day of X but not start of day. - ("2020-01-01 06:00", "15T", (0.25, 0.25)), - ("2020-01-01 06:00", "H", (1, 1)), + ("2020-01-01 06:00", "15min", (0.25, 0.25)), + ("2020-01-01 06:00", "h", (1, 1)), ("2020-01-01 06:00", "D", (24, 24)), ("2020-01-01 06:00", "MS", (744, 696)), - ("2020-01-01 06:00", "AS", (8784, 8760)), + ("2020-01-01 06:00", "YS", (8784, 8760)), # Not first day of X but start of day. - ("2020-04-01", "15T", (0.25, 0.25)), - ("2020-04-01", "H", (1, 1)), + ("2020-04-01", "15min", (0.25, 0.25)), + ("2020-04-01", "h", (1, 1)), ("2020-04-01", "D", (24, 24)), ("2020-04-01", "MS", (720, 744)), ("2020-04-01", "QS", (2184, 2208)), - ("2020-04-21", "15T", (0.25, 0.25)), - ("2020-04-21", "H", (1, 1)), + ("2020-04-21", "15min", (0.25, 0.25)), + ("2020-04-21", "h", (1, 1)), ("2020-04-21", "D", (24, 24)), # Not first day of X and not start of day. - ("2020-04-01 06:00", "15T", (0.25, 0.25)), - ("2020-04-01 06:00", "H", (1, 1)), + ("2020-04-01 06:00", "15min", (0.25, 0.25)), + ("2020-04-01 06:00", "h", (1, 1)), ("2020-04-01 06:00", "D", (24, 24)), ("2020-04-01 06:00", "MS", (720, 744)), ("2020-04-01 06:00", "QS", (2184, 2208)), - ("2020-04-21 06:00", "15T", (0.25, 0.25)), - ("2020-04-21 06:00", "H", (1, 1)), + ("2020-04-21 06:00", "15min", (0.25, 0.25)), + ("2020-04-21 06:00", "h", (1, 1)), ("2020-04-21 06:00", "D", (24, 24)), ] TESTCASES_DST = [ # ts, freq, expected_hours # Start of DST. - ("2020-03-29 01:00", "15T", (0.25, 0.25)), - ("2020-03-29 01:00", "H", (1, 1)), + ("2020-03-29 01:00", "15min", (0.25, 0.25)), + ("2020-03-29 01:00", "h", (1, 1)), ("2020-03-29 00:00", "D", (23, 24)), ("2020-03-29 01:00", "D", (23, 24)), ("2020-03-29 03:00", "D", (24, 24)), @@ -52,14 +52,14 @@ ("2020-01-01 00:00", "QS", (2183, 2184)), ("2020-01-01 06:00", "QS", (2183, 2184)), # End of DST. - ("2020-10-25 01:00", "15T", (0.25, 0.25)), - ("2020-10-25 02:00+0200", "15T", (0.25, 0.25)), - ("2020-10-25 02:00+0100", "15T", (0.25, 0.25)), - ("2020-10-25 03:00", "15T", (0.25, 0.25)), - ("2020-10-25 01:00", "H", (1, 1)), - ("2020-10-25 02:00+0200", "H", (1, 1)), - ("2020-10-25 02:00+0100", "H", (1, 1)), - ("2020-10-25 03:00", "H", (1, 1)), + ("2020-10-25 01:00", "15min", (0.25, 0.25)), + ("2020-10-25 02:00+0200", "15min", (0.25, 0.25)), + ("2020-10-25 02:00+0100", "15min", (0.25, 0.25)), + ("2020-10-25 03:00", "15min", (0.25, 0.25)), + ("2020-10-25 01:00", "h", (1, 1)), + ("2020-10-25 02:00+0200", "h", (1, 1)), + ("2020-10-25 02:00+0100", "h", (1, 1)), + ("2020-10-25 03:00", "h", (1, 1)), ("2020-10-25 00:00", "D", (25, 24)), ("2020-10-25 01:00", "D", (25, 24)), ("2020-10-25 03:00", "D", (24, 24)), diff --git a/tests/tools/test_floorceil.py b/tests/tools/test_floorceil.py index 3a925f8..3b311b3 100644 --- a/tests/tools/test_floorceil.py +++ b/tests/tools/test_floorceil.py @@ -7,40 +7,40 @@ from portfolyo import tools # TESTCASES_ALL_ON_BOUNDARY = [ # date, freq, periods, freqs -# ("2020-01-01", "15T", 80, ("D", "MS", "QS", "AS")), -# ("2020-01-01", "H", 20, ("D", "MS", "QS", "AS")), -# ("2020-01-01", "D", 25, ("MS", "QS", "AS")), -# ("2020-01-01", "MS", 3, ("QS", "AS")), -# ("2020-01-01", "QS", 3, ("AS",)), -# ("2020-04-01", "15T", 80, ("D", "MS", "QS")), -# ("2020-04-01", "H", 20, ("D", "MS", "QS")), +# ("2020-01-01", "15min", 80, ("D", "MS", "QS", "YS")), +# ("2020-01-01", "h", 20, ("D", "MS", "QS", "YS")), +# ("2020-01-01", "D", 25, ("MS", "QS", "YS")), +# ("2020-01-01", "MS", 3, ("QS", "YS")), +# ("2020-01-01", "QS", 3, ("YS",)), +# ("2020-04-01", "15min", 80, ("D", "MS", "QS")), +# ("2020-04-01", "h", 20, ("D", "MS", "QS")), # ("2020-04-01", "D", 25, ("MS", "QS")), # ("2020-04-01", "MS", 3, ("QS",)), -# ("2020-02-01", "15T", 80, ("D", "MS")), -# ("2020-02-01", "H", 20, ("D", "MS")), +# ("2020-02-01", "15min", 80, ("D", "MS")), +# ("2020-02-01", "h", 20, ("D", "MS")), # ("2020-02-01", "D", 25, ("MS",)), -# ("2020-04-21", "15T", 80, ("D",)), -# ("2020-04-21", "H", 20, ("D",)), +# ("2020-04-21", "15min", 80, ("D",)), +# ("2020-04-21", "h", 20, ("D",)), # ] # TESTCASES_STAMPS_ONBOUNDARY = [ # date, freq -# ("2020-01-01", "15T"), -# ("2020-01-01", "H"), +# ("2020-01-01", "15min"), +# ("2020-01-01", "h"), # ("2020-01-01", "D"), # ("2020-01-01", "MS"), # ("2020-01-01", "QS"), -# ("2020-01-01", "AS"), -# ("2020-04-01", "15T"), -# ("2020-04-01", "H"), +# ("2020-01-01", "YS"), +# ("2020-04-01", "15min"), +# ("2020-04-01", "h"), # ("2020-04-01", "D"), # ("2020-04-01", "MS"), # ("2020-04-01", "QS"), -# ("2020-02-01", "15T"), -# ("2020-02-01", "H"), +# ("2020-02-01", "15min"), +# ("2020-02-01", "h"), # ("2020-02-01", "D"), # ("2020-02-01", "MS"), -# ("2020-04-21", "15T"), -# ("2020-04-21", "H"), +# ("2020-04-21", "15min"), +# ("2020-04-21", "h"), # ("2020-04-21", "D"), # ] @@ -48,74 +48,74 @@ # ("2020-01-01", 0, "D", "2020-01-01", "2020-01-01"), # ("2020-01-01", 0, "MS", "2020-01-01", "2020-01-01"), # ("2020-01-01", 0, "QS", "2020-01-01", "2020-01-01"), -# ("2020-01-01", 0, "AS", "2020-01-01", "2020-01-01"), +# ("2020-01-01", 0, "YS", "2020-01-01", "2020-01-01"), # ("2020-01-01", 1, "D", "2020-01-02", "2020-01-02"), # ("2020-01-01", 1, "MS", "2020-02-01", "2020-02-01"), # ("2020-01-01", 1, "QS", "2020-04-01", "2020-04-01"), -# ("2020-01-01", 1, "AS", "2021-01-01", "2021-01-01"), +# ("2020-01-01", 1, "YS", "2021-01-01", "2021-01-01"), # ("2020-01-01", -1, "D", "2019-12-31", "2019-12-31"), # ("2020-01-01", -1, "MS", "2019-12-01", "2019-12-01"), # ("2020-01-01", -1, "QS", "2019-10-01", "2019-10-01"), -# ("2020-01-01", -1, "AS", "2019-01-01", "2019-01-01"), +# ("2020-01-01", -1, "YS", "2019-01-01", "2019-01-01"), # ] # TESTCASES_STAMPS_DATES = [ # ts, fut, freq, floored, ceiled # ("2020-12-31 12:00", 0, "D", "2020-12-31", "2021-01-01"), # ("2020-12-31 12:00", 0, "MS", "2020-12-01", "2021-01-01"), # ("2020-12-31 12:00", 0, "QS", "2020-10-01", "2021-01-01"), -# ("2020-12-31 12:00", 0, "AS", "2020-01-01", "2021-01-01"), +# ("2020-12-31 12:00", 0, "YS", "2020-01-01", "2021-01-01"), # ("2020-02-01", 0, "D", "2020-02-01", "2020-02-01"), # ("2020-02-01", 0, "MS", "2020-02-01", "2020-02-01"), # ("2020-02-01", 0, "QS", "2020-01-01", "2020-04-01"), -# ("2020-02-01", 0, "AS", "2020", "2021"), +# ("2020-02-01", 0, "YS", "2020", "2021"), # ("2020-01-01 23:55", 0, "D", "2020", "2020-01-02"), # ("2020-01-24 1:32", 0, "MS", "2020", "2020-02"), # ("2020-03-03 3:33", 0, "QS", "2020", "2020-04"), -# ("2020-10-11 12:34", 0, "AS", "2020", "2021"), +# ("2020-10-11 12:34", 0, "YS", "2020", "2021"), # ("2020-01-01 23:55", 1, "D", "2020-01-02", "2020-01-03"), # ("2020-01-24 1:32", 1, "MS", "2020-02", "2020-03"), # ("2020-03-03 3:33", 1, "QS", "2020-04", "2020-07"), -# ("2020-10-11 12:34", 1, "AS", "2021", "2022"), +# ("2020-10-11 12:34", 1, "YS", "2021", "2022"), # ("2020-01-01 23:55", -1, "D", "2019-12-31", "2020-01-01"), # ("2020-01-24 1:32", -1, "MS", "2019-12", "2020"), # ("2020-03-03 3:33", -1, "QS", "2019-10", "2020"), -# ("2020-10-11 12:34", -1, "AS", "2019", "2020"), -# ("2020-03-29 00:00", 0, "H", "2020-03-29 00:00", "2020-03-29 00:00"), -# ("2020-10-25 00:00", 0, "H", "2020-10-25 00:00", "2020-10-25 00:00"), +# ("2020-10-11 12:34", -1, "YS", "2019", "2020"), +# ("2020-03-29 00:00", 0, "h", "2020-03-29 00:00", "2020-03-29 00:00"), +# ("2020-10-25 00:00", 0, "h", "2020-10-25 00:00", "2020-10-25 00:00"), # ] # TESTCASES = [ # ts, freq, floored, # # First day of X and start of day. -# ("2020-01-01", "15T", ["2020-01-01 00:15"), -# ("2020-01-01", "H", "2020-01-01 01:00"), +# ("2020-01-01", "15min", ["2020-01-01 00:15"), +# ("2020-01-01", "h", "2020-01-01 01:00"), # ("2020-01-01", "D", "2020-01-02"), # ("2020-01-01", "MS", "2020-02-01"), # ("2020-01-01", "QS", "2020-04-01"), -# ("2020-01-01", "AS", "2021-01-01"), +# ("2020-01-01", "YS", "2021-01-01"), # # First day of X but not start of day. -# ("2020-01-01 06:00",0, "15T", "2020-01-01 06:15"), -# ("2020-01-01 06:00",0, "H", "2020-01-01 07:00"), +# ("2020-01-01 06:00",0, "15min", "2020-01-01 06:15"), +# ("2020-01-01 06:00",0, "h", "2020-01-01 07:00"), # ("2020-01-01 06:00",0, "D", "2020-01-02 06:00"), # ("2020-01-01 06:00",0, "MS", "2020-02-01 06:00"), # ("2020-01-01 06:00",0, "QS", "2020-04-01 06:00"), -# ("2020-01-01 06:00",0, "AS", "2021-01-01 06:00"), +# ("2020-01-01 06:00",0, "YS", "2021-01-01 06:00"), # # Not first day of X but start of day. -# ("2020-04-01",0, "15T", "2020-04-01 00:15"), -# ("2020-04-01",0, "H", "2020-04-01 01:00"), +# ("2020-04-01",0, "15min", "2020-04-01 00:15"), +# ("2020-04-01",0, "h", "2020-04-01 01:00"), # ("2020-04-01",0, "D", "2020-04-02"), # ("2020-04-01",0, "MS", "2020-05-01"), # ("2020-04-01",0, "QS", "2020-07-01"), -# ("2020-04-21",0, "15T", "2020-04-21 00:15"), -# ("2020-04-21",0, "H", "2020-04-21 01:00"), +# ("2020-04-21",0, "15min", "2020-04-21 00:15"), +# ("2020-04-21",0, "h", "2020-04-21 01:00"), # ("2020-04-21",0, "D", "2020-04-22"), # # Not first day of X and not start of day. -# ("2020-04-01 06:00", 0, "15T", "2020-04-01 06:15"), -# ("2020-04-01 06:00", 0, "H", "2020-04-01 07:00"), +# ("2020-04-01 06:00", 0, "15min", "2020-04-01 06:15"), +# ("2020-04-01 06:00", 0, "h", "2020-04-01 07:00"), # ("2020-04-01 06:00", 0, "D", "2020-04-02 06:00"), # ("2020-04-01 06:00", 0, "MS", "2020-05-01 06:00"), # ("2020-04-01 06:00", 0, "QS", "2020-07-01 06:00"), -# ("2020-04-21 06:00", 0, "15T", "2020-04-21 06:15"), -# ("2020-04-21 06:00", 0, "H", "2020-04-21 07:00"), +# ("2020-04-21 06:00", 0, "15min", "2020-04-21 06:15"), +# ("2020-04-21 06:00", 0, "h", "2020-04-21 07:00"), # ("2020-04-21 06:00", 0, "D", "2020-04-22 06:00"), # ] @@ -194,158 +194,158 @@ TESTCASES = [ # ts, fut, freq, floored, ceiled - ("2020-04-21 12:34:56", 0, "15T", "2020-04-21 12:30", "2020-04-21 12:45"), - ("2020-04-21 12:34:56", 0, "H", "2020-04-21 12:00", "2020-04-21 13:00"), + ("2020-04-21 12:34:56", 0, "15min", "2020-04-21 12:30", "2020-04-21 12:45"), + ("2020-04-21 12:34:56", 0, "h", "2020-04-21 12:00", "2020-04-21 13:00"), ("2020-04-21 12:34:56", 0, "D", "2020-04-21", "2020-04-22"), ("2020-04-21 12:34:56", 0, "MS", "2020-04-01", "2020-05-01"), ("2020-04-21 12:34:56", 0, "QS", "2020-04-01", "2020-07-01"), - ("2020-04-21 12:34:56", 0, "AS", "2020-01-01", "2021-01-01"), - ("2020-04-21 23:54:16", 0, "15T", "2020-04-21 23:45", "2020-04-22"), - ("2020-04-21 23:54:16", 0, "H", "2020-04-21 23:00", "2020-04-22"), + ("2020-04-21 12:34:56", 0, "YS", "2020-01-01", "2021-01-01"), + ("2020-04-21 23:54:16", 0, "15min", "2020-04-21 23:45", "2020-04-22"), + ("2020-04-21 23:54:16", 0, "h", "2020-04-21 23:00", "2020-04-22"), ("2020-04-21 23:54:16", 0, "D", "2020-04-21", "2020-04-22"), ("2020-04-21 23:54:16", 0, "MS", "2020-04-01", "2020-05-01"), ("2020-04-21 23:54:16", 0, "QS", "2020-04-01", "2020-07-01"), - ("2020-04-21 23:54:16", 0, "AS", "2020-01-01", "2021-01-01"), + ("2020-04-21 23:54:16", 0, "YS", "2020-01-01", "2021-01-01"), ("2020", 0, "D", "2020", "2020"), ("2020", 0, "MS", "2020", "2020"), ("2020", 0, "QS", "2020", "2020"), - ("2020", 0, "AS", "2020", "2020"), + ("2020", 0, "YS", "2020", "2020"), ("2020", 1, "D", "2020-01-02", "2020-01-02"), ("2020", 1, "MS", "2020-02", "2020-02"), ("2020", 1, "QS", "2020-04", "2020-04"), - ("2020", 1, "AS", "2021", "2021"), + ("2020", 1, "YS", "2021", "2021"), ("2020", -1, "D", "2019-12-31", "2019-12-31"), ("2020", -1, "MS", "2019-12", "2019-12"), ("2020", -1, "QS", "2019-10", "2019-10"), - ("2020", -1, "AS", "2019", "2019"), + ("2020", -1, "YS", "2019", "2019"), ("2020-12-31 12:00", 0, "D", "2020-12-31", "2021-01-01"), ("2020-12-31 12:00", 0, "MS", "2020-12-01", "2021-01-01"), ("2020-12-31 12:00", 0, "QS", "2020-10-01", "2021-01-01"), - ("2020-12-31 12:00", 0, "AS", "2020-01-01", "2021-01-01"), + ("2020-12-31 12:00", 0, "YS", "2020-01-01", "2021-01-01"), ("2020-02-01", 0, "D", "2020-02-01", "2020-02-01"), ("2020-02-01", 0, "MS", "2020-02-01", "2020-02-01"), ("2020-02-01", 0, "QS", "2020-01-01", "2020-04-01"), - ("2020-02-01", 0, "AS", "2020", "2021"), + ("2020-02-01", 0, "YS", "2020", "2021"), ("2020-01-01 23:55", 0, "D", "2020", "2020-01-02"), ("2020-01-24 1:32", 0, "MS", "2020", "2020-02"), ("2020-03-03 3:33", 0, "QS", "2020", "2020-04"), - ("2020-10-11 12:34:56", 0, "AS", "2020", "2021"), + ("2020-10-11 12:34:56", 0, "YS", "2020", "2021"), ("2020-01-01 23:55", 1, "D", "2020-01-02", "2020-01-03"), ("2020-01-24 1:32", 1, "MS", "2020-02", "2020-03"), ("2020-03-03 3:33", 1, "QS", "2020-04", "2020-07"), - ("2020-10-11 12:34:56", 1, "AS", "2021", "2022"), + ("2020-10-11 12:34:56", 1, "YS", "2021", "2022"), ("2020-01-01 23:55", -1, "D", "2019-12-31", "2020-01-01"), ("2020-01-24 1:32", -1, "MS", "2019-12", "2020"), ("2020-03-03 3:33", -1, "QS", "2019-10", "2020"), - ("2020-10-11 12:34:56", -1, "AS", "2019", "2020"), - ("2020-03-29 00:00", 0, "H", "2020-03-29 00:00", "2020-03-29 00:00"), - ("2020-10-25 00:00", 0, "H", "2020-10-25 00:00", "2020-10-25 00:00"), + ("2020-10-11 12:34:56", -1, "YS", "2019", "2020"), + ("2020-03-29 00:00", 0, "h", "2020-03-29 00:00", "2020-03-29 00:00"), + ("2020-10-25 00:00", 0, "h", "2020-10-25 00:00", "2020-10-25 00:00"), ] TESTCASES_DST = [ # ts, tz, freq, floored, ceiled - ("2020-04-21 15:25", None, "H", "2020-04-21 15:00", "2020-04-21 16:00"), - ("2020-04-21 15:25", "Europe/Berlin", "H", "2020-04-21 15:00", "2020-04-21 16:00"), + ("2020-04-21 15:25", None, "h", "2020-04-21 15:00", "2020-04-21 16:00"), + ("2020-04-21 15:25", "Europe/Berlin", "h", "2020-04-21 15:00", "2020-04-21 16:00"), ( "2020-04-21 15:25+0200", "Europe/Berlin", - "H", + "h", "2020-04-21 15:00+0200", "2020-04-21 16:00+0200", ), - ("2020-04-21 15:25", "Asia/Kolkata", "H", "2020-04-21 15:00", "2020-04-21 16:00"), - ("2020-03-29 01:50", None, "15T", "2020-03-29 01:45", "2020-03-29 02:00"), - ("2020-03-29 03:05", None, "15T", "2020-03-29 03:00", "2020-03-29 03:15"), + ("2020-04-21 15:25", "Asia/Kolkata", "h", "2020-04-21 15:00", "2020-04-21 16:00"), + ("2020-03-29 01:50", None, "15min", "2020-03-29 01:45", "2020-03-29 02:00"), + ("2020-03-29 03:05", None, "15min", "2020-03-29 03:00", "2020-03-29 03:15"), ( "2020-03-29 01:50+0100", "Europe/Berlin", - "15T", + "15min", "2020-03-29 01:45+0100", "2020-03-29 03:00+0200", ), ( "2020-03-29 03:05+0200", "Europe/Berlin", - "15T", + "15min", "2020-03-29 03:00+0200", "2020-03-29 03:15+0200", ), ( "2020-03-29 01:50", "Europe/Berlin", - "15T", + "15min", "2020-03-29 01:45", "2020-03-29 03:00", ), - ("2020-03-29 03:05", None, "15T", "2020-03-29 03:00", "2020-03-29 03:15"), - ("2020-10-25 02:50", None, "15T", "2020-10-25 02:45", "2020-10-25 03:00"), - ("2020-10-25 02:05", None, "15T", "2020-10-25 02:00", "2020-10-25 02:15"), + ("2020-03-29 03:05", None, "15min", "2020-03-29 03:00", "2020-03-29 03:15"), + ("2020-10-25 02:50", None, "15min", "2020-10-25 02:45", "2020-10-25 03:00"), + ("2020-10-25 02:05", None, "15min", "2020-10-25 02:00", "2020-10-25 02:15"), ( "2020-10-25 02:50+0200", "Europe/Berlin", - "15T", + "15min", "2020-10-25 02:45+0200", "2020-10-25 02:00+0100", ), ( "2020-10-25 02:05+0200", "Europe/Berlin", - "15T", + "15min", "2020-10-25 02:00+0200", "2020-10-25 02:15+0200", ), ( "2020-10-25 02:50+0100", "Europe/Berlin", - "15T", + "15min", "2020-10-25 02:45+0100", "2020-10-25 03:00+0100", ), ( "2020-10-25 02:05+0100", "Europe/Berlin", - "15T", + "15min", "2020-10-25 02:00+0100", "2020-10-25 02:15+0100", ), ( "2020-10-25 02:30+0200", "Europe/Berlin", - "H", + "h", "2020-10-25 02:00+0200", "2020-10-25 02:00+0100", ), ( "2020-10-25 02:30+0100", "Europe/Berlin", - "H", + "h", "2020-10-25 02:00+0100", "2020-10-25 03:00+0100", ), ] TESTCASES_NONNATURAL = [ # ts, freq, offset_hours, floored, ceiled - ("2020-01-01 00:00", "15T", 6, "2020-01-01 00:00", "2020-01-01 00:00"), - ("2020-01-01 00:00", "H", 6, "2020-01-01 00:00", "2020-01-01 00:00"), + ("2020-01-01 00:00", "15min", 6, "2020-01-01 00:00", "2020-01-01 00:00"), + ("2020-01-01 00:00", "h", 6, "2020-01-01 00:00", "2020-01-01 00:00"), ("2020-01-01 00:00", "D", 6, "2019-12-31 06:00", "2020-01-01 06:00"), ("2020-01-01 00:00", "MS", 6, "2019-12-01 06:00", "2020-01-01 06:00"), ("2020-01-01 00:00", "QS", 6, "2019-10-01 06:00", "2020-01-01 06:00"), - ("2020-01-01 00:00", "AS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), - ("2020-01-01 04:00", "15T", 6, "2020-01-01 04:00", "2020-01-01 04:00"), - ("2020-01-01 04:00", "H", 6, "2020-01-01 04:00", "2020-01-01 04:00"), + ("2020-01-01 00:00", "YS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), + ("2020-01-01 04:00", "15min", 6, "2020-01-01 04:00", "2020-01-01 04:00"), + ("2020-01-01 04:00", "h", 6, "2020-01-01 04:00", "2020-01-01 04:00"), ("2020-01-01 04:00", "D", 6, "2019-12-31 06:00", "2020-01-01 06:00"), ("2020-01-01 04:00", "MS", 6, "2019-12-01 06:00", "2020-01-01 06:00"), ("2020-01-01 04:00", "QS", 6, "2019-10-01 06:00", "2020-01-01 06:00"), - ("2020-01-01 04:00", "AS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), - ("2019-12-31 12:00", "15T", 6, "2019-12-31 12:00", "2019-12-31 12:00"), - ("2019-12-31 12:00", "H", 6, "2019-12-31 12:00", "2019-12-31 12:00"), + ("2020-01-01 04:00", "YS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), + ("2019-12-31 12:00", "15min", 6, "2019-12-31 12:00", "2019-12-31 12:00"), + ("2019-12-31 12:00", "h", 6, "2019-12-31 12:00", "2019-12-31 12:00"), ("2019-12-31 12:00", "D", 6, "2019-12-31 06:00", "2020-01-01 06:00"), ("2019-12-31 12:00", "MS", 6, "2019-12-01 06:00", "2020-01-01 06:00"), ("2019-12-31 12:00", "QS", 6, "2019-10-01 06:00", "2020-01-01 06:00"), - ("2019-12-31 12:00", "AS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), - ("2020-04-21 15:25", "15T", 6, "2020-04-21 15:15", "2020-04-21 15:30"), - ("2020-04-21 15:25", "H", 6, "2020-04-21 15:00", "2020-04-21 16:00"), - ("2020-03-29 01:40", "15T", 6, "2020-03-29 01:30", "2020-03-29 01:45"), - ("2020-03-29 03:05", "15T", 6, "2020-03-29 03:00", "2020-03-29 03:15"), + ("2019-12-31 12:00", "YS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), + ("2020-04-21 15:25", "15min", 6, "2020-04-21 15:15", "2020-04-21 15:30"), + ("2020-04-21 15:25", "h", 6, "2020-04-21 15:00", "2020-04-21 16:00"), + ("2020-03-29 01:40", "15min", 6, "2020-03-29 01:30", "2020-03-29 01:45"), + ("2020-03-29 03:05", "15min", 6, "2020-03-29 03:00", "2020-03-29 03:15"), ("2020-04-21 15:25", "D", 6, "2020-04-21 06:00", "2020-04-22 06:00"), ("2020-03-28 01:40", "D", 6, "2020-03-27 06:00", "2020-03-28 06:00"), ("2020-03-29 01:40", "D", 6, "2020-03-28 06:00", "2020-03-29 06:00"), @@ -378,28 +378,28 @@ ] # TESTCASES_NONNATURAL_INDEX = [ # ts, i_freq, freq, floored, ceiled -# ("2020-01-01 00:00", "15T", 6, "2020-01-01 00:00", "2020-01-01 00:00"), -# ("2020-01-01 00:00", "H", 6, "2020-01-01 00:00", "2020-01-01 00:00"), +# ("2020-01-01 00:00", "15min", 6, "2020-01-01 00:00", "2020-01-01 00:00"), +# ("2020-01-01 00:00", "h", 6, "2020-01-01 00:00", "2020-01-01 00:00"), # ("2020-01-01 00:00", "D", 6, "2019-12-31 06:00", "2020-01-01 06:00"), # ("2020-01-01 00:00", "MS", 6, "2019-12-01 06:00", "2020-01-01 06:00"), # ("2020-01-01 00:00", "QS", 6, "2019-10-01 06:00", "2020-01-01 06:00"), -# ("2020-01-01 00:00", "AS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), -# ("2020-01-01 04:00", "15T", 6, "2020-01-01 04:00", "2020-01-01 04:00"), -# ("2020-01-01 04:00", "H", 6, "2020-01-01 04:00", "2020-01-01 04:00"), +# ("2020-01-01 00:00", "YS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), +# ("2020-01-01 04:00", "15min", 6, "2020-01-01 04:00", "2020-01-01 04:00"), +# ("2020-01-01 04:00", "h", 6, "2020-01-01 04:00", "2020-01-01 04:00"), # ("2020-01-01 04:00", "D", 6, "2019-12-31 06:00", "2020-01-01 06:00"), # ("2020-01-01 04:00", "MS", 6, "2019-12-01 06:00", "2020-01-01 06:00"), # ("2020-01-01 04:00", "QS", 6, "2019-10-01 06:00", "2020-01-01 06:00"), -# ("2020-01-01 04:00", "AS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), -# ("2019-12-31 12:00", "15T", 6, "2019-12-31 12:00", "2019-12-31 12:00"), -# ("2019-12-31 12:00", "H", 6, "2019-12-31 12:00", "2019-12-31 12:00"), +# ("2020-01-01 04:00", "YS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), +# ("2019-12-31 12:00", "15min", 6, "2019-12-31 12:00", "2019-12-31 12:00"), +# ("2019-12-31 12:00", "h", 6, "2019-12-31 12:00", "2019-12-31 12:00"), # ("2019-12-31 12:00", "D", 6, "2019-12-31 06:00", "2020-01-01 06:00"), # ("2019-12-31 12:00", "MS", 6, "2019-12-01 06:00", "2020-01-01 06:00"), # ("2019-12-31 12:00", "QS", 6, "2019-10-01 06:00", "2020-01-01 06:00"), -# ("2019-12-31 12:00", "AS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), -# ("2020-04-21 15:25", "15T", 6, "2020-04-21 15:15", "2020-04-21 15:30"), -# ("2020-04-21 15:25", "H", 6, "2020-04-21 15:00", "2020-04-21 16:00"), -# ("2020-03-29 01:40", "15T", 6, "2020-03-29 01:30", "2020-03-29 01:45"), -# ("2020-03-29 03:05", "15T", 6, "2020-03-29 03:00", "2020-03-29 03:15"), +# ("2019-12-31 12:00", "YS", 6, "2019-01-01 06:00", "2020-01-01 06:00"), +# ("2020-04-21 15:25", "15min", 6, "2020-04-21 15:15", "2020-04-21 15:30"), +# ("2020-04-21 15:25", "h", 6, "2020-04-21 15:00", "2020-04-21 16:00"), +# ("2020-03-29 01:40", "15min", 6, "2020-03-29 01:30", "2020-03-29 01:45"), +# ("2020-03-29 03:05", "15min", 6, "2020-03-29 03:00", "2020-03-29 03:15"), # ("2020-04-21 15:25", "D", 6, "2020-04-21 06:00", "2020-04-22 06:00"), # ("2020-03-28 01:40", "D", 6, "2020-03-27 06:00", "2020-03-28 06:00"), # ("2020-03-29 01:40", "D", 6, "2020-03-28 06:00", "2020-03-29 06:00"), diff --git a/tests/tools/test_frames.py b/tests/tools/test_frames.py index 77e8caa..f65a30f 100644 --- a/tests/tools/test_frames.py +++ b/tests/tools/test_frames.py @@ -26,7 +26,7 @@ (range(7), 0), (range(-3, 4), 0), (pd.date_range("2020", periods=7, freq="D"), 0), - (pd.date_range("2020", periods=7, freq="M", tz="Europe/Berlin"), 0.04), + (pd.date_range("2020", periods=7, freq="ME", tz="Europe/Berlin"), 0.04), ], ) def test_fill_gaps(values, index, maxgap, gapvalues, tol): @@ -72,7 +72,7 @@ def test_addheader_tocolumns(df_columns, header, expected_columns): # TODO: put in ... fixture (?) test_index_D = dev.get_index("D") test_index_D_deconstructed = test_index_D.map(lambda ts: (ts.year, ts.month, ts.day)) -test_index_H = dev.get_index("H") +test_index_H = dev.get_index("h") test_index_H_deconstructed = test_index_H.map(lambda ts: (ts.year, ts.month, ts.day)) diff --git a/tests/tools/test_freq.py b/tests/tools/test_freq.py index 88e38fb..ac9b275 100644 --- a/tests/tools/test_freq.py +++ b/tests/tools/test_freq.py @@ -4,18 +4,29 @@ from portfolyo import tools -freqs_small_to_large = ["T", "5T", "15T", "30T", "H", "2H", "D", "MS", "QS", "AS"] +freqs_small_to_large = [ + "min", + "5min", + "15min", + "30min", + "h", + "2h", + "D", + "MS", + "QS", + "YS", +] freqs_small_to_large_valid = [ - "15T", - "H", + "15min", + "h", "D", "MS", "QS", "QS-FEB", - "AS", - "AS-APR", + "YS", + "YS-APR", ] -invalid_freq = ["T", "5T", "2H", "5D", "3MS"] +invalid_freq = ["min", "5min", "2h", "5D", "3MS"] @pytest.mark.parametrize("count", range(1, 30)) @@ -30,12 +41,12 @@ def test_longestshortestfreq(count): @pytest.mark.parametrize( ("start", "end", "expected"), [ - ("2020", "2021", "AS"), + ("2020", "2021", "YS"), ("2020", "2020-04", "QS"), ("2020", "2020-02", "MS"), ("2020", "2020-01-02", "D"), - ("2020", "2020-01-01 01:00", "H"), - ("2020", "2020-01-01 00:15", "15T"), + ("2020", "2020-01-01 01:00", "h"), + ("2020", "2020-01-01 00:15", "15min"), ("2020-03-29", "2020-03-30", "D"), ("2020-03-01", "2020-04-01", "MS"), ("2020-10-25", "2020-10-26", "D"), @@ -57,19 +68,19 @@ def test_fromtdelta(start, end, expected, tz): ("start", "end", "expected"), [ # Hourly. - ("2020-03-29 01:00", "2020-03-29 03:00", "H"), - ("2020-10-25 01:00", "2020-10-25 02:00+0200", "H"), + ("2020-03-29 01:00", "2020-03-29 03:00", "h"), + ("2020-10-25 01:00", "2020-10-25 02:00+0200", "h"), ("2020-10-25 01:00", "2020-10-25 02:00+0100", None), - ("2020-10-25 02:00+0200", "2020-10-25 02:00+0100", "H"), + ("2020-10-25 02:00+0200", "2020-10-25 02:00+0100", "h"), ("2020-10-25 02:00+0200", "2020-10-25 03:00", None), - ("2020-10-25 02:00+0100", "2020-10-25 03:00", "H"), + ("2020-10-25 02:00+0100", "2020-10-25 03:00", "h"), # Quarterhourly. - ("2020-03-29 01:45", "2020-03-29 03:00", "15T"), - ("2020-10-25 01:45", "2020-10-25 02:00+0200", "15T"), + ("2020-03-29 01:45", "2020-03-29 03:00", "15min"), + ("2020-10-25 01:45", "2020-10-25 02:00+0200", "15min"), ("2020-10-25 01:45", "2020-10-25 02:00+0100", None), - ("2020-10-25 02:45+0200", "2020-10-25 02:00+0100", "15T"), + ("2020-10-25 02:45+0200", "2020-10-25 02:00+0100", "15min"), ("2020-10-25 02:45+0200", "2020-10-25 03:00", None), - ("2020-10-25 02:45+0100", "2020-10-25 03:00", "15T"), + ("2020-10-25 02:45+0100", "2020-10-25 03:00", "15min"), ], ) def test_fromtdelta_dst(start, end, expected): @@ -94,9 +105,9 @@ def test_fromtdelta_dst(start, end, expected): # . too few ("D", 2, "MS", ValueError), ("D", 2, "D", "D"), - # 15T, too few - ("15T", 2, "MS", ValueError), - ("15T", 2, "15T", "15T"), + # 15min, too few + ("15min", 2, "MS", ValueError), + ("15min", 2, "15min", "15min"), # invalid freq # . enough ("2D", 10, "MS", ValueError), @@ -148,8 +159,8 @@ def test_setfreq( ("D", 10, "D"), # . too few ("D", 2, None), - # 15T, too few - ("15T", 2, None), + # 15min, too few + ("15min", 2, None), # invalid freq # . enough ("2D", 10, "2D"), @@ -164,7 +175,7 @@ def test_setfreq( # . too few ("QS-APR", 2, None), ("QS", 2, None), - ("AS-FEB", 10, "AS-FEB"), + ("YS-FEB", 10, "YS-FEB"), ], ) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) @@ -191,18 +202,18 @@ def test_guessfreq( # Define your frequencies and their validity freqs_with_validity = [ - ("15T", True), - ("30T", False), + ("15min", True), + ("30min", False), ("D", True), - ("H", True), + ("h", True), ("MS", True), ("QS", True), - ("AS", True), - ("AS-APR", True), + ("YS", True), + ("YS-APR", True), ("QS-FEB", True), - ("T", False), - ("5T", False), - ("2H", False), + ("min", False), + ("5min", False), + ("2h", False), ("5D", False), ("3MS", False), ] @@ -222,20 +233,20 @@ def test_freq_validity(freq: str, is_valid: bool): @pytest.mark.parametrize( ("freq1", "freq2", "strict", "is_supposed_to_fail"), [ - ("15T", "15T", False, False), - ("15T", "15T", True, True), - ("H", "15T", True, False), - ("15T", "H", True, True), - ("15T", "H", False, True), + ("15min", "15min", False, False), + ("15min", "15min", True, True), + ("h", "15min", True, False), + ("15min", "h", True, True), + ("15min", "h", False, True), ("MS", "MS", True, True), ("MS", "MS", False, False), ("MS", "QS-APR", False, True), - ("QS", "AS", True, True), + ("QS", "YS", True, True), ("QS", "QS-APR", False, False), ("QS-FEB", "QS-APR", True, True), ("QS-FEB", "QS-APR", False, False), - ("AS", "QS", False, False), - ("QS-APR", "AS-APR", False, True), + ("YS", "QS", False, False), + ("QS-APR", "YS-APR", False, True), ], ) def test_freq_sufficiently_long( @@ -251,17 +262,17 @@ def test_freq_sufficiently_long( @pytest.mark.parametrize( ("freq1", "freq2", "is_supposed_to_fail"), [ - ("15T", "15T", False), - ("H", "15T", True), - ("15T", "H", True), + ("15min", "15min", False), + ("h", "15min", True), + ("15min", "h", True), ("MS", "MS", False), ("MS", "QS-APR", True), - ("QS", "AS", True), + ("QS", "YS", True), ("QS", "QS-APR", False), ("QS-FEB", "QS-APR", False), - ("AS", "QS", True), - ("QS-APR", "AS-APR", True), - ("AS-APR", "AS-FEB", False), + ("YS", "QS", True), + ("QS-APR", "YS-APR", True), + ("YS-APR", "YS-FEB", False), ], ) def test_freq_equally_long(freq1: str, freq2: str, is_supposed_to_fail: bool): @@ -275,20 +286,20 @@ def test_freq_equally_long(freq1: str, freq2: str, is_supposed_to_fail: bool): @pytest.mark.parametrize( ("freq1", "freq2", "strict", "is_supposed_to_fail"), [ - ("15T", "15T", False, False), - ("15T", "15T", True, True), - ("H", "15T", True, True), - ("15T", "H", True, False), - ("15T", "H", False, False), + ("15min", "15min", False, False), + ("15min", "15min", True, True), + ("h", "15min", True, True), + ("15min", "h", True, False), + ("15min", "h", False, False), ("MS", "MS", True, True), ("MS", "MS", False, False), ("MS", "QS-APR", False, False), - ("QS", "AS", True, False), + ("QS", "YS", True, False), ("QS", "QS-APR", False, False), ("QS-FEB", "QS-APR", True, True), ("QS-FEB", "QS-APR", False, False), - ("AS", "QS", False, True), - ("QS-APR", "AS-APR", False, False), + ("YS", "QS", False, True), + ("QS-APR", "YS-APR", False, False), ], ) def test_freq_sufficiently_short( @@ -308,11 +319,11 @@ def test_freq_sufficiently_short( ("D", "MS", -1), ("MS", "QS", -1), ("MS", "QS-APR", -1), - ("QS", "AS-APR", -1), - ("QS", "AS", -1), + ("QS", "YS-APR", -1), + ("QS", "YS", -1), # upsampling ("QS", "D", 1), - ("AS-APR", "QS", 1), + ("YS-APR", "QS", 1), # the same ("MS", "MS", 0), ("QS", "QS", 0), @@ -320,9 +331,9 @@ def test_freq_sufficiently_short( ("QS", "QS-JAN", 0), # ValueError ("QS", "QS-FEB", ValueError), - ("QS", "AS-FEB", ValueError), - ("AS-APR", "AS", ValueError), - ("AS-FEB", "QS", ValueError), + ("QS", "YS-FEB", ValueError), + ("YS-APR", "YS", ValueError), + ("YS-FEB", "QS", ValueError), ], ) def test_up_pr_down2(source_freq: str, ref_freq: str, expected: int | Exception): diff --git a/tests/tools/test_hedge.py b/tests/tools/test_hedge.py index 38b9dba..8f403ef 100644 --- a/tests/tools/test_hedge.py +++ b/tests/tools/test_hedge.py @@ -19,7 +19,7 @@ ) def test_onehedge_uniformpricesandduration(w_vals, start, w_expected, how): """Test hedge with uniform prices and durations.""" - i = pd.date_range(start, freq="H", periods=len(w_vals), tz="Europe/Berlin") + i = pd.date_range(start, freq="h", periods=len(w_vals), tz="Europe/Berlin") df = pd.DataFrame({"w": w_vals, "p": 100.0}, i) df["duration"] = tools.duration.index(i) @@ -108,8 +108,8 @@ def test_onehedge( @pytest.mark.parametrize("withunits", ["units", "nounits"]) @pytest.mark.parametrize("how", ["vol", "val"]) @pytest.mark.parametrize("bpo", ["b", "po"]) -@pytest.mark.parametrize("aggfreq", ["MS", "QS", "AS"]) -@pytest.mark.parametrize("freq", ["H", "D"]) +@pytest.mark.parametrize("aggfreq", ["MS", "QS", "YS"]) +@pytest.mark.parametrize("freq", ["h", "D"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin"]) def test_hedge_fromexcel(tz, freq, aggfreq, bpo, how, withunits): """Test if hedge results are correctly calculated, by comparing against previously calculated results.""" diff --git a/tests/tools/test_hedge1.py b/tests/tools/test_hedge1.py index e3d1008..ac277c8 100644 --- a/tests/tools/test_hedge1.py +++ b/tests/tools/test_hedge1.py @@ -19,7 +19,7 @@ ) def test_onehedge_uniformpricesandduration(w_vals, start, w_expected, how): """Test hedge with uniform prices and durations.""" - i = pd.date_range(start, freq="H", periods=len(w_vals), tz="Europe/Berlin") + i = pd.date_range(start, freq="h", periods=len(w_vals), tz="Europe/Berlin") df = pd.DataFrame({"w": w_vals, "p": 100.0}, i) df["duration"] = tools.duration.index(i) diff --git a/tests/tools/test_hedge2.py b/tests/tools/test_hedge2.py index bc82b59..d034934 100644 --- a/tests/tools/test_hedge2.py +++ b/tests/tools/test_hedge2.py @@ -22,7 +22,7 @@ def has_units(request): return request.param -@pytest.fixture(scope="session", params=["H", "D"]) +@pytest.fixture(scope="session", params=["h", "D"]) def freq(request): return request.param @@ -48,7 +48,7 @@ def peak_fn(request): return request.param -@pytest.fixture(scope="session", params=["MS", "QS", "AS"]) +@pytest.fixture(scope="session", params=["MS", "QS", "YS"]) def aggfreq(request): return request.param diff --git a/tests/tools/test_hedge_data.xlsx b/tests/tools/test_hedge_data.xlsx index 463bb26..386d8d0 100644 Binary files a/tests/tools/test_hedge_data.xlsx and b/tests/tools/test_hedge_data.xlsx differ diff --git a/tests/tools/test_intersect.py b/tests/tools/test_intersect.py index 18c1770..b425c18 100644 --- a/tests/tools/test_intersect.py +++ b/tests/tools/test_intersect.py @@ -6,39 +6,39 @@ from portfolyo import testing, tools TEST_FREQUENCIES = [ - "AS", - "AS-FEB", - "AS-APR", + "YS", + "YS-FEB", + "YS-APR", "QS", "QS-FEB", "QS-APR", "MS", "D", - "H", - "15T", + "h", + "15min", ] COMMON_END = "2022-02-02" TESTCASES = [ # startdates, freq, expected_startdate # One starts at first day of year. - (("2020-01-01", "2020-01-20"), "15T", "2020-01-20"), - (("2020-01-01", "2020-01-20"), "15T", "2020-01-20"), - (("2020-01-01", "2020-01-20"), "H", "2020-01-20"), - (("2020-01-01", "2020-01-20"), "H", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "15min", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "15min", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "h", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "h", "2020-01-20"), (("2020-01-01", "2020-01-20"), "D", "2020-01-20"), (("2020-01-01", "2020-01-20"), "D", "2020-01-20"), (("2020-01-01", "2020-03-01"), "MS", "2020-03-01"), (("2020-01-01", "2020-03-01"), "MS", "2020-03-01"), (("2020-01-01", "2020-04-01"), "QS", "2020-04-01"), (("2020-01-01", "2020-04-01"), "QS", "2020-04-01"), - (("2020-01-01", "2021-01-01"), "AS", "2021-01-01"), - (("2020-01-01", "2021-01-01"), "AS", "2021-01-01"), + (("2020-01-01", "2021-01-01"), "YS", "2021-01-01"), + (("2020-01-01", "2021-01-01"), "YS", "2021-01-01"), # Both start in middle of year. - (("2020-04-21", "2020-06-20"), "15T", "2020-06-20"), - (("2020-04-21", "2020-06-20"), "15T", "2020-06-20"), - (("2020-04-21", "2020-06-20"), "H", "2020-06-20"), - (("2020-04-21", "2020-06-20"), "H", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "15min", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "15min", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "h", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "h", "2020-06-20"), (("2020-04-21", "2020-06-20"), "D", "2020-06-20"), (("2020-04-21", "2020-06-20"), "D", "2020-06-20"), ] @@ -107,7 +107,7 @@ def test_intersect_distinctfreq( expected_startdate: str, ): """Test if intersection of indices with distinct frequencies gives correct result.""" - otherfreq = "H" if freq == "D" else "D" + otherfreq = "h" if freq == "D" else "D" idxs = [ get_idx(startdates[0], starttime, tz, freq), get_idx(startdates[1], starttime, tz, otherfreq), diff --git a/tests/tools/test_intersect_flex.py b/tests/tools/test_intersect_flex.py index fab118a..fcd9d19 100644 --- a/tests/tools/test_intersect_flex.py +++ b/tests/tools/test_intersect_flex.py @@ -9,23 +9,23 @@ TESTCASES = [ # startdates, freq, expected_startdate # One starts at first day of year. - (("2020-01-01", "2020-01-20"), "15T", "2020-01-20"), - (("2020-01-01", "2020-01-20"), "15T", "2020-01-20"), - (("2020-01-01", "2020-01-20"), "H", "2020-01-20"), - (("2020-01-01", "2020-01-20"), "H", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "15min", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "15min", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "h", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "h", "2020-01-20"), (("2020-01-01", "2020-01-20"), "D", "2020-01-20"), (("2020-01-01", "2020-01-20"), "D", "2020-01-20"), (("2020-01-01", "2020-03-01"), "MS", "2020-03-01"), (("2020-01-01", "2020-03-01"), "MS", "2020-03-01"), (("2020-01-01", "2020-04-01"), "QS", "2020-04-01"), (("2020-01-01", "2020-04-01"), "QS", "2020-04-01"), - (("2020-01-01", "2021-01-01"), "AS", "2021-01-01"), - (("2020-01-01", "2021-01-01"), "AS", "2021-01-01"), + (("2020-01-01", "2021-01-01"), "YS", "2021-01-01"), + (("2020-01-01", "2021-01-01"), "YS", "2021-01-01"), # Both start in middle of year. - (("2020-04-21", "2020-06-20"), "15T", "2020-06-20"), - (("2020-04-21", "2020-06-20"), "15T", "2020-06-20"), - (("2020-04-21", "2020-06-20"), "H", "2020-06-20"), - (("2020-04-21", "2020-06-20"), "H", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "15min", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "15min", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "h", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "h", "2020-06-20"), (("2020-04-21", "2020-06-20"), "D", "2020-06-20"), (("2020-04-21", "2020-06-20"), "D", "2020-06-20"), ] @@ -33,17 +33,17 @@ COMMON_END_2 = "2023-01-01" TESTCASES_2 = [ # startdates, freq, expected_dates # One starts at first day of year. - (("2020-01-01", "2020-01-20"), ("15T", "H"), "2020-01-20"), - (("2020-01-01", "2020-01-20"), ("15T", "D"), "2020-01-20"), - (("2022-04-01", "2021-02-01"), ("H", "MS"), "2022-04-01"), - (("2020-01-01", "2020-04-01"), ("H", "QS"), "2020-04-01"), - (("2020-01-01", "2021-01-01"), ("D", "AS"), "2021-01-01"), + (("2020-01-01", "2020-01-20"), ("15min", "h"), "2020-01-20"), + (("2020-01-01", "2020-01-20"), ("15min", "D"), "2020-01-20"), + (("2022-04-01", "2021-02-01"), ("h", "MS"), "2022-04-01"), + (("2020-01-01", "2020-04-01"), ("h", "QS"), "2020-04-01"), + (("2020-01-01", "2021-01-01"), ("D", "YS"), "2021-01-01"), # Both start in middle of year. - (("2020-04-21", "2020-06-20"), ("15T", "H"), "2020-06-20"), - (("2020-04-21", "2020-06-20"), ("15T", "D"), "2020-06-20"), - (("2020-04-21", "2020-07-01"), ("H", "MS"), "2020-07-01"), - (("2020-04-21", "2020-07-01"), ("H", "QS"), "2020-07-01"), - (("2020-04-21", "2021-01-01"), ("D", "AS"), "2021-01-01"), + (("2020-04-21", "2020-06-20"), ("15min", "h"), "2020-06-20"), + (("2020-04-21", "2020-06-20"), ("15min", "D"), "2020-06-20"), + (("2020-04-21", "2020-07-01"), ("h", "MS"), "2020-07-01"), + (("2020-04-21", "2020-07-01"), ("h", "QS"), "2020-07-01"), + (("2020-04-21", "2021-01-01"), ("D", "YS"), "2021-01-01"), ] @@ -95,7 +95,7 @@ def test_intersect_flex_ignore_start_of_day( do_test_intersect( "idx", idxs, - ValueError if freq == "15T" or freq == "H" else expected_startdate, + ValueError if freq == "15min" or freq == "h" else expected_startdate, expected_tz=tz, expected_freq=freq, expected_starttime=starttime, @@ -192,7 +192,10 @@ def test_ignore_all( # indexorframe: str, idxs, ( ValueError - if freq[0] == "15T" or freq[0] == "H" or freq[1] == "15T" or freq[1] == "H" + if freq[0] == "15min" + or freq[0] == "h" + or freq[1] == "15min" + or freq[1] == "h" else expected_startdate ), expected_tz=tz, diff --git a/tests/tools/test_intersect_flex_frame.py b/tests/tools/test_intersect_flex_frame.py index 5c42b05..9f0d5b6 100644 --- a/tests/tools/test_intersect_flex_frame.py +++ b/tests/tools/test_intersect_flex_frame.py @@ -91,11 +91,11 @@ def test_frames_ignore_freq(types: str, ignore_freq: bool): idx_a = pd.date_range("2022-04-01", "2024-07-01", freq="QS", inclusive="left") a = pd.Series(range(0, 9), idx_a) - idx_b = pd.date_range("2021-01-01", "2024-01-01", freq="AS", inclusive="left") + idx_b = pd.date_range("2021-01-01", "2024-01-01", freq="YS", inclusive="left") b = pd.Series(range(0, 3), idx_b) exp_idx_a = pd.date_range("2023-01-01", "2024-01-01", freq="QS", inclusive="left") - exp_idx_b = pd.date_range("2023-01-01", "2024-01-01", freq="AS", inclusive="left") + exp_idx_b = pd.date_range("2023-01-01", "2024-01-01", freq="YS", inclusive="left") exp_a = pd.Series(range(3, 7), exp_idx_a) exp_b = pd.Series(range(2, 3), exp_idx_b) if types == "series": @@ -131,7 +131,7 @@ def test_frames_ignore_all(types: str, ignore_all: bool): a = pd.Series(range(0, 9), idx_a) idx_b = pd.date_range( - "2021-01-01 06:00", "2024-01-01 06:00", freq="AS", inclusive="left" + "2021-01-01 06:00", "2024-01-01 06:00", freq="YS", inclusive="left" ) b = pd.Series(range(0, 3), idx_b) @@ -143,7 +143,7 @@ def test_frames_ignore_all(types: str, ignore_all: bool): inclusive="left", ) exp_idx_b = pd.date_range( - "2023-01-01 06:00", "2024-01-01 06:00", freq="AS", inclusive="left" + "2023-01-01 06:00", "2024-01-01 06:00", freq="YS", inclusive="left" ) exp_a = pd.Series(range(3, 7), exp_idx_a) exp_b = pd.Series(range(2, 3), exp_idx_b) diff --git a/tests/tools/test_isboundary.py b/tests/tools/test_isboundary.py index ce1f505..2cc81b2 100644 --- a/tests/tools/test_isboundary.py +++ b/tests/tools/test_isboundary.py @@ -7,239 +7,245 @@ from portfolyo import testing, tools TESTCASES = [ # date, i_freq, periods, freq, expected_repeat - ("2020-01-01", "15T", 2 * 366 * 24 * 4, "15T", 1), - ("2020-01-01", "15T", 2 * 366 * 24 * 4, "H", 4), - ("2020-01-01", "15T", 2 * 31 * 24 * 4, "D", 24 * 4), # >2MS - ("2020-01-01", "15T", 2 * 31 * 24 * 4, "MS", (31 * 24 * 4, 29 * 24 * 4)), # >2MS - ("2020-01-01", "15T", 2 * 366 * 24 * 4, "AS", (366 * 24 * 4, 365 * 24 * 4)), # >2AS - ("2020-01-01", "H", 2 * 366 * 24, "15T", 1), - ("2020-01-01", "H", 2 * 366 * 24, "H", 1), - ("2020-01-01", "H", 2 * 31 * 24, "D", 24), # >2MS - ("2020-01-01", "H", 2 * 31 * 24, "MS", (31 * 24, 29 * 24)), # >2MS - ("2020-01-01", "H", 2 * 366 * 24, "AS", (366 * 24, 365 * 24)), # >2AS - ("2020-01-01", "D", 2 * 366, "15T", 1), - ("2020-01-01", "D", 2 * 366, "H", 1), + ("2020-01-01", "15min", 2 * 366 * 24 * 4, "15min", 1), + ("2020-01-01", "15min", 2 * 366 * 24 * 4, "h", 4), + ("2020-01-01", "15min", 2 * 31 * 24 * 4, "D", 24 * 4), # >2MS + ("2020-01-01", "15min", 2 * 31 * 24 * 4, "MS", (31 * 24 * 4, 29 * 24 * 4)), # >2MS + ( + "2020-01-01", + "15min", + 2 * 366 * 24 * 4, + "YS", + (366 * 24 * 4, 365 * 24 * 4), + ), # >2AS + ("2020-01-01", "h", 2 * 366 * 24, "15min", 1), + ("2020-01-01", "h", 2 * 366 * 24, "h", 1), + ("2020-01-01", "h", 2 * 31 * 24, "D", 24), # >2MS + ("2020-01-01", "h", 2 * 31 * 24, "MS", (31 * 24, 29 * 24)), # >2MS + ("2020-01-01", "h", 2 * 366 * 24, "YS", (366 * 24, 365 * 24)), # >2AS + ("2020-01-01", "D", 2 * 366, "15min", 1), + ("2020-01-01", "D", 2 * 366, "h", 1), ("2020-01-01", "D", 2 * 366, "D", 1), ("2020-01-01", "D", 2 * 31, "MS", (31, 29)), # >2MS ("2020-01-01", "D", 6 * 31, "QS", (91, 91)), # >2QS - ("2020-01-01", "D", 2 * 366, "AS", (366, 365)), # >2AS - ("2020-01-01", "MS", 2 * 12, "15T", 1), - ("2020-01-01", "MS", 2 * 12, "H", 1), + ("2020-01-01", "D", 2 * 366, "YS", (366, 365)), # >2AS + ("2020-01-01", "MS", 2 * 12, "15min", 1), + ("2020-01-01", "MS", 2 * 12, "h", 1), ("2020-01-01", "MS", 2 * 12, "D", 1), ("2020-01-01", "MS", 2 * 12, "MS", 1), ("2020-01-01", "MS", 2 * 3, "QS", 3), # >2QS - ("2020-01-01", "MS", 2 * 12, "AS", 12), # >2AS - ("2020-01-01", "QS", 2 * 4, "15T", 1), - ("2020-01-01", "QS", 2 * 4, "H", 1), + ("2020-01-01", "MS", 2 * 12, "YS", 12), # >2AS + ("2020-01-01", "QS", 2 * 4, "15min", 1), + ("2020-01-01", "QS", 2 * 4, "h", 1), ("2020-01-01", "QS", 2 * 4, "D", 1), ("2020-01-01", "QS", 2 * 4, "MS", 1), ("2020-01-01", "QS", 2 * 4, "QS", 1), - ("2020-01-01", "QS", 2 * 4, "AS", 4), # >2AS - ("2020-01-01", "AS", 2, "15T", 1), - ("2020-01-01", "AS", 2, "H", 1), - ("2020-01-01", "AS", 2, "D", 1), - ("2020-01-01", "AS", 2, "MS", 1), - ("2020-01-01", "AS", 2, "QS", 1), - ("2020-01-01", "AS", 2, "AS", 1), + ("2020-01-01", "QS", 2 * 4, "YS", 4), # >2AS + ("2020-01-01", "YS", 2, "15min", 1), + ("2020-01-01", "YS", 2, "h", 1), + ("2020-01-01", "YS", 2, "D", 1), + ("2020-01-01", "YS", 2, "MS", 1), + ("2020-01-01", "YS", 2, "QS", 1), + ("2020-01-01", "YS", 2, "YS", 1), ] TESTCASES_DST = [ # ts, i_freq, periods, freq, expected_repeat # Start of DST, 2 days. - ("2020-03-28 00:00", "15T", 2 * 24 * 4 - 4, "15T", 1), - ("2020-03-28 00:00", "15T", 2 * 24 * 4 - 4, "H", 4), - ("2020-03-28 00:00", "15T", 2 * 24 * 4 - 4, "D", (24 * 4, 23 * 4)), - ("2020-03-28 00:00", "15T", 2 * 24 * 4 - 4, "MS", None), # None at month start - ("2020-03-28 00:00", "15T", 2 * 24 * 4 - 4, "QS", None), # None at quarter start - ("2020-03-28 00:00", "15T", 2 * 24 * 4 - 4, "AS", None), # None at year start - ("2020-03-28 00:00", "H", 2 * 24 - 1, "15T", 1), - ("2020-03-28 00:00", "H", 2 * 24 - 1, "H", 1), - ("2020-03-28 00:00", "H", 2 * 24 - 1, "D", (24, 23)), - ("2020-03-28 00:00", "H", 2 * 24 - 1, "MS", None), # None at month start - ("2020-03-28 00:00", "H", 2 * 24 - 1, "QS", None), # None at quarter start - ("2020-03-28 00:00", "H", 2 * 24 - 1, "AS", None), # None at year start - ("2020-03-28 00:00", "D", 2, "15T", 1), - ("2020-03-28 00:00", "D", 2, "H", 1), + ("2020-03-28 00:00", "15min", 2 * 24 * 4 - 4, "15min", 1), + ("2020-03-28 00:00", "15min", 2 * 24 * 4 - 4, "h", 4), + ("2020-03-28 00:00", "15min", 2 * 24 * 4 - 4, "D", (24 * 4, 23 * 4)), + ("2020-03-28 00:00", "15min", 2 * 24 * 4 - 4, "MS", None), # None at month start + ("2020-03-28 00:00", "15min", 2 * 24 * 4 - 4, "QS", None), # None at quarter start + ("2020-03-28 00:00", "15min", 2 * 24 * 4 - 4, "YS", None), # None at year start + ("2020-03-28 00:00", "h", 2 * 24 - 1, "15min", 1), + ("2020-03-28 00:00", "h", 2 * 24 - 1, "h", 1), + ("2020-03-28 00:00", "h", 2 * 24 - 1, "D", (24, 23)), + ("2020-03-28 00:00", "h", 2 * 24 - 1, "MS", None), # None at month start + ("2020-03-28 00:00", "h", 2 * 24 - 1, "QS", None), # None at quarter start + ("2020-03-28 00:00", "h", 2 * 24 - 1, "YS", None), # None at year start + ("2020-03-28 00:00", "D", 2, "15min", 1), + ("2020-03-28 00:00", "D", 2, "h", 1), ("2020-03-28 00:00", "D", 2, "D", 1), - ("2020-03-28 06:00", "15T", 2 * 24 * 4 - 4, "15T", 1), - ("2020-03-28 06:00", "15T", 2 * 24 * 4 - 4, "H", 4), - ("2020-03-28 06:00", "15T", 2 * 24 * 4 - 4, "D", (23 * 4, 24 * 4)), - ("2020-03-28 06:00", "15T", 2 * 24 * 4 - 4, "MS", None), # None at month start - ("2020-03-28 06:00", "15T", 2 * 24 * 4 - 4, "QS", None), # None at quarter start. - ("2020-03-28 06:00", "15T", 2 * 24 * 4 - 4, "AS", None), # None at year start - ("2020-03-28 06:00", "H", 2 * 24 - 1, "15T", 1), - ("2020-03-28 06:00", "H", 2 * 24 - 1, "H", 1), - ("2020-03-28 06:00", "H", 2 * 24 - 1, "D", (23, 24)), - ("2020-03-28 06:00", "H", 2 * 24 - 1, "MS", None), # None at month start - ("2020-03-28 06:00", "H", 2 * 24 - 1, "QS", None), # None at quarter start - ("2020-03-28 06:00", "H", 2 * 24 - 1, "AS", None), # None at year start - ("2020-03-28 06:00", "D", 2, "15T", 1), - ("2020-03-28 06:00", "D", 2, "H", 1), + ("2020-03-28 06:00", "15min", 2 * 24 * 4 - 4, "15min", 1), + ("2020-03-28 06:00", "15min", 2 * 24 * 4 - 4, "h", 4), + ("2020-03-28 06:00", "15min", 2 * 24 * 4 - 4, "D", (23 * 4, 24 * 4)), + ("2020-03-28 06:00", "15min", 2 * 24 * 4 - 4, "MS", None), # None at month start + ("2020-03-28 06:00", "15min", 2 * 24 * 4 - 4, "QS", None), # None at quarter start. + ("2020-03-28 06:00", "15min", 2 * 24 * 4 - 4, "YS", None), # None at year start + ("2020-03-28 06:00", "h", 2 * 24 - 1, "15min", 1), + ("2020-03-28 06:00", "h", 2 * 24 - 1, "h", 1), + ("2020-03-28 06:00", "h", 2 * 24 - 1, "D", (23, 24)), + ("2020-03-28 06:00", "h", 2 * 24 - 1, "MS", None), # None at month start + ("2020-03-28 06:00", "h", 2 * 24 - 1, "QS", None), # None at quarter start + ("2020-03-28 06:00", "h", 2 * 24 - 1, "YS", None), # None at year start + ("2020-03-28 06:00", "D", 2, "15min", 1), + ("2020-03-28 06:00", "D", 2, "h", 1), ("2020-03-28 06:00", "D", 2, "D", 1), # Start of DST, entire month. - ("2020-03-01 00:00", "15T", 31 * 24 * 4 - 4, "15T", 1), - ("2020-03-01 00:00", "15T", 31 * 24 * 4 - 4, "H", 4), - ("2020-03-01 00:00", "15T", 31 * 24 * 4 - 4, "D", (*[96] * 28, 92, *[96] * 2)), - ("2020-03-01 00:00", "15T", 31 * 24 * 4 - 4, "MS", 31 * 24 * 4 - 4), - ("2020-03-01 00:00", "15T", 31 * 24 * 4 - 4, "AS", None), - ("2020-03-01 00:00", "H", 31 * 24 - 1, "15T", 1), - ("2020-03-01 00:00", "H", 31 * 24 - 1, "H", 1), - ("2020-03-01 00:00", "H", 31 * 24 - 1, "D", (*[24] * 28, 23, *[24] * 2)), - ("2020-03-01 00:00", "H", 31 * 24 - 1, "MS", 31 * 24 - 1), - ("2020-03-01 00:00", "H", 31 * 24 - 1, "AS", None), - ("2020-03-01 00:00", "D", 31, "15T", 1), - ("2020-03-01 00:00", "D", 31, "H", 1), + ("2020-03-01 00:00", "15min", 31 * 24 * 4 - 4, "15min", 1), + ("2020-03-01 00:00", "15min", 31 * 24 * 4 - 4, "h", 4), + ("2020-03-01 00:00", "15min", 31 * 24 * 4 - 4, "D", (*[96] * 28, 92, *[96] * 2)), + ("2020-03-01 00:00", "15min", 31 * 24 * 4 - 4, "MS", 31 * 24 * 4 - 4), + ("2020-03-01 00:00", "15min", 31 * 24 * 4 - 4, "YS", None), + ("2020-03-01 00:00", "h", 31 * 24 - 1, "15min", 1), + ("2020-03-01 00:00", "h", 31 * 24 - 1, "h", 1), + ("2020-03-01 00:00", "h", 31 * 24 - 1, "D", (*[24] * 28, 23, *[24] * 2)), + ("2020-03-01 00:00", "h", 31 * 24 - 1, "MS", 31 * 24 - 1), + ("2020-03-01 00:00", "h", 31 * 24 - 1, "YS", None), + ("2020-03-01 00:00", "D", 31, "15min", 1), + ("2020-03-01 00:00", "D", 31, "h", 1), ("2020-03-01 00:00", "D", 31, "D", 1), ("2020-03-01 00:00", "D", 31, "MS", 31), - ("2020-03-01 00:00", "D", 31, "AS", None), - ("2020-03-01 00:00", "MS", 1, "15T", 1), - ("2020-03-01 00:00", "MS", 1, "H", 1), + ("2020-03-01 00:00", "D", 31, "YS", None), + ("2020-03-01 00:00", "MS", 1, "15min", 1), + ("2020-03-01 00:00", "MS", 1, "h", 1), ("2020-03-01 00:00", "MS", 1, "D", 1), ("2020-03-01 00:00", "MS", 1, "MS", 1), - ("2020-03-01 00:00", "MS", 1, "AS", None), - ("2020-03-01 06:00", "15T", 31 * 24 * 4 - 4, "15T", 1), - ("2020-03-01 06:00", "15T", 31 * 24 * 4 - 4, "H", 4), - ("2020-03-01 06:00", "15T", 31 * 24 * 4 - 4, "D", (*[96] * 27, 92, *[96] * 3)), - ("2020-03-01 06:00", "15T", 31 * 24 * 4 - 4, "MS", 31 * 24 * 4 - 4), - ("2020-03-01 06:00", "15T", 31 * 24 * 4 - 4, "AS", None), - ("2020-03-01 06:00", "H", 31 * 24 - 1, "15T", 1), - ("2020-03-01 06:00", "H", 31 * 24 - 1, "H", 1), - ("2020-03-01 06:00", "H", 31 * 24 - 1, "D", (*[24] * 27, 23, *[24] * 3)), - ("2020-03-01 06:00", "H", 31 * 24 - 1, "MS", 31 * 24 - 1), - ("2020-03-01 06:00", "H", 31 * 24 - 1, "AS", None), - ("2020-03-01 06:00", "D", 31, "15T", 1), - ("2020-03-01 06:00", "D", 31, "H", 1), + ("2020-03-01 00:00", "MS", 1, "YS", None), + ("2020-03-01 06:00", "15min", 31 * 24 * 4 - 4, "15min", 1), + ("2020-03-01 06:00", "15min", 31 * 24 * 4 - 4, "h", 4), + ("2020-03-01 06:00", "15min", 31 * 24 * 4 - 4, "D", (*[96] * 27, 92, *[96] * 3)), + ("2020-03-01 06:00", "15min", 31 * 24 * 4 - 4, "MS", 31 * 24 * 4 - 4), + ("2020-03-01 06:00", "15min", 31 * 24 * 4 - 4, "YS", None), + ("2020-03-01 06:00", "h", 31 * 24 - 1, "15min", 1), + ("2020-03-01 06:00", "h", 31 * 24 - 1, "h", 1), + ("2020-03-01 06:00", "h", 31 * 24 - 1, "D", (*[24] * 27, 23, *[24] * 3)), + ("2020-03-01 06:00", "h", 31 * 24 - 1, "MS", 31 * 24 - 1), + ("2020-03-01 06:00", "h", 31 * 24 - 1, "YS", None), + ("2020-03-01 06:00", "D", 31, "15min", 1), + ("2020-03-01 06:00", "D", 31, "h", 1), ("2020-03-01 06:00", "D", 31, "D", 1), ("2020-03-01 06:00", "D", 31, "MS", 31), - ("2020-03-01 06:00", "D", 31, "AS", None), - ("2020-03-01 06:00", "MS", 1, "15T", 1), - ("2020-03-01 06:00", "MS", 1, "H", 1), + ("2020-03-01 06:00", "D", 31, "YS", None), + ("2020-03-01 06:00", "MS", 1, "15min", 1), + ("2020-03-01 06:00", "MS", 1, "h", 1), ("2020-03-01 06:00", "MS", 1, "D", 1), ("2020-03-01 06:00", "MS", 1, "MS", 1), - ("2020-03-01 06:00", "MS", 1, "AS", None), + ("2020-03-01 06:00", "MS", 1, "YS", None), # End of DST, 2 days. - ("2020-10-24 00:00", "15T", 2 * 24 * 4 + 4, "15T", 1), - ("2020-10-24 00:00", "15T", 2 * 24 * 4 + 4, "H", 4), - ("2020-10-24 00:00", "15T", 2 * 24 * 4 + 4, "D", (24 * 4, 25 * 4)), - ("2020-10-24 00:00", "15T", 2 * 24 * 4 + 4, "MS", None), # None at month start - ("2020-10-24 00:00", "15T", 2 * 24 * 4 + 4, "QS", None), # None at quarter start - ("2020-10-24 00:00", "15T", 2 * 24 * 4 + 4, "AS", None), # None at year start - ("2020-10-24 00:00", "H", 2 * 24 + 1, "15T", 1), - ("2020-10-24 00:00", "H", 2 * 24 + 1, "H", 1), - ("2020-10-24 00:00", "H", 2 * 24 + 1, "D", (24, 25)), - ("2020-10-24 00:00", "H", 2 * 24 + 1, "MS", None), # None at month start - ("2020-10-24 00:00", "H", 2 * 24 + 1, "QS", None), # None at quarter start - ("2020-10-24 00:00", "H", 2 * 24 + 1, "AS", None), # None at year start - ("2020-10-24 00:00", "D", 2 * 24 + 1, "15T", 1), - ("2020-10-24 00:00", "D", 2 * 24 + 1, "H", 1), + ("2020-10-24 00:00", "15min", 2 * 24 * 4 + 4, "15min", 1), + ("2020-10-24 00:00", "15min", 2 * 24 * 4 + 4, "h", 4), + ("2020-10-24 00:00", "15min", 2 * 24 * 4 + 4, "D", (24 * 4, 25 * 4)), + ("2020-10-24 00:00", "15min", 2 * 24 * 4 + 4, "MS", None), # None at month start + ("2020-10-24 00:00", "15min", 2 * 24 * 4 + 4, "QS", None), # None at quarter start + ("2020-10-24 00:00", "15min", 2 * 24 * 4 + 4, "YS", None), # None at year start + ("2020-10-24 00:00", "h", 2 * 24 + 1, "15min", 1), + ("2020-10-24 00:00", "h", 2 * 24 + 1, "h", 1), + ("2020-10-24 00:00", "h", 2 * 24 + 1, "D", (24, 25)), + ("2020-10-24 00:00", "h", 2 * 24 + 1, "MS", None), # None at month start + ("2020-10-24 00:00", "h", 2 * 24 + 1, "QS", None), # None at quarter start + ("2020-10-24 00:00", "h", 2 * 24 + 1, "YS", None), # None at year start + ("2020-10-24 00:00", "D", 2 * 24 + 1, "15min", 1), + ("2020-10-24 00:00", "D", 2 * 24 + 1, "h", 1), ("2020-10-24 00:00", "D", 2 * 24 + 1, "D", 1), - ("2020-10-24 06:00", "15T", 2 * 24 * 4 + 4, "15T", 1), - ("2020-10-24 06:00", "15T", 2 * 24 * 4 + 4, "H", 4), - ("2020-10-24 06:00", "15T", 2 * 24 * 4 + 4, "D", (25 * 4, 24 * 4)), - ("2020-10-24 06:00", "15T", 2 * 24 * 4 + 4, "MS", None), # None at month start - ("2020-10-24 06:00", "15T", 2 * 24 * 4 + 4, "QS", None), # None at quarter start - ("2020-10-24 06:00", "15T", 2 * 24 * 4 + 4, "AS", None), # None at year start - ("2020-10-24 06:00", "H", 2 * 24 + 1, "15T", 1), - ("2020-10-24 06:00", "H", 2 * 24 + 1, "H", 1), - ("2020-10-24 06:00", "H", 2 * 24 + 1, "D", (25, 24)), - ("2020-10-24 06:00", "H", 2 * 24 + 1, "MS", None), # None at month start - ("2020-10-24 06:00", "H", 2 * 24 + 1, "QS", None), # None at quarter start - ("2020-10-24 06:00", "H", 2 * 24 + 1, "AS", None), # None at year start - ("2020-10-24 06:00", "D", 2, "15T", 1), - ("2020-10-24 06:00", "D", 2, "H", 1), + ("2020-10-24 06:00", "15min", 2 * 24 * 4 + 4, "15min", 1), + ("2020-10-24 06:00", "15min", 2 * 24 * 4 + 4, "h", 4), + ("2020-10-24 06:00", "15min", 2 * 24 * 4 + 4, "D", (25 * 4, 24 * 4)), + ("2020-10-24 06:00", "15min", 2 * 24 * 4 + 4, "MS", None), # None at month start + ("2020-10-24 06:00", "15min", 2 * 24 * 4 + 4, "QS", None), # None at quarter start + ("2020-10-24 06:00", "15min", 2 * 24 * 4 + 4, "YS", None), # None at year start + ("2020-10-24 06:00", "h", 2 * 24 + 1, "15min", 1), + ("2020-10-24 06:00", "h", 2 * 24 + 1, "h", 1), + ("2020-10-24 06:00", "h", 2 * 24 + 1, "D", (25, 24)), + ("2020-10-24 06:00", "h", 2 * 24 + 1, "MS", None), # None at month start + ("2020-10-24 06:00", "h", 2 * 24 + 1, "QS", None), # None at quarter start + ("2020-10-24 06:00", "h", 2 * 24 + 1, "YS", None), # None at year start + ("2020-10-24 06:00", "D", 2, "15min", 1), + ("2020-10-24 06:00", "D", 2, "h", 1), ("2020-10-24 06:00", "D", 2, "D", 1), # End of DST, entire month. - ("2020-10-01 00:00", "15T", 31 * 24 * 4 + 4, "15T", 1), - ("2020-10-01 00:00", "15T", 31 * 24 * 4 + 4, "H", 4), - ("2020-10-01 00:00", "15T", 31 * 24 * 4 + 4, "D", (*[96] * 24, 100, *[96] * 6)), - ("2020-10-01 00:00", "15T", 31 * 24 * 4 + 4, "MS", 31 * 24 * 4 + 4), - ("2020-10-01 00:00", "15T", 31 * 24 * 4 + 4, "AS", None), - ("2020-10-01 00:00", "H", 31 * 24 + 1, "15T", 1), - ("2020-10-01 00:00", "H", 31 * 24 + 1, "H", 1), - ("2020-10-01 00:00", "H", 31 * 24 + 1, "D", (*[24] * 24, 25, *[24] * 6)), - ("2020-10-01 00:00", "H", 31 * 24 + 1, "MS", 31 * 24 + 1), - ("2020-10-01 00:00", "H", 31 * 24 + 1, "AS", None), - ("2020-10-01 00:00", "D", 31, "15T", 1), - ("2020-10-01 00:00", "D", 31, "H", 1), + ("2020-10-01 00:00", "15min", 31 * 24 * 4 + 4, "15min", 1), + ("2020-10-01 00:00", "15min", 31 * 24 * 4 + 4, "h", 4), + ("2020-10-01 00:00", "15min", 31 * 24 * 4 + 4, "D", (*[96] * 24, 100, *[96] * 6)), + ("2020-10-01 00:00", "15min", 31 * 24 * 4 + 4, "MS", 31 * 24 * 4 + 4), + ("2020-10-01 00:00", "15min", 31 * 24 * 4 + 4, "YS", None), + ("2020-10-01 00:00", "h", 31 * 24 + 1, "15min", 1), + ("2020-10-01 00:00", "h", 31 * 24 + 1, "h", 1), + ("2020-10-01 00:00", "h", 31 * 24 + 1, "D", (*[24] * 24, 25, *[24] * 6)), + ("2020-10-01 00:00", "h", 31 * 24 + 1, "MS", 31 * 24 + 1), + ("2020-10-01 00:00", "h", 31 * 24 + 1, "YS", None), + ("2020-10-01 00:00", "D", 31, "15min", 1), + ("2020-10-01 00:00", "D", 31, "h", 1), ("2020-10-01 00:00", "D", 31, "D", 1), ("2020-10-01 00:00", "D", 31, "MS", 31), - ("2020-10-01 00:00", "D", 31, "AS", None), - ("2020-10-01 00:00", "MS", 1, "15T", 1), - ("2020-10-01 00:00", "MS", 1, "H", 1), + ("2020-10-01 00:00", "D", 31, "YS", None), + ("2020-10-01 00:00", "MS", 1, "15min", 1), + ("2020-10-01 00:00", "MS", 1, "h", 1), ("2020-10-01 00:00", "MS", 1, "D", 1), ("2020-10-01 00:00", "MS", 1, "MS", 1), - ("2020-10-01 00:00", "MS", 1, "AS", None), - ("2020-10-01 06:00", "15T", 31 * 24 * 4 + 4, "15T", 1), - ("2020-10-01 06:00", "15T", 31 * 24 * 4 + 4, "H", 4), - ("2020-10-01 06:00", "15T", 31 * 24 * 4 + 4, "D", (*[96] * 23, 100, *[96] * 7)), - ("2020-10-01 06:00", "15T", 31 * 24 * 4 + 4, "MS", 31 * 24 * 4 + 4), - ("2020-10-01 06:00", "15T", 31 * 24 * 4 + 4, "AS", None), - ("2020-10-01 06:00", "H", 31 * 24 + 1, "15T", 1), - ("2020-10-01 06:00", "H", 31 * 24 + 1, "H", 1), - ("2020-10-01 06:00", "H", 31 * 24 + 1, "D", (*[24] * 23, 25, *[24] * 7)), - ("2020-10-01 06:00", "H", 31 * 24 + 1, "MS", 31 * 24 + 1), - ("2020-10-01 06:00", "H", 31 * 24 + 1, "AS", None), - ("2020-10-01 06:00", "D", 31, "15T", 1), - ("2020-10-01 06:00", "D", 31, "H", 1), + ("2020-10-01 00:00", "MS", 1, "YS", None), + ("2020-10-01 06:00", "15min", 31 * 24 * 4 + 4, "15min", 1), + ("2020-10-01 06:00", "15min", 31 * 24 * 4 + 4, "h", 4), + ("2020-10-01 06:00", "15min", 31 * 24 * 4 + 4, "D", (*[96] * 23, 100, *[96] * 7)), + ("2020-10-01 06:00", "15min", 31 * 24 * 4 + 4, "MS", 31 * 24 * 4 + 4), + ("2020-10-01 06:00", "15min", 31 * 24 * 4 + 4, "YS", None), + ("2020-10-01 06:00", "h", 31 * 24 + 1, "15min", 1), + ("2020-10-01 06:00", "h", 31 * 24 + 1, "h", 1), + ("2020-10-01 06:00", "h", 31 * 24 + 1, "D", (*[24] * 23, 25, *[24] * 7)), + ("2020-10-01 06:00", "h", 31 * 24 + 1, "MS", 31 * 24 + 1), + ("2020-10-01 06:00", "h", 31 * 24 + 1, "YS", None), + ("2020-10-01 06:00", "D", 31, "15min", 1), + ("2020-10-01 06:00", "D", 31, "h", 1), ("2020-10-01 06:00", "D", 31, "D", 1), ("2020-10-01 06:00", "D", 31, "MS", 31), - ("2020-10-01 06:00", "D", 31, "AS", None), - ("2020-10-01 06:00", "MS", 1, "15T", 1), - ("2020-10-01 06:00", "MS", 1, "H", 1), + ("2020-10-01 06:00", "D", 31, "YS", None), + ("2020-10-01 06:00", "MS", 1, "15min", 1), + ("2020-10-01 06:00", "MS", 1, "h", 1), ("2020-10-01 06:00", "MS", 1, "D", 1), ("2020-10-01 06:00", "MS", 1, "MS", 1), - ("2020-10-01 06:00", "MS", 1, "AS", None), + ("2020-10-01 06:00", "MS", 1, "YS", None), ] TESTCASES_MIDYEAR = [ # date, i_freq, periods, freq, expected_repeat, - ("2019-12-15", "15T", 2 * 366 * 24 * 4, "15T", 1), - ("2019-12-15", "15T", 2 * 366 * 24 * 4, "H", 4), - ("2019-12-15", "15T", 2 * 31 * 24 * 4, "D", 24 * 4), # >2MS - ("2019-12-15", "H", 2 * 366 * 24, "15T", 1), - ("2019-12-15", "H", 2 * 366 * 24, "H", 1), - ("2019-12-15", "H", 2 * 31 * 24, "D", 24), # >2MS - ("2019-12-15", "D", 2 * 366, "15T", 1), - ("2019-12-15", "D", 2 * 366, "H", 1), + ("2019-12-15", "15min", 2 * 366 * 24 * 4, "15min", 1), + ("2019-12-15", "15min", 2 * 366 * 24 * 4, "h", 4), + ("2019-12-15", "15min", 2 * 31 * 24 * 4, "D", 24 * 4), # >2MS + ("2019-12-15", "h", 2 * 366 * 24, "15min", 1), + ("2019-12-15", "h", 2 * 366 * 24, "h", 1), + ("2019-12-15", "h", 2 * 31 * 24, "D", 24), # >2MS + ("2019-12-15", "D", 2 * 366, "15min", 1), + ("2019-12-15", "D", 2 * 366, "h", 1), ("2019-12-15", "D", 2 * 366, "D", 1), - ("2019-12-01", "MS", 2 * 12, "15T", 1), - ("2019-12-01", "MS", 2 * 12, "H", 1), + ("2019-12-01", "MS", 2 * 12, "15min", 1), + ("2019-12-01", "MS", 2 * 12, "h", 1), ("2019-12-01", "MS", 2 * 12, "D", 1), ("2019-12-01", "MS", 2 * 12, "MS", 1), - ("2019-10-01", "QS", 2 * 4, "15T", 1), - ("2019-10-01", "QS", 2 * 4, "H", 1), + ("2019-10-01", "QS", 2 * 4, "15min", 1), + ("2019-10-01", "QS", 2 * 4, "h", 1), ("2019-10-01", "QS", 2 * 4, "D", 1), ("2019-10-01", "QS", 2 * 4, "MS", 1), ("2019-10-01", "QS", 2 * 4, "QS", 1), ] TESTCASES_LEADINGZERO = [ # date, i_freq, periods, freq, expected_repeat, leading_zeros - ("2019-12-15", "15T", 2 * 31 * 24 * 4, "MS", 31 * 24 * 4, 17 * 24 * 4), - ("2019-12-15", "15T", 2 * 366 * 24 * 4, "AS", 366 * 24 * 4, 17 * 24 * 4), - ("2019-12-15", "H", 2 * 31 * 24, "MS", 31 * 24, 17 * 24), - ("2019-12-15", "H", 2 * 366 * 24, "AS", 366 * 24, 17 * 24), + ("2019-12-15", "15min", 2 * 31 * 24 * 4, "MS", 31 * 24 * 4, 17 * 24 * 4), + ("2019-12-15", "15min", 2 * 366 * 24 * 4, "YS", 366 * 24 * 4, 17 * 24 * 4), + ("2019-12-15", "h", 2 * 31 * 24, "MS", 31 * 24, 17 * 24), + ("2019-12-15", "h", 2 * 366 * 24, "YS", 366 * 24, 17 * 24), ("2019-12-15", "D", 2 * 31, "MS", 31, 17), ("2019-12-15", "D", 6 * 31, "QS", 91, 17), - ("2019-12-15", "D", 2 * 366, "AS", 366, 17), # >2AS + ("2019-12-15", "D", 2 * 366, "YS", 366, 17), # >2AS ("2019-12-01", "MS", 2 * 3, "QS", 3, 1), # >2QS - ("2019-12-01", "MS", 2 * 12, "AS", 12, 1), # >2AS - ("2019-10-01", "QS", 2 * 4, "AS", 4, 1), # >2AS + ("2019-12-01", "MS", 2 * 12, "YS", 12, 1), # >2AS + ("2019-10-01", "QS", 2 * 4, "YS", 4, 1), # >2AS # DST start. # ("2020-03-29 00:00", "D", 2*24, "MS", None), #None at month start # ("2020-03-29 00:00", "D", 2*24, "QS", None), #None at quarter start - # ("2020-03-29 00:00", "D", 2*24, "AS", None), #None at year start - # ("2020-03-01 00:00", "15T", 31*24*4-4, "QS", 31*24*4-4), - # ("2020-03-01 00:00", "H", 31*24-1, "QS", 31*24*4-4), + # ("2020-03-29 00:00", "D", 2*24, "YS", None), #None at year start + # ("2020-03-01 00:00", "15min", 31*24*4-4, "QS", 31*24*4-4), + # ("2020-03-01 00:00", "h", 31*24-1, "QS", 31*24*4-4), # ("2020-03-01 00:00", "D", 31, "QS", 31*24*4-4), # ("2020-03-01 00:00", "MS", 1, "QS", 31*24*4-4), - # ("2020-03-01 06:00", "15T", 31*24*4-4, "QS", 31*24*4-4), - # ("2020-03-01 06:00", "H", 31*24-1, "QS", 31*24*4-4), + # ("2020-03-01 06:00", "15min", 31*24*4-4, "QS", 31*24*4-4), + # ("2020-03-01 06:00", "h", 31*24-1, "QS", 31*24*4-4), # ("2020-03-01 06:00", "D", 31, "QS", 31*24*4-4), # ("2020-03-01 06:00", "MS", 1, "QS", 31*24*4-4), - # ("2020-10-01 00:00", "15T", 31*24*4+4, "QS", 31*24*4-4), - # ("2020-10-01 00:00", "H", 31*24+1, "QS", 31*24+1*4-4), + # ("2020-10-01 00:00", "15min", 31*24*4+4, "QS", 31*24*4-4), + # ("2020-10-01 00:00", "h", 31*24+1, "QS", 31*24+1*4-4), # ("2020-10-01 00:00", "D", 31, "QS", 31*24*4-4), # ("2020-10-01 00:00", "MS", 1, "QS", 31*24*4-4), - # ("2020-10-01 06:00", "15T", 31*24*4+4, "QS", 31*24*4+4-4), - # ("2020-10-01 06:00", "H", 31*24+1, "QS", 31*24+1*4-4), + # ("2020-10-01 06:00", "15min", 31*24*4+4, "QS", 31*24*4+4-4), + # ("2020-10-01 06:00", "h", 31*24+1, "QS", 31*24+1*4-4), # ("2020-10-01 06:00", "D", 31, "QS", 31*24+1*4-4), # ("2020-10-01 06:00", "MS", 1, "QS", 31*24*4-4), ] @@ -340,66 +346,66 @@ def do_test_index(ts, i_freq, periods, tz, freq, expected_repeat, leading_zeros) TESTCASES_STAMP = [ # ts, freq, offset_hours, expected - ("2020", "15T", 0, True), - ("2020", "15T", 6, True), - ("2020", "H", 0, True), - ("2020", "H", 6, True), + ("2020", "15min", 0, True), + ("2020", "15min", 6, True), + ("2020", "h", 0, True), + ("2020", "h", 6, True), ("2020", "D", 0, True), ("2020", "D", 6, False), ("2020", "MS", 0, True), ("2020", "MS", 6, False), - ("2020", "AS", 0, True), - ("2020", "AS", 6, False), - ("2020-04-01", "15T", 0, True), - ("2020-04-01", "15T", 6, True), - ("2020-04-01", "H", 0, True), - ("2020-04-01", "H", 6, True), + ("2020", "YS", 0, True), + ("2020", "YS", 6, False), + ("2020-04-01", "15min", 0, True), + ("2020-04-01", "15min", 6, True), + ("2020-04-01", "h", 0, True), + ("2020-04-01", "h", 6, True), ("2020-04-01", "D", 0, True), ("2020-04-01", "D", 6, False), ("2020-04-01", "MS", 0, True), ("2020-04-01", "MS", 6, False), - ("2020-04-01", "AS", 0, False), - ("2020-04-01", "AS", 6, False), - ("2020-01-01 15:00", "15T", 0, True), - ("2020-01-01 15:00", "15T", 6, True), - ("2020-01-01 15:00", "H", 0, True), - ("2020-01-01 15:00", "H", 6, True), + ("2020-04-01", "YS", 0, False), + ("2020-04-01", "YS", 6, False), + ("2020-01-01 15:00", "15min", 0, True), + ("2020-01-01 15:00", "15min", 6, True), + ("2020-01-01 15:00", "h", 0, True), + ("2020-01-01 15:00", "h", 6, True), ("2020-01-01 15:00", "D", 0, False), ("2020-01-01 15:00", "D", 6, False), ("2020-01-01 15:00", "MS", 0, False), ("2020-01-01 15:00", "MS", 6, False), - ("2020-01-01 15:00", "AS", 0, False), - ("2020-01-01 15:00", "AS", 6, False), - ("2020-01-01 15:45", "15T", 0, True), - ("2020-01-01 15:45", "15T", 6, True), - ("2020-01-01 15:45", "H", 0, False), - ("2020-01-01 15:45", "H", 6, False), + ("2020-01-01 15:00", "YS", 0, False), + ("2020-01-01 15:00", "YS", 6, False), + ("2020-01-01 15:45", "15min", 0, True), + ("2020-01-01 15:45", "15min", 6, True), + ("2020-01-01 15:45", "h", 0, False), + ("2020-01-01 15:45", "h", 6, False), ("2020-01-01 15:45", "D", 0, False), ("2020-01-01 15:45", "D", 6, False), ("2020-01-01 15:45", "MS", 0, False), ("2020-01-01 15:45", "MS", 6, False), - ("2020-01-01 15:45", "AS", 0, False), - ("2020-01-01 15:45", "AS", 6, False), - ("2020-01-01 06:00", "15T", 0, True), - ("2020-01-01 06:00", "15T", 6, True), - ("2020-01-01 06:00", "H", 0, True), - ("2020-01-01 06:00", "H", 6, True), + ("2020-01-01 15:45", "YS", 0, False), + ("2020-01-01 15:45", "YS", 6, False), + ("2020-01-01 06:00", "15min", 0, True), + ("2020-01-01 06:00", "15min", 6, True), + ("2020-01-01 06:00", "h", 0, True), + ("2020-01-01 06:00", "h", 6, True), ("2020-01-01 06:00", "D", 0, False), ("2020-01-01 06:00", "D", 6, True), ("2020-01-01 06:00", "MS", 0, False), ("2020-01-01 06:00", "MS", 6, True), - ("2020-01-01 06:00", "AS", 0, False), - ("2020-01-01 06:00", "AS", 6, True), - ("2020-04-21 06:00", "15T", 0, True), - ("2020-04-21 06:00", "15T", 6, True), - ("2020-04-21 06:00", "H", 0, True), - ("2020-04-21 06:00", "H", 6, True), + ("2020-01-01 06:00", "YS", 0, False), + ("2020-01-01 06:00", "YS", 6, True), + ("2020-04-21 06:00", "15min", 0, True), + ("2020-04-21 06:00", "15min", 6, True), + ("2020-04-21 06:00", "h", 0, True), + ("2020-04-21 06:00", "h", 6, True), ("2020-04-21 06:00", "D", 0, False), ("2020-04-21 06:00", "D", 6, True), ("2020-04-21 06:00", "MS", 0, False), ("2020-04-21 06:00", "MS", 6, False), - ("2020-04-21 06:00", "AS", 0, False), - ("2020-04-21 06:00", "AS", 6, False), + ("2020-04-21 06:00", "YS", 0, False), + ("2020-04-21 06:00", "YS", 6, False), ] diff --git a/tests/tools/test_peakconvert.py b/tests/tools/test_peakconvert.py index 0b7eff9..c18e22d 100644 --- a/tests/tools/test_peakconvert.py +++ b/tests/tools/test_peakconvert.py @@ -27,7 +27,7 @@ "base": [80.0, 80, 80, 80], "offpeak": [68.8510638, 68.8699360, 68.9361702, 68.9361702], }, - pd.date_range("2020", periods=4, freq="AS", tz="Europe/Berlin"), + pd.date_range("2020", periods=4, freq="YS", tz="Europe/Berlin"), ), ], ) @@ -59,7 +59,7 @@ def test_completebpoframe_averagable(bpoframe, testcol: str, withunits: str): "base": [280.0, 180, 80, 380], "offpeak": [180, 80, -20, 280], }, - pd.date_range("2020", periods=4, freq="AS", tz="Europe/Berlin"), + pd.date_range("2020", periods=4, freq="YS", tz="Europe/Berlin"), ), ], ) diff --git a/tests/tools/test_peakfn.py b/tests/tools/test_peakfn.py index df8a9e4..1bc9215 100644 --- a/tests/tools/test_peakfn.py +++ b/tests/tools/test_peakfn.py @@ -17,56 +17,56 @@ def index(start: str, end: str, freq: str, tz: str) -> pd.DatetimeIndex: f_germanpower = tools.peakfn.factory(dt.time(hour=8), dt.time(hour=20)) TESTCASES_GERMANPOWER = [ # end, freq, count, stretch - ("2020-01-08", "15T", 5 * 12 * 4, (32, 79)), - ("2020-01-08", "H", 5 * 12, (8, 19)), - ("2020-04-01", "15T", 65 * 12 * 4, (-64, -17)), - ("2020-04-01", "H", 65 * 12, (-16, -5)), - ("2021", "15T", 262 * 12 * 4, (-64, -17)), - ("2021", "H", 262 * 12, (-16, -5)), + ("2020-01-08", "15min", 5 * 12 * 4, (32, 79)), + ("2020-01-08", "h", 5 * 12, (8, 19)), + ("2020-04-01", "15min", 65 * 12 * 4, (-64, -17)), + ("2020-04-01", "h", 65 * 12, (-16, -5)), + ("2021", "15min", 262 * 12 * 4, (-64, -17)), + ("2021", "h", 262 * 12, (-16, -5)), ("2021", "D", ValueError, None), ("2021", "MS", ValueError, None), ("2021", "QS", ValueError, None), - ("2021", "AS", ValueError, None), + ("2021", "YS", ValueError, None), ] f_everyday_13half = tools.peakfn.factory( dt.time(hour=8), dt.time(hour=21, minute=30), [1, 2, 3, 4, 5, 6, 7] ) TESTCASES_13HALF = [ # end, freq, count, stretch - ("2020-01-08", "15T", 7 * 13.5 * 4, (32, 85)), - ("2020-04-01", "15T", 91 * 13.5 * 4, (-64, -11)), - ("2021", "15T", 366 * 13.5 * 4, (-64, -11)), - ("2021", "H", ValueError, None), + ("2020-01-08", "15min", 7 * 13.5 * 4, (32, 85)), + ("2020-04-01", "15min", 91 * 13.5 * 4, (-64, -11)), + ("2021", "15min", 366 * 13.5 * 4, (-64, -11)), + ("2021", "h", ValueError, None), ("2021", "D", ValueError, None), ("2021", "MS", ValueError, None), ("2021", "QS", ValueError, None), - ("2021", "AS", ValueError, None), + ("2021", "YS", ValueError, None), ] f_workingdays_full = tools.peakfn.factory(None, None, [1, 2, 3, 4, 5]) TESTCASES_WORKINGDAYS = [ # end, freq, count, stretch - ("2020-01-08", "15T", 5 * 24 * 4, (0, 72 * 4 - 1)), - ("2020-01-08", "H", 5 * 24, (0, 72 - 1)), + ("2020-01-08", "15min", 5 * 24 * 4, (0, 72 * 4 - 1)), + ("2020-01-08", "h", 5 * 24, (0, 72 - 1)), ("2020-01-08", "D", 5, (0, 3 - 1)), - ("2020-04-01", "15T", 65 * 24 * 4, (0, 72 * 4 - 1)), # avoid DST transition - ("2020-04-01", "H", 65 * 24, (0, 72 - 1)), # avoid DST transition + ("2020-04-01", "15min", 65 * 24 * 4, (0, 72 * 4 - 1)), # avoid DST transition + ("2020-04-01", "h", 65 * 24, (0, 72 - 1)), # avoid DST transition ("2020-04-01", "D", 65, (-9, -4 - 1)), - ("2021", "15T", 262 * 24 * 4, (-11 * 24 * 4, -6 * 24 * 4 - 1)), - ("2021", "H", 262 * 24, (-11 * 24, -6 * 24 - 1)), + ("2021", "15min", 262 * 24 * 4, (-11 * 24 * 4, -6 * 24 * 4 - 1)), + ("2021", "h", 262 * 24, (-11 * 24, -6 * 24 - 1)), ("2021", "D", 262, (-11, -6 - 1)), ("2021", "MS", ValueError, None), ("2021", "QS", ValueError, None), - ("2021", "AS", ValueError, None), + ("2021", "YS", ValueError, None), ] f_everyday_until6 = tools.peakfn.factory(None, dt.time(hour=6), [1, 2, 3, 4, 5, 6, 7]) TESTCASES_EVERYDAY6 = [ # month, freq, tz, count, stretch - (1, "15T", None, 31 * 6 * 4, (24 * 4, 30 * 4 - 1)), - (1, "15T", "Europe/Berlin", 31 * 6 * 4, (24 * 4, 30 * 4 - 1)), - (1, "15T", "Asia/Kolkata", 31 * 6 * 4, (24 * 4, 30 * 4 - 1)), - (1, "H", None, 31 * 6, (24, 30 - 1)), - (1, "H", "Europe/Berlin", 31 * 6, (24, 30 - 1)), - (1, "H", "Asia/Kolkata", 31 * 6, (24, 30 - 1)), + (1, "15min", None, 31 * 6 * 4, (24 * 4, 30 * 4 - 1)), + (1, "15min", "Europe/Berlin", 31 * 6 * 4, (24 * 4, 30 * 4 - 1)), + (1, "15min", "Asia/Kolkata", 31 * 6 * 4, (24 * 4, 30 * 4 - 1)), + (1, "h", None, 31 * 6, (24, 30 - 1)), + (1, "h", "Europe/Berlin", 31 * 6, (24, 30 - 1)), + (1, "h", "Asia/Kolkata", 31 * 6, (24, 30 - 1)), (1, "D", None, ValueError, None), (1, "D", "Europe/Berlin", ValueError, None), (1, "D", "Asia/Kolkata", ValueError, None), @@ -76,21 +76,21 @@ def index(start: str, end: str, freq: str, tz: str) -> pd.DatetimeIndex: (1, "QS", None, ValueError, None), (1, "QS", "Europe/Berlin", ValueError, None), (1, "QS", "Asia/Kolkata", ValueError, None), - (1, "AS", None, ValueError, None), - (1, "AS", "Europe/Berlin", ValueError, None), - (1, "AS", "Asia/Kolkata", ValueError, None), - (3, "15T", None, 31 * 6 * 4, (-72 * 4, -66 * 4 - 1)), - (3, "15T", "Europe/Berlin", (31 * 6 - 1) * 4, (-71 * 4, -66 * 4 - 1)), - (3, "15T", "Asia/Kolkata", 31 * 6 * 4, (-72 * 4, -66 * 4 - 1)), - (3, "H", None, 31 * 6, (-72, -66 - 1)), - (3, "H", "Europe/Berlin", 31 * 6 - 1, (-71, -66 - 1)), # dst start; one hour less - (3, "H", "Asia/Kolkata", 31 * 6, (-72, -66 - 1)), - (10, "15T", None, 31 * 6 * 4, (-168 * 4, -162 * 4 - 1)), - (10, "15T", "Europe/Berlin", (31 * 6 + 1) * 4, (-169 * 4, -162 * 4 - 1)), - (10, "15T", "Asia/Kolkata", 31 * 6 * 4, (-168 * 4, -162 * 4 - 1)), - (10, "H", None, 31 * 6, (-168, -162 - 1)), - (10, "H", "Europe/Berlin", 31 * 6 + 1, (-169, -162 - 1)), # dst end; one hour more - (10, "H", "Asia/Kolkata", 31 * 6, (-168, -162 - 1)), + (1, "YS", None, ValueError, None), + (1, "YS", "Europe/Berlin", ValueError, None), + (1, "YS", "Asia/Kolkata", ValueError, None), + (3, "15min", None, 31 * 6 * 4, (-72 * 4, -66 * 4 - 1)), + (3, "15min", "Europe/Berlin", (31 * 6 - 1) * 4, (-71 * 4, -66 * 4 - 1)), + (3, "15min", "Asia/Kolkata", 31 * 6 * 4, (-72 * 4, -66 * 4 - 1)), + (3, "h", None, 31 * 6, (-72, -66 - 1)), + (3, "h", "Europe/Berlin", 31 * 6 - 1, (-71, -66 - 1)), # dst start; one hour less + (3, "h", "Asia/Kolkata", 31 * 6, (-72, -66 - 1)), + (10, "15min", None, 31 * 6 * 4, (-168 * 4, -162 * 4 - 1)), + (10, "15min", "Europe/Berlin", (31 * 6 + 1) * 4, (-169 * 4, -162 * 4 - 1)), + (10, "15min", "Asia/Kolkata", 31 * 6 * 4, (-168 * 4, -162 * 4 - 1)), + (10, "h", None, 31 * 6, (-168, -162 - 1)), + (10, "h", "Europe/Berlin", 31 * 6 + 1, (-169, -162 - 1)), # dst end; one hour more + (10, "h", "Asia/Kolkata", 31 * 6, (-168, -162 - 1)), ] @@ -184,7 +184,7 @@ def do_test( @pytest.mark.parametrize(("tz", "mar_b_corr"), [(None, 0), ("Europe/Berlin", -1)]) @pytest.mark.parametrize("month", [1, 2, 3]) -@pytest.mark.parametrize("freq", ["D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq", ["D", "MS", "QS", "YS"]) @pytest.mark.parametrize( ("year", "bp", "jan_1_weekday", "jan_p", "feb_p", "mar_p"), [ @@ -208,7 +208,7 @@ def test_peakduration_longfreqs( i = pd.date_range(start, freq=freq, periods=1) # Expected values. - if freq == "AS": + if freq == "YS": b = 24 * bp[0] p = 12 * bp[1] elif freq == "QS": @@ -285,24 +285,24 @@ def test_peakduration_longfreqs( ), # Hours ( - pd.date_range("2020", freq="H", periods=48, tz=None), + pd.date_range("2020", freq="h", periods=48, tz=None), [1] * 48, [*[0] * 8, *[1] * 12, *[0] * 12, *[1] * 12, *[0] * 4], ), # . End-of-March: DST (if observed in tz) ( - pd.date_range("2020-03-29", freq="H", periods=48, tz=None), + pd.date_range("2020-03-29", freq="h", periods=48, tz=None), [1] * 48, [*[0] * 32, *[1] * 12, *[0] * 4], ), ( - pd.date_range("2020-03-29", freq="H", periods=47, tz="Europe/Berlin"), + pd.date_range("2020-03-29", freq="h", periods=47, tz="Europe/Berlin"), [1] * 47, [*[0] * 31, *[1] * 12, *[0] * 4], ), # Quarterhours ( - pd.date_range("2020", freq="15T", periods=192, tz=None), + pd.date_range("2020", freq="15min", periods=192, tz=None), [0.25] * 192, [*[0] * 32, *[0.25] * 48, *[0] * 48, *[0.25] * 48, *[0] * 16], ), diff --git a/tests/tools/test_product.py b/tests/tools/test_product.py index aabaa15..2a9b7ac 100644 --- a/tests/tools/test_product.py +++ b/tests/tools/test_product.py @@ -13,32 +13,32 @@ ("2020-1-1", "m", 0, "2020-1-1"), ("2020-1-1", "q", 0, "2020-1-1"), ("2020-1-1", "s", 0, "2019-10-1"), - ("2020-1-1", "a", 0, "2020-1-1"), + ("2020-1-1", "y", 0, "2020-1-1"), ("2020-1-1", "d", 1, "2020-1-2"), ("2020-1-1", "m", 1, "2020-2-1"), ("2020-1-1", "q", 1, "2020-4-1"), ("2020-1-1", "s", 1, "2020-4-1"), - ("2020-1-1", "a", 1, "2021-1-1"), + ("2020-1-1", "y", 1, "2021-1-1"), ("2020-1-1", "d", 3, "2020-1-4"), ("2020-1-1", "m", 3, "2020-4-1"), ("2020-1-1", "q", 3, "2020-10-1"), ("2020-1-1", "s", 3, "2021-4-1"), - ("2020-1-1", "a", 3, "2023-1-1"), + ("2020-1-1", "y", 3, "2023-1-1"), ("2020-1-31", "d", 0, "2020-1-31"), ("2020-1-31", "m", 0, "2020-1-1"), ("2020-1-31", "q", 0, "2020-1-1"), ("2020-1-31", "s", 0, "2019-10-1"), - ("2020-1-31", "a", 0, "2020-1-1"), + ("2020-1-31", "y", 0, "2020-1-1"), ("2020-1-31", "d", 1, "2020-2-1"), ("2020-1-31", "m", 1, "2020-2-1"), ("2020-1-31", "q", 1, "2020-4-1"), ("2020-1-31", "s", 1, "2020-4-1"), - ("2020-1-31", "a", 1, "2021-1-1"), + ("2020-1-31", "y", 1, "2021-1-1"), ("2020-1-31", "d", 3, "2020-2-3"), ("2020-1-31", "m", 3, "2020-4-1"), ("2020-1-31", "q", 3, "2020-10-1"), ("2020-1-31", "s", 3, "2021-4-1"), - ("2020-1-31", "a", 3, "2023-1-1"), + ("2020-1-31", "y", 3, "2023-1-1"), ("2020-2-14", "s", 0, "2019-10-1"), ("2020-3-14", "s", 0, "2019-10-1"), ("2020-4-14", "s", 0, "2020-4-1"), @@ -95,7 +95,7 @@ def test_deliveryperiod( ) assert ts_deliv[0] == expected_left try: - add = {"m": 1, "q": 3, "s": 6, "a": 12}[period_type] + add = {"m": 1, "q": 3, "s": 6, "y": 12}[period_type] assert ts_deliv[1] == expected_left + pd.offsets.MonthBegin(add) except KeyError: assert ts_deliv[1] == expected_left + dt.timedelta(1) diff --git a/tests/tools/test_right.py b/tests/tools/test_right.py index b4db911..67a0d12 100644 --- a/tests/tools/test_right.py +++ b/tests/tools/test_right.py @@ -8,43 +8,43 @@ TESTCASES = [ # ts, freq, expected_ts_right # First day of X and start of day. - ("2020-01-01", "15T", "2020-01-01 00:15"), - ("2020-01-01", "H", "2020-01-01 01:00"), + ("2020-01-01", "15min", "2020-01-01 00:15"), + ("2020-01-01", "h", "2020-01-01 01:00"), ("2020-01-01", "D", "2020-01-02"), ("2020-01-01", "MS", "2020-02-01"), ("2020-01-01", "QS", "2020-04-01"), - ("2020-01-01", "AS", "2021-01-01"), + ("2020-01-01", "YS", "2021-01-01"), # First day of X but not start of day. - ("2020-01-01 06:00", "15T", "2020-01-01 06:15"), - ("2020-01-01 06:00", "H", "2020-01-01 07:00"), + ("2020-01-01 06:00", "15min", "2020-01-01 06:15"), + ("2020-01-01 06:00", "h", "2020-01-01 07:00"), ("2020-01-01 06:00", "D", "2020-01-02 06:00"), ("2020-01-01 06:00", "MS", "2020-02-01 06:00"), ("2020-01-01 06:00", "QS", "2020-04-01 06:00"), - ("2020-01-01 06:00", "AS", "2021-01-01 06:00"), + ("2020-01-01 06:00", "YS", "2021-01-01 06:00"), # Not first day of X but start of day. - ("2020-04-01", "15T", "2020-04-01 00:15"), - ("2020-04-01", "H", "2020-04-01 01:00"), + ("2020-04-01", "15min", "2020-04-01 00:15"), + ("2020-04-01", "h", "2020-04-01 01:00"), ("2020-04-01", "D", "2020-04-02"), ("2020-04-01", "MS", "2020-05-01"), ("2020-04-01", "QS", "2020-07-01"), - ("2020-04-21", "15T", "2020-04-21 00:15"), - ("2020-04-21", "H", "2020-04-21 01:00"), + ("2020-04-21", "15min", "2020-04-21 00:15"), + ("2020-04-21", "h", "2020-04-21 01:00"), ("2020-04-21", "D", "2020-04-22"), # Not first day of X and not start of day. - ("2020-04-01 06:00", "15T", "2020-04-01 06:15"), - ("2020-04-01 06:00", "H", "2020-04-01 07:00"), + ("2020-04-01 06:00", "15min", "2020-04-01 06:15"), + ("2020-04-01 06:00", "h", "2020-04-01 07:00"), ("2020-04-01 06:00", "D", "2020-04-02 06:00"), ("2020-04-01 06:00", "MS", "2020-05-01 06:00"), ("2020-04-01 06:00", "QS", "2020-07-01 06:00"), - ("2020-04-21 06:00", "15T", "2020-04-21 06:15"), - ("2020-04-21 06:00", "H", "2020-04-21 07:00"), + ("2020-04-21 06:00", "15min", "2020-04-21 06:15"), + ("2020-04-21 06:00", "h", "2020-04-21 07:00"), ("2020-04-21 06:00", "D", "2020-04-22 06:00"), ] TESTCASES_DST = [ # ts, freq, expected_ts_right, periods # Start of DST. - ("2020-03-29 01:00", "15T", "2020-03-29 01:15", 92 + 9 * 96), - ("2020-03-29 01:00", "H", "2020-03-29 03:00", 23 + 9 * 24), + ("2020-03-29 01:00", "15min", "2020-03-29 01:15", 92 + 9 * 96), + ("2020-03-29 01:00", "h", "2020-03-29 03:00", 23 + 9 * 24), ("2020-03-29 00:00", "D", "2020-03-30 00:00", None), ("2020-03-29 01:00", "D", "2020-03-30 01:00", None), ("2020-03-29 03:00", "D", "2020-03-30 03:00", None), @@ -54,14 +54,14 @@ ("2020-01-01 00:00", "QS", "2020-04-01 00:00", None), ("2020-01-01 06:00", "QS", "2020-04-01 06:00", None), # End of DST. - ("2020-10-25 01:00", "15T", "2020-10-25 01:15+0200", 100 + 9 * 96), - ("2020-10-25 02:00+0200", "15T", "2020-10-25 02:15+0200", 100 + 9 * 96), - ("2020-10-25 02:00+0100", "15T", "2020-10-25 02:15+0100", 10 * 96), - ("2020-10-25 03:00", "15T", "2020-10-25 03:15", 10 * 96), - ("2020-10-25 01:00", "H", "2020-10-25 02:00+0200", 25 + 9 * 24), - ("2020-10-25 02:00+0200", "H", "2020-10-25 02:00+0100", 25 + 9 * 24), - ("2020-10-25 02:00+0100", "H", "2020-10-25 03:00", 10 * 24), - ("2020-10-25 03:00", "H", "2020-10-25 04:00", 10 * 24), + ("2020-10-25 01:00", "15min", "2020-10-25 01:15+0200", 100 + 9 * 96), + ("2020-10-25 02:00+0200", "15min", "2020-10-25 02:15+0200", 100 + 9 * 96), + ("2020-10-25 02:00+0100", "15min", "2020-10-25 02:15+0100", 10 * 96), + ("2020-10-25 03:00", "15min", "2020-10-25 03:15", 10 * 96), + ("2020-10-25 01:00", "h", "2020-10-25 02:00+0200", 25 + 9 * 24), + ("2020-10-25 02:00+0200", "h", "2020-10-25 02:00+0100", 25 + 9 * 24), + ("2020-10-25 02:00+0100", "h", "2020-10-25 03:00", 10 * 24), + ("2020-10-25 03:00", "h", "2020-10-25 04:00", 10 * 24), ("2020-10-25 00:00", "D", "2020-10-26 00:00", None), ("2020-10-25 01:00", "D", "2020-10-26 01:00", None), ("2020-10-25 03:00", "D", "2020-10-26 03:00", None), @@ -72,7 +72,7 @@ ("2020-10-01 06:00", "QS", "2021-01-01 06:00", None), ] -TESTCASES_15T_STAMPONLY = [ # ts, expected_ts_right +TESTCASES_15min_STAMPONLY = [ # ts, expected_ts_right # Start of DST. ("2020-03-29 00:45", "2020-03-29 01:00"), ("2020-03-29 01:45", "2020-03-29 03:00"), @@ -112,10 +112,10 @@ def test_right_stamp_dst(ts: str, freq: str, expected_ts_right: str, periods: in do_test_stamp(ts, freq, "Europe/Berlin", expected_ts_right) -@pytest.mark.parametrize(("ts", "expected_ts_right"), TESTCASES_15T_STAMPONLY) -def test_right_stamp_15T(ts: str, expected_ts_right: str): +@pytest.mark.parametrize(("ts", "expected_ts_right"), TESTCASES_15min_STAMPONLY) +def test_right_stamp_15min(ts: str, expected_ts_right: str): """Test if right timestamp is correctly calculated for timestamp at non-round hour.""" - do_test_stamp(ts, "15T", "Europe/Berlin", expected_ts_right) + do_test_stamp(ts, "15min", "Europe/Berlin", expected_ts_right) def do_test_index(ts, freq, tz, expected_ts_right, periods): diff --git a/tests/tools/test_righttoleft.py b/tests/tools/test_righttoleft.py index ae594d5..e0d3746 100644 --- a/tests/tools/test_righttoleft.py +++ b/tests/tools/test_righttoleft.py @@ -6,26 +6,26 @@ TESTCASES = [ # start, periods, freq, expected_start # Natural-boundary. # Natural-boundary timestamps; without DST-transition. - ("2020-02-01 1:00", 24, "H", "2020-02-01"), - ("2020-02-01 0:15", 96, "15T", "2020-02-01"), + ("2020-02-01 1:00", 24, "h", "2020-02-01"), + ("2020-02-01 0:15", 96, "15min", "2020-02-01"), ("2020-02-02", 28, "D", "2020-02-01"), # Natural-boundary timestamps; with DST-start. - ("2020-03-29 1:00", 24, "H", "2020-03-29"), - ("2020-03-29 4:00", 24, "H", "2020-03-29 3:00"), - ("2020-03-29 0:15", 96, "15T", "2020-03-29"), - ("2020-03-29 0:30", 96, "15T", "2020-03-29 0:15"), - ("2020-03-29 1:30", 96, "15T", "2020-03-29 1:15"), - ("2020-03-29 3:30", 96, "15T", "2020-03-29 3:15"), - ("2020-03-29 3:15", 96, "15T", "2020-03-29 3:00"), + ("2020-03-29 1:00", 24, "h", "2020-03-29"), + ("2020-03-29 4:00", 24, "h", "2020-03-29 3:00"), + ("2020-03-29 0:15", 96, "15min", "2020-03-29"), + ("2020-03-29 0:30", 96, "15min", "2020-03-29 0:15"), + ("2020-03-29 1:30", 96, "15min", "2020-03-29 1:15"), + ("2020-03-29 3:30", 96, "15min", "2020-03-29 3:15"), + ("2020-03-29 3:15", 96, "15min", "2020-03-29 3:00"), ("2020-03-02", 31, "D", "2020-03-01"), # Natural-boundary timestamps; with DST-end. - ("2020-10-25 1:00", 24, "H", "2020-10-25"), - ("2020-10-25 4:00", 24, "H", "2020-10-25 3:00"), - ("2020-10-25 0:15", 96, "15T", "2020-10-25"), - ("2020-10-25 0:30", 96, "15T", "2020-10-25 0:15"), - ("2020-10-25 1:30", 96, "15T", "2020-10-25 1:15"), - ("2020-10-25 3:30", 96, "15T", "2020-10-25 3:15"), - ("2020-10-25 3:15", 96, "15T", "2020-10-25 3:00"), + ("2020-10-25 1:00", 24, "h", "2020-10-25"), + ("2020-10-25 4:00", 24, "h", "2020-10-25 3:00"), + ("2020-10-25 0:15", 96, "15min", "2020-10-25"), + ("2020-10-25 0:30", 96, "15min", "2020-10-25 0:15"), + ("2020-10-25 1:30", 96, "15min", "2020-10-25 1:15"), + ("2020-10-25 3:30", 96, "15min", "2020-10-25 3:15"), + ("2020-10-25 3:15", 96, "15min", "2020-10-25 3:00"), ("2020-10-02", 31, "D", "2020-10-01"), # Natural-boundary timestamps; with DST-start and DST-end. ("2020-04-01", 12, "MS", "2020-03-01"), @@ -38,26 +38,26 @@ ("2020-08-01", 4, "QS-FEB", "2020-05-01"), # Unnatural-boundary. # Unnatural-boundary timestamps; without DST-transition. - ("2020-02-01 01:30", 24, "H", "2020-02-01 00:30"), - ("2020-02-01 01:32", 96, "15T", "2020-02-01 01:17"), + ("2020-02-01 01:30", 24, "h", "2020-02-01 00:30"), + ("2020-02-01 01:32", 96, "15min", "2020-02-01 01:17"), ("2020-02-02 06:00", 5, "D", "2020-02-01 06:00"), # Unnatural-boundary timestamps; with DST-start. - ("2020-03-29 1:30", 24, "H", "2020-03-29 0:30"), - ("2020-03-29 4:30", 24, "H", "2020-03-29 3:30"), - ("2020-03-29 0:17", 96, "15T", "2020-03-29 0:02"), - ("2020-03-29 0:32", 96, "15T", "2020-03-29 0:17"), - ("2020-03-29 1:32", 96, "15T", "2020-03-29 1:17"), - ("2020-03-29 3:32", 96, "15T", "2020-03-29 3:17"), - ("2020-03-29 3:17", 96, "15T", "2020-03-29 3:02"), + ("2020-03-29 1:30", 24, "h", "2020-03-29 0:30"), + ("2020-03-29 4:30", 24, "h", "2020-03-29 3:30"), + ("2020-03-29 0:17", 96, "15min", "2020-03-29 0:02"), + ("2020-03-29 0:32", 96, "15min", "2020-03-29 0:17"), + ("2020-03-29 1:32", 96, "15min", "2020-03-29 1:17"), + ("2020-03-29 3:32", 96, "15min", "2020-03-29 3:17"), + ("2020-03-29 3:17", 96, "15min", "2020-03-29 3:02"), ("2020-03-02 06:00", 31, "D", "2020-03-01 06:00"), # Natural-boundary timestamps; with DST-end. - ("2020-10-25 1:30", 24, "H", "2020-10-25 0:30"), - ("2020-10-25 4:30", 24, "H", "2020-10-25 3:30"), - ("2020-10-25 0:17", 96, "15T", "2020-10-25 0:02"), - ("2020-10-25 0:32", 96, "15T", "2020-10-25 0:17"), - ("2020-10-25 1:32", 96, "15T", "2020-10-25 1:17"), - ("2020-10-25 3:32", 96, "15T", "2020-10-25 3:17"), - ("2020-10-25 3:17", 96, "15T", "2020-10-25 3:02"), + ("2020-10-25 1:30", 24, "h", "2020-10-25 0:30"), + ("2020-10-25 4:30", 24, "h", "2020-10-25 3:30"), + ("2020-10-25 0:17", 96, "15min", "2020-10-25 0:02"), + ("2020-10-25 0:32", 96, "15min", "2020-10-25 0:17"), + ("2020-10-25 1:32", 96, "15min", "2020-10-25 1:17"), + ("2020-10-25 3:32", 96, "15min", "2020-10-25 3:17"), + ("2020-10-25 3:17", 96, "15min", "2020-10-25 3:02"), ("2020-10-02 06:00", 31, "D", "2020-10-01 06:00"), # Natural-boundary timestamps; with DST-start and DST-end. ("2020-04-01 06:00", 12, "MS", "2020-03-01 06:00"), @@ -68,31 +68,31 @@ TESTCASES_DST_AWARE = [ # start, periods, freq, expected_start" # Index with DST-start. - ("2020-03-29 1:00", 24, "H", "2020-03-29"), - ("2020-03-29 3:00", 24, "H", "2020-03-29 1:00"), - ("2020-03-29 4:00", 24, "H", "2020-03-29 3:00"), - ("2020-03-29 0:15", 96, "15T", "2020-03-29"), - ("2020-03-29 1:30", 96, "15T", "2020-03-29 1:15"), - ("2020-03-29 1:45", 96, "15T", "2020-03-29 1:30"), - ("2020-03-29 3:00", 96, "15T", "2020-03-29 1:45"), - ("2020-03-29 3:15", 96, "15T", "2020-03-29 3:00"), + ("2020-03-29 1:00", 24, "h", "2020-03-29"), + ("2020-03-29 3:00", 24, "h", "2020-03-29 1:00"), + ("2020-03-29 4:00", 24, "h", "2020-03-29 3:00"), + ("2020-03-29 0:15", 96, "15min", "2020-03-29"), + ("2020-03-29 1:30", 96, "15min", "2020-03-29 1:15"), + ("2020-03-29 1:45", 96, "15min", "2020-03-29 1:30"), + ("2020-03-29 3:00", 96, "15min", "2020-03-29 1:45"), + ("2020-03-29 3:15", 96, "15min", "2020-03-29 3:00"), # Index with DST-end. - ("2020-10-25 1:00", 24, "H", "2020-10-25"), - ("2020-10-25 2:00+0200", 24, "H", "2020-10-25 1:00"), - ("2020-10-25 2:00+0100", 24, "H", "2020-10-25 2:00+0200"), - ("2020-10-25 3:00", 24, "H", "2020-10-25 2:00+0100"), - ("2020-10-25 4:00", 24, "H", "2020-10-25 3:00"), - ("2020-10-25 0:15", 96, "15T", "2020-10-25"), - ("2020-10-25 0:30", 96, "15T", "2020-10-25 0:15"), - ("2020-10-25 1:30", 96, "15T", "2020-10-25 1:15"), - ("2020-10-25 2:15+0200", 96, "15T", "2020-10-25 2:00+0200"), - ("2020-10-25 2:30+0200", 96, "15T", "2020-10-25 2:15+0200"), - ("2020-10-25 2:00+0100", 96, "15T", "2020-10-25 2:45+0200"), - ("2020-10-25 2:15+0100", 96, "15T", "2020-10-25 2:00+0100"), - ("2020-10-25 2:30+0100", 96, "15T", "2020-10-25 2:15+0100"), - ("2020-10-25 1:30", 96, "15T", "2020-10-25 1:15"), - ("2020-10-25 3:30", 96, "15T", "2020-10-25 3:15"), - ("2020-10-25 3:15", 96, "15T", "2020-10-25 3:00"), + ("2020-10-25 1:00", 24, "h", "2020-10-25"), + ("2020-10-25 2:00+0200", 24, "h", "2020-10-25 1:00"), + ("2020-10-25 2:00+0100", 24, "h", "2020-10-25 2:00+0200"), + ("2020-10-25 3:00", 24, "h", "2020-10-25 2:00+0100"), + ("2020-10-25 4:00", 24, "h", "2020-10-25 3:00"), + ("2020-10-25 0:15", 96, "15min", "2020-10-25"), + ("2020-10-25 0:30", 96, "15min", "2020-10-25 0:15"), + ("2020-10-25 1:30", 96, "15min", "2020-10-25 1:15"), + ("2020-10-25 2:15+0200", 96, "15min", "2020-10-25 2:00+0200"), + ("2020-10-25 2:30+0200", 96, "15min", "2020-10-25 2:15+0200"), + ("2020-10-25 2:00+0100", 96, "15min", "2020-10-25 2:45+0200"), + ("2020-10-25 2:15+0100", 96, "15min", "2020-10-25 2:00+0100"), + ("2020-10-25 2:30+0100", 96, "15min", "2020-10-25 2:15+0100"), + ("2020-10-25 1:30", 96, "15min", "2020-10-25 1:15"), + ("2020-10-25 3:30", 96, "15min", "2020-10-25 3:15"), + ("2020-10-25 3:15", 96, "15min", "2020-10-25 3:00"), ] TESTCASES_DST_NAIVE = [ # date, times, expected_times diff --git a/tests/tools/test_standardize.py b/tests/tools/test_standardize.py index 93ca893..d6751b1 100644 --- a/tests/tools/test_standardize.py +++ b/tests/tools/test_standardize.py @@ -5,16 +5,16 @@ from portfolyo import dev, tools TEST_FREQUENCIES = [ - "AS", - "AS-FEB", - "AS-APR", + "YS", + "YS-FEB", + "YS-APR", "QS", "QS-FEB", "QS-APR", "MS", "D", - "H", - "15T", + "h", + "15min", ] @@ -27,7 +27,7 @@ @pytest.mark.parametrize("in_aware", [True, False]) @pytest.mark.parametrize("in_tz", ["Europe/Berlin", "Asia/Kolkata"]) @pytest.mark.parametrize("force", ["agnostic", "aware"]) -@pytest.mark.parametrize("freq", ["15T", "D"]) +@pytest.mark.parametrize("freq", ["15min", "D"]) def test_standardize_DST( in_vals_num_specialconditions: int, start: str, @@ -115,9 +115,9 @@ def test_standardize_convert(freq, in_tz, floating, series_or_df, bound, out_tz) # Get index. i = dev.get_index(freq, in_tz, _seed=1) - if bound == "right" and freq == "15T": # Ensure it's a correct full-hour index + if bound == "right" and freq == "15min": # Ensure it's a correct full-hour index i += pd.Timedelta(minutes=15) - if freq == "15T" and in_tz == "Asia/Kolkata" and not floating and out_tz: + if freq == "15min" and in_tz == "Asia/Kolkata" and not floating and out_tz: i += pd.Timedelta(minutes=30) # If no timezone specified and below-daily values, the created index will have too few/many datapoints. @@ -134,7 +134,7 @@ def test_standardize_convert(freq, in_tz, floating, series_or_df, bound, out_tz) if ( in_tz == "Asia/Kolkata" and out_tz == "Europe/Berlin" - and tools.freq.shortest(freq, "H") == "H" + and tools.freq.shortest(freq, "h") == "h" and not floating ): # Kolkata and Berlin timezone only share 15T-boundaries. Therefore, any other diff --git a/tests/tools/test_startofday.py b/tests/tools/test_startofday.py index f8460ad..ea784a7 100644 --- a/tests/tools/test_startofday.py +++ b/tests/tools/test_startofday.py @@ -19,7 +19,7 @@ STARTDATE_AND_FREQ = [ - ("2020-01-01", "H"), + ("2020-01-01", "h"), ("2020-01-01", "D"), ("2020-01-01", "MS"), ("2020-03-28", "D"), @@ -59,7 +59,7 @@ def create_start_of_day(hour, minute, returntype): ), ( pd.date_range( - "2020-01-01", "2021-01-01", freq="H", inclusive="left", tz=None + "2020-01-01", "2021-01-01", freq="h", inclusive="left", tz=None ), 0, 0, @@ -91,7 +91,7 @@ def create_start_of_day(hour, minute, returntype): pd.date_range( "2020-01-01", "2021-01-01", - freq="H", + freq="h", inclusive="left", tz="Europe/Berlin", ), @@ -126,7 +126,7 @@ def create_start_of_day(hour, minute, returntype): pd.date_range( "2020-01-01 06:00", "2021-01-01 06:00", - freq="H", + freq="h", inclusive="left", tz=None, ), @@ -160,7 +160,7 @@ def create_start_of_day(hour, minute, returntype): pd.date_range( "2020-01-01 06:30", "2021-01-01 06:30", - freq="H", + freq="h", inclusive="left", tz="Europe/Berlin", ), @@ -200,7 +200,7 @@ def test_get_startofday(i: pd.DatetimeIndex, hour: int, minute: int, returntype: ), ( pd.date_range( - "2020-01-01", "2021-01-01", freq="H", inclusive="left", tz=None + "2020-01-01", "2021-01-01", freq="h", inclusive="left", tz=None ), 0, 0, @@ -235,7 +235,7 @@ def test_get_startofday(i: pd.DatetimeIndex, hour: int, minute: int, returntype: pd.date_range( "2020-01-01", "2021-01-01", - freq="H", + freq="h", inclusive="left", tz="Europe/Berlin", ), @@ -275,14 +275,14 @@ def test_get_startofday(i: pd.DatetimeIndex, hour: int, minute: int, returntype: ), ( pd.date_range( - "2020-01-01", "2021-01-01", freq="H", inclusive="left", tz=None + "2020-01-01", "2021-01-01", freq="h", inclusive="left", tz=None ), 6, 0, pd.date_range( "2020-01-01 06:00", "2020-12-31 06:00", - freq="H", + freq="h", inclusive="left", tz=None, ), @@ -328,7 +328,7 @@ def test_get_startofday(i: pd.DatetimeIndex, hour: int, minute: int, returntype: pd.date_range( "2020-01-01", "2021-01-01", - freq="H", + freq="h", inclusive="left", tz="Europe/Berlin", ), @@ -337,7 +337,7 @@ def test_get_startofday(i: pd.DatetimeIndex, hour: int, minute: int, returntype: pd.date_range( "2020-01-01 06:00", "2020-12-31 06:00", - freq="H", + freq="h", inclusive="left", tz="Europe/Berlin", ), @@ -362,7 +362,7 @@ def test_get_startofday(i: pd.DatetimeIndex, hour: int, minute: int, returntype: ), ( pd.date_range( - "2020-01-01", "2021-01-01", freq="H", inclusive="left", tz=None + "2020-01-01", "2021-01-01", freq="h", inclusive="left", tz=None ), 6, 3, @@ -397,7 +397,7 @@ def test_get_startofday(i: pd.DatetimeIndex, hour: int, minute: int, returntype: pd.date_range( "2020-01-01", "2021-01-01", - freq="H", + freq="h", inclusive="left", tz="Europe/Berlin", ), diff --git a/tests/tools/test_trim.py b/tests/tools/test_trim.py index 1a5a35f..f01183d 100644 --- a/tests/tools/test_trim.py +++ b/tests/tools/test_trim.py @@ -5,142 +5,184 @@ TESTCASES_ALMOSTFULL = [ # start, end, freq, trimfreq, tr_start, tr_end # Trimming below-daily index to below-daily freq. - ("2020-01-01 00", "2022-01-01 15", "15T", "15T", "2020-01-01 00", "2022-01-01 15"), - ("2020-01-01 00", "2022-01-01 15", "15T", "H", "2020-01-01 00", "2022-01-01 15"), - ("2020-01-01 00", "2022-01-01 15", "H", "15T", "2020-01-01 00", "2022-01-01 15"), - ("2020-01-01 00", "2022-01-01 15", "H", "H", "2020-01-01 00", "2022-01-01 15"), - ("2020-01-01 06", "2022-01-01 15", "15T", "15T", "2020-01-01 06", "2022-01-01 15"), - ("2020-01-01 06", "2022-01-01 15", "15T", "H", "2020-01-01 06", "2022-01-01 15"), - ("2020-01-01 06", "2022-01-01 15", "H", "15T", "2020-01-01 06", "2022-01-01 15"), - ("2020-01-01 06", "2022-01-01 15", "H", "H", "2020-01-01 06", "2022-01-01 15"), - ("2020-01-01 06", "2022-01-01 04", "15T", "15T", "2020-01-01 06", "2022-01-01 04"), - ("2020-01-01 06", "2022-01-01 04", "15T", "H", "2020-01-01 06", "2022-01-01 04"), - ("2020-01-01 06", "2022-01-01 04", "H", "15T", "2020-01-01 06", "2022-01-01 04"), - ("2020-01-01 06", "2022-01-01 04", "H", "H", "2020-01-01 06", "2022-01-01 04"), + ( + "2020-01-01 00", + "2022-01-01 15", + "15min", + "15min", + "2020-01-01 00", + "2022-01-01 15", + ), + ("2020-01-01 00", "2022-01-01 15", "15min", "h", "2020-01-01 00", "2022-01-01 15"), + ("2020-01-01 00", "2022-01-01 15", "h", "15min", "2020-01-01 00", "2022-01-01 15"), + ("2020-01-01 00", "2022-01-01 15", "h", "h", "2020-01-01 00", "2022-01-01 15"), + ( + "2020-01-01 06", + "2022-01-01 15", + "15min", + "15min", + "2020-01-01 06", + "2022-01-01 15", + ), + ("2020-01-01 06", "2022-01-01 15", "15min", "h", "2020-01-01 06", "2022-01-01 15"), + ("2020-01-01 06", "2022-01-01 15", "h", "15min", "2020-01-01 06", "2022-01-01 15"), + ("2020-01-01 06", "2022-01-01 15", "h", "h", "2020-01-01 06", "2022-01-01 15"), + ( + "2020-01-01 06", + "2022-01-01 04", + "15min", + "15min", + "2020-01-01 06", + "2022-01-01 04", + ), + ("2020-01-01 06", "2022-01-01 04", "15min", "h", "2020-01-01 06", "2022-01-01 04"), + ("2020-01-01 06", "2022-01-01 04", "h", "15min", "2020-01-01 06", "2022-01-01 04"), + ("2020-01-01 06", "2022-01-01 04", "h", "h", "2020-01-01 06", "2022-01-01 04"), # Trimming below-daily index to daily-or-longer freq. - ("2020-01-01 00", "2022-01-01 15", "15T", "D", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 15", "15T", "MS", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 15", "15T", "QS", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 15", "15T", "AS", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 15", "H", "D", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 15", "H", "MS", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 15", "H", "QS", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 15", "H", "AS", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 06", "2022-01-01 15", "15T", "D", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 15", "15T", "MS", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 15", "15T", "QS", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 15", "15T", "AS", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 15", "H", "D", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 15", "H", "MS", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 15", "H", "QS", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 15", "H", "AS", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 04", "15T", "D", "2020-01-01 06", "2021-12-31 06"), - ("2020-01-01 06", "2022-01-01 04", "15T", "MS", "2020-01-01 06", "2021-12-01 06"), - ("2020-01-01 06", "2022-01-01 04", "15T", "QS", "2020-01-01 06", "2021-10-01 06"), - ("2020-01-01 06", "2022-01-01 04", "15T", "AS", "2020-01-01 06", "2021-01-01 06"), - ("2020-01-01 06", "2022-01-01 04", "H", "D", "2020-01-01 06", "2021-12-31 06"), - ("2020-01-01 06", "2022-01-01 04", "H", "MS", "2020-01-01 06", "2021-12-01 06"), - ("2020-01-01 06", "2022-01-01 04", "H", "QS", "2020-01-01 06", "2021-10-01 06"), - ("2020-01-01 06", "2022-01-01 04", "H", "AS", "2020-01-01 06", "2021-01-01 06"), + ("2020-01-01 00", "2022-01-01 15", "15min", "D", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 15", "15min", "MS", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 15", "15min", "QS", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 15", "15min", "YS", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 15", "h", "D", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 15", "h", "MS", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 15", "h", "QS", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 15", "h", "YS", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 06", "2022-01-01 15", "15min", "D", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 15", "15min", "MS", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 15", "15min", "QS", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 15", "15min", "YS", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 15", "h", "D", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 15", "h", "MS", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 15", "h", "QS", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 15", "h", "YS", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 04", "15min", "D", "2020-01-01 06", "2021-12-31 06"), + ("2020-01-01 06", "2022-01-01 04", "15min", "MS", "2020-01-01 06", "2021-12-01 06"), + ("2020-01-01 06", "2022-01-01 04", "15min", "QS", "2020-01-01 06", "2021-10-01 06"), + ("2020-01-01 06", "2022-01-01 04", "15min", "YS", "2020-01-01 06", "2021-01-01 06"), + ("2020-01-01 06", "2022-01-01 04", "h", "D", "2020-01-01 06", "2021-12-31 06"), + ("2020-01-01 06", "2022-01-01 04", "h", "MS", "2020-01-01 06", "2021-12-01 06"), + ("2020-01-01 06", "2022-01-01 04", "h", "QS", "2020-01-01 06", "2021-10-01 06"), + ("2020-01-01 06", "2022-01-01 04", "h", "YS", "2020-01-01 06", "2021-01-01 06"), ] TESTCASES_MIDYEAR = [ # start, end, freq, trimfreq, tr_start, tr_end # Trimming below-daily index to below-daily freq. - ("2020-04-21 00", "2022-12-15 15", "15T", "15T", "2020-04-21 00", "2022-12-15 15"), - ("2020-04-21 00", "2022-12-15 15", "15T", "H", "2020-04-21 00", "2022-12-15 15"), - ("2020-04-21 00", "2022-12-15 15", "H", "15T", "2020-04-21 00", "2022-12-15 15"), - ("2020-04-21 00", "2022-12-15 15", "H", "H", "2020-04-21 00", "2022-12-15 15"), - ("2020-04-21 06", "2022-12-15 15", "15T", "15T", "2020-04-21 06", "2022-12-15 15"), - ("2020-04-21 06", "2022-12-15 15", "15T", "H", "2020-04-21 06", "2022-12-15 15"), - ("2020-04-21 06", "2022-12-15 15", "H", "15T", "2020-04-21 06", "2022-12-15 15"), - ("2020-04-21 06", "2022-12-15 15", "H", "H", "2020-04-21 06", "2022-12-15 15"), - ("2020-04-21 06", "2022-12-15 04", "15T", "15T", "2020-04-21 06", "2022-12-15 04"), - ("2020-04-21 06", "2022-12-15 04", "15T", "H", "2020-04-21 06", "2022-12-15 04"), - ("2020-04-21 06", "2022-12-15 04", "H", "15T", "2020-04-21 06", "2022-12-15 04"), - ("2020-04-21 06", "2022-12-15 04", "H", "H", "2020-04-21 06", "2022-12-15 04"), + ( + "2020-04-21 00", + "2022-12-15 15", + "15min", + "15min", + "2020-04-21 00", + "2022-12-15 15", + ), + ("2020-04-21 00", "2022-12-15 15", "15min", "h", "2020-04-21 00", "2022-12-15 15"), + ("2020-04-21 00", "2022-12-15 15", "h", "15min", "2020-04-21 00", "2022-12-15 15"), + ("2020-04-21 00", "2022-12-15 15", "h", "h", "2020-04-21 00", "2022-12-15 15"), + ( + "2020-04-21 06", + "2022-12-15 15", + "15min", + "15min", + "2020-04-21 06", + "2022-12-15 15", + ), + ("2020-04-21 06", "2022-12-15 15", "15min", "h", "2020-04-21 06", "2022-12-15 15"), + ("2020-04-21 06", "2022-12-15 15", "h", "15min", "2020-04-21 06", "2022-12-15 15"), + ("2020-04-21 06", "2022-12-15 15", "h", "h", "2020-04-21 06", "2022-12-15 15"), + ( + "2020-04-21 06", + "2022-12-15 04", + "15min", + "15min", + "2020-04-21 06", + "2022-12-15 04", + ), + ("2020-04-21 06", "2022-12-15 04", "15min", "h", "2020-04-21 06", "2022-12-15 04"), + ("2020-04-21 06", "2022-12-15 04", "h", "15min", "2020-04-21 06", "2022-12-15 04"), + ("2020-04-21 06", "2022-12-15 04", "h", "h", "2020-04-21 06", "2022-12-15 04"), # Trimming below-daily index to daily-or-longer freq. - ("2020-04-21 00", "2022-12-15 15", "15T", "D", "2020-04-21 00", "2022-12-15 00"), - ("2020-04-21 00", "2022-12-15 15", "15T", "MS", "2020-05-01 00", "2022-12-01 00"), - ("2020-04-21 00", "2022-12-15 15", "15T", "QS", "2020-07-01 00", "2022-10-01 00"), - ("2020-04-21 00", "2022-12-15 15", "15T", "AS", "2021-01-01 00", "2022-01-01 00"), - ("2020-04-21 00", "2022-12-15 15", "H", "D", "2020-04-21 00", "2022-12-15 00"), - ("2020-04-21 00", "2022-12-15 15", "H", "MS", "2020-05-01 00", "2022-12-01 00"), - ("2020-04-21 00", "2022-12-15 15", "H", "QS", "2020-07-01 00", "2022-10-01 00"), - ("2020-04-21 00", "2022-12-15 15", "H", "AS", "2021-01-01 00", "2022-01-01 00"), - ("2020-04-21 06", "2022-12-15 15", "15T", "D", "2020-04-21 06", "2022-12-15 06"), - ("2020-04-21 06", "2022-12-15 15", "15T", "MS", "2020-05-01 06", "2022-12-01 06"), - ("2020-04-21 06", "2022-12-15 15", "15T", "QS", "2020-07-01 06", "2022-10-01 06"), - ("2020-04-21 06", "2022-12-15 15", "15T", "AS", "2021-01-01 06", "2022-01-01 06"), - ("2020-04-21 06", "2022-12-15 15", "H", "D", "2020-04-21 06", "2022-12-15 06"), - ("2020-04-21 06", "2022-12-15 15", "H", "MS", "2020-05-01 06", "2022-12-01 06"), - ("2020-04-21 06", "2022-12-15 15", "H", "QS", "2020-07-01 06", "2022-10-01 06"), - ("2020-04-21 06", "2022-12-15 15", "H", "AS", "2021-01-01 06", "2022-01-01 06"), - ("2020-04-21 06", "2022-12-15 04", "15T", "D", "2020-04-21 06", "2022-12-14 06"), - ("2020-04-21 06", "2022-12-15 04", "15T", "MS", "2020-05-01 06", "2022-12-01 06"), - ("2020-04-21 06", "2022-12-15 04", "15T", "QS", "2020-07-01 06", "2022-10-01 06"), - ("2020-04-21 06", "2022-12-15 04", "15T", "AS", "2021-01-01 06", "2022-01-01 06"), - ("2020-04-21 06", "2022-12-15 04", "H", "D", "2020-04-21 06", "2022-12-14 06"), - ("2020-04-21 06", "2022-12-15 04", "H", "MS", "2020-05-01 06", "2022-12-01 06"), - ("2020-04-21 06", "2022-12-15 04", "H", "QS", "2020-07-01 06", "2022-10-01 06"), - ("2020-04-21 06", "2022-12-15 04", "H", "AS", "2021-01-01 06", "2022-01-01 06"), + ("2020-04-21 00", "2022-12-15 15", "15min", "D", "2020-04-21 00", "2022-12-15 00"), + ("2020-04-21 00", "2022-12-15 15", "15min", "MS", "2020-05-01 00", "2022-12-01 00"), + ("2020-04-21 00", "2022-12-15 15", "15min", "QS", "2020-07-01 00", "2022-10-01 00"), + ("2020-04-21 00", "2022-12-15 15", "15min", "YS", "2021-01-01 00", "2022-01-01 00"), + ("2020-04-21 00", "2022-12-15 15", "h", "D", "2020-04-21 00", "2022-12-15 00"), + ("2020-04-21 00", "2022-12-15 15", "h", "MS", "2020-05-01 00", "2022-12-01 00"), + ("2020-04-21 00", "2022-12-15 15", "h", "QS", "2020-07-01 00", "2022-10-01 00"), + ("2020-04-21 00", "2022-12-15 15", "h", "YS", "2021-01-01 00", "2022-01-01 00"), + ("2020-04-21 06", "2022-12-15 15", "15min", "D", "2020-04-21 06", "2022-12-15 06"), + ("2020-04-21 06", "2022-12-15 15", "15min", "MS", "2020-05-01 06", "2022-12-01 06"), + ("2020-04-21 06", "2022-12-15 15", "15min", "QS", "2020-07-01 06", "2022-10-01 06"), + ("2020-04-21 06", "2022-12-15 15", "15min", "YS", "2021-01-01 06", "2022-01-01 06"), + ("2020-04-21 06", "2022-12-15 15", "h", "D", "2020-04-21 06", "2022-12-15 06"), + ("2020-04-21 06", "2022-12-15 15", "h", "MS", "2020-05-01 06", "2022-12-01 06"), + ("2020-04-21 06", "2022-12-15 15", "h", "QS", "2020-07-01 06", "2022-10-01 06"), + ("2020-04-21 06", "2022-12-15 15", "h", "YS", "2021-01-01 06", "2022-01-01 06"), + ("2020-04-21 06", "2022-12-15 04", "15min", "D", "2020-04-21 06", "2022-12-14 06"), + ("2020-04-21 06", "2022-12-15 04", "15min", "MS", "2020-05-01 06", "2022-12-01 06"), + ("2020-04-21 06", "2022-12-15 04", "15min", "QS", "2020-07-01 06", "2022-10-01 06"), + ("2020-04-21 06", "2022-12-15 04", "15min", "YS", "2021-01-01 06", "2022-01-01 06"), + ("2020-04-21 06", "2022-12-15 04", "h", "D", "2020-04-21 06", "2022-12-14 06"), + ("2020-04-21 06", "2022-12-15 04", "h", "MS", "2020-05-01 06", "2022-12-01 06"), + ("2020-04-21 06", "2022-12-15 04", "h", "QS", "2020-07-01 06", "2022-10-01 06"), + ("2020-04-21 06", "2022-12-15 04", "h", "YS", "2021-01-01 06", "2022-01-01 06"), # Trimming daily-or-longer index to shorter-than-daily freq. - ("2020-04-21 00", "2022-12-15 00", "D", "15T", "2020-04-21 00", "2022-12-15 00"), - ("2020-04-21 00", "2022-12-15 00", "D", "H", "2020-04-21 00", "2022-12-15 00"), - ("2020-04-01 00", "2022-12-01 00", "MS", "15T", "2020-04-01 00", "2022-12-01 00"), - ("2020-04-01 00", "2022-12-01 00", "MS", "H", "2020-04-01 00", "2022-12-01 00"), - ("2020-04-01 00", "2022-10-01 00", "QS", "15T", "2020-04-01 00", "2022-10-01 00"), - ("2020-04-01 00", "2022-10-01 00", "QS", "H", "2020-04-01 00", "2022-10-01 00"), - ("2020-01-01 00", "2022-01-01 00", "AS", "15T", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 00", "AS", "H", "2020-01-01 00", "2022-01-01 00"), - ("2020-04-21 06", "2022-12-15 06", "D", "15T", "2020-04-21 06", "2022-12-15 06"), - ("2020-04-21 06", "2022-12-15 06", "D", "H", "2020-04-21 06", "2022-12-15 06"), - ("2020-04-01 06", "2022-12-01 06", "MS", "15T", "2020-04-01 06", "2022-12-01 06"), - ("2020-04-01 06", "2022-12-01 06", "MS", "H", "2020-04-01 06", "2022-12-01 06"), - ("2020-04-01 06", "2022-10-01 06", "QS", "15T", "2020-04-01 06", "2022-10-01 06"), - ("2020-04-01 06", "2022-10-01 06", "QS", "H", "2020-04-01 06", "2022-10-01 06"), - ("2020-01-01 06", "2022-01-01 06", "AS", "15T", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 06", "AS", "H", "2020-01-01 06", "2022-01-01 06"), + ("2020-04-21 00", "2022-12-15 00", "D", "15min", "2020-04-21 00", "2022-12-15 00"), + ("2020-04-21 00", "2022-12-15 00", "D", "h", "2020-04-21 00", "2022-12-15 00"), + ("2020-04-01 00", "2022-12-01 00", "MS", "15min", "2020-04-01 00", "2022-12-01 00"), + ("2020-04-01 00", "2022-12-01 00", "MS", "h", "2020-04-01 00", "2022-12-01 00"), + ("2020-04-01 00", "2022-10-01 00", "QS", "15min", "2020-04-01 00", "2022-10-01 00"), + ("2020-04-01 00", "2022-10-01 00", "QS", "h", "2020-04-01 00", "2022-10-01 00"), + ("2020-01-01 00", "2022-01-01 00", "YS", "15min", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 00", "YS", "h", "2020-01-01 00", "2022-01-01 00"), + ("2020-04-21 06", "2022-12-15 06", "D", "15min", "2020-04-21 06", "2022-12-15 06"), + ("2020-04-21 06", "2022-12-15 06", "D", "h", "2020-04-21 06", "2022-12-15 06"), + ("2020-04-01 06", "2022-12-01 06", "MS", "15min", "2020-04-01 06", "2022-12-01 06"), + ("2020-04-01 06", "2022-12-01 06", "MS", "h", "2020-04-01 06", "2022-12-01 06"), + ("2020-04-01 06", "2022-10-01 06", "QS", "15min", "2020-04-01 06", "2022-10-01 06"), + ("2020-04-01 06", "2022-10-01 06", "QS", "h", "2020-04-01 06", "2022-10-01 06"), + ("2020-01-01 06", "2022-01-01 06", "YS", "15min", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 06", "YS", "h", "2020-01-01 06", "2022-01-01 06"), # Trimming daily-or-longer index to daily-or-longer freq. ("2020-04-21 00", "2022-12-15 00", "D", "D", "2020-04-21 00", "2022-12-15 00"), ("2020-04-21 00", "2022-12-15 00", "D", "MS", "2020-05-01 00", "2022-12-01 00"), ("2020-04-21 00", "2022-12-15 00", "D", "QS", "2020-07-01 00", "2022-10-01 00"), - ("2020-04-21 00", "2022-12-15 00", "D", "AS", "2021-01-01 00", "2022-01-01 00"), + ("2020-04-21 00", "2022-12-15 00", "D", "YS", "2021-01-01 00", "2022-01-01 00"), ("2020-04-01 00", "2022-12-01 00", "MS", "D", "2020-04-01 00", "2022-12-01 00"), ("2020-04-01 00", "2022-12-01 00", "MS", "MS", "2020-04-01 00", "2022-12-01 00"), ("2020-04-01 00", "2022-12-01 00", "MS", "QS", "2020-04-01 00", "2022-10-01 00"), - ("2020-04-01 00", "2022-12-01 00", "MS", "AS", "2021-01-01 00", "2022-01-01 00"), + ("2020-04-01 00", "2022-12-01 00", "MS", "YS", "2021-01-01 00", "2022-01-01 00"), ("2020-04-01 00", "2022-10-01 00", "QS", "D", "2020-04-01 00", "2022-10-01 00"), ("2020-04-01 00", "2022-10-01 00", "QS", "MS", "2020-04-01 00", "2022-10-01 00"), ("2020-04-01 00", "2022-10-01 00", "QS", "QS", "2020-04-01 00", "2022-10-01 00"), - ("2020-04-01 00", "2022-10-01 00", "QS", "AS", "2021-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 00", "AS", "D", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 00", "AS", "MS", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 00", "AS", "QS", "2020-01-01 00", "2022-01-01 00"), - ("2020-01-01 00", "2022-01-01 00", "AS", "AS", "2020-01-01 00", "2022-01-01 00"), + ("2020-04-01 00", "2022-10-01 00", "QS", "YS", "2021-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 00", "YS", "D", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 00", "YS", "MS", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 00", "YS", "QS", "2020-01-01 00", "2022-01-01 00"), + ("2020-01-01 00", "2022-01-01 00", "YS", "YS", "2020-01-01 00", "2022-01-01 00"), ("2020-04-21 06", "2022-12-15 06", "D", "D", "2020-04-21 06", "2022-12-15 06"), ("2020-04-21 06", "2022-12-15 06", "D", "MS", "2020-05-01 06", "2022-12-01 06"), ("2020-04-21 06", "2022-12-15 06", "D", "QS", "2020-07-01 06", "2022-10-01 06"), - ("2020-04-21 06", "2022-12-15 06", "D", "AS", "2021-01-01 06", "2022-01-01 06"), + ("2020-04-21 06", "2022-12-15 06", "D", "YS", "2021-01-01 06", "2022-01-01 06"), ("2020-04-01 06", "2022-12-01 06", "MS", "D", "2020-04-01 06", "2022-12-01 06"), ("2020-04-01 06", "2022-12-01 06", "MS", "MS", "2020-04-01 06", "2022-12-01 06"), ("2020-04-01 06", "2022-12-01 06", "MS", "QS", "2020-04-01 06", "2022-10-01 06"), - ("2020-04-01 06", "2022-12-01 06", "MS", "AS", "2021-01-01 06", "2022-01-01 06"), + ("2020-04-01 06", "2022-12-01 06", "MS", "YS", "2021-01-01 06", "2022-01-01 06"), ("2020-04-01 06", "2022-10-01 06", "QS", "D", "2020-04-01 06", "2022-10-01 06"), ("2020-04-01 06", "2022-10-01 06", "QS", "MS", "2020-04-01 06", "2022-10-01 06"), ("2020-04-01 06", "2022-10-01 06", "QS", "QS", "2020-04-01 06", "2022-10-01 06"), - ("2020-04-01 06", "2022-10-01 06", "QS", "AS", "2021-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 06", "AS", "D", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 06", "AS", "MS", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 06", "AS", "QS", "2020-01-01 06", "2022-01-01 06"), - ("2020-01-01 06", "2022-01-01 06", "AS", "AS", "2020-01-01 06", "2022-01-01 06"), + ("2020-04-01 06", "2022-10-01 06", "QS", "YS", "2021-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 06", "YS", "D", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 06", "YS", "MS", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 06", "YS", "QS", "2020-01-01 06", "2022-01-01 06"), + ("2020-01-01 06", "2022-01-01 06", "YS", "YS", "2020-01-01 06", "2022-01-01 06"), ] @pytest.mark.parametrize("indexorframe", ["index", "s", "s_unit", "df", "df_unit"]) @pytest.mark.parametrize("starttime", ["00:00", "06:00"]) @pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) -@pytest.mark.parametrize("freq", ["15T", "H", "D", "MS", "QS", "AS"]) -@pytest.mark.parametrize("trimfreq", ["15T", "H", "D", "MS", "QS", "AS"]) +@pytest.mark.parametrize("freq", ["15min", "h", "D", "MS", "QS", "YS"]) +@pytest.mark.parametrize("trimfreq", ["15min", "h", "D", "MS", "QS", "YS"]) def test_trim_notrimming( indexorframe: str, freq: str, tz: str, trimfreq: str, starttime: str ): @@ -197,7 +239,7 @@ def test_trim_short2short(indexorframe: str, tz: str): # If input and output are below-daily, the offset parameter to the trim function should be ignored. start, tr_start = "2020-01-05 04:15:00", "2020-01-05 05:00" end, tr_end = "2022-12-21 04:15:00", "2022-12-21 04:00" - do_test_general(indexorframe, start, end, "15T", tz, "H", tr_start, tr_end) + do_test_general(indexorframe, start, end, "15min", tz, "h", tr_start, tr_end) @pytest.mark.parametrize("indexorframe", ["index", "s", "s_unit", "df", "df_unit"]) @@ -208,7 +250,7 @@ def test_trim_short2short(indexorframe: str, tz: str): [ ("MS", "2020-02-01", "2022-12-01"), ("QS", "2020-04-01", "2022-10-01"), - ("AS", "2021-01-01", "2022-01-01"), + ("YS", "2021-01-01", "2022-01-01"), ], ) def test_trim_long2long( @@ -234,10 +276,10 @@ def test_trim_long2long( [ ("MS", "2020-02-01", "2022-12-01"), ("QS", "2020-04-01", "2022-10-01"), - ("AS", "2021-01-01", "2022-01-01"), + ("YS", "2021-01-01", "2022-01-01"), ], ) -@pytest.mark.parametrize("trimfreq", ["H", "15T"]) +@pytest.mark.parametrize("trimfreq", ["h", "15min"]) def test_trim_long2short( indexorframe: str, start: str, @@ -263,7 +305,7 @@ def test_trim_long2short( ("D", "2020-01-05", "2022-12-21"), ("MS", "2020-02-01", "2022-12-01"), ("QS", "2020-04-01", "2022-10-01"), - ("AS", "2021-01-01", "2022-01-01"), + ("YS", "2021-01-01", "2022-01-01"), ], ) def test_trim_short2long( @@ -278,7 +320,7 @@ def test_trim_short2long( # Only in case a below-daily index is trimmed to a daily-or-longer frequency is the offset parameter used. start, tr_start = f"2020-01-05 {starttime}", f"{tr_startdate} {starttime}" end, tr_end = "2022-12-21 23:00:00", f"{tr_enddate} {starttime}" - do_test_general(indexorframe, start, end, "H", tz, trimfreq, tr_start, tr_end) + do_test_general(indexorframe, start, end, "h", tz, trimfreq, tr_start, tr_end) def do_test_general( diff --git a/tests/tools/test_tzone.py b/tests/tools/test_tzone.py index 23ca810..214f2e6 100644 --- a/tests/tools/test_tzone.py +++ b/tests/tools/test_tzone.py @@ -27,7 +27,7 @@ class TzType(Enum): def get_df_fromexcel(aggfreq, tzt_in: TzType, tzt_out: TzType) -> pd.DataFrame: path = Path(__file__).parent / "test_tzone_data.xlsx" - if tzt_in is TzType.A_NONFLOAT and aggfreq != "15T": + if tzt_in is TzType.A_NONFLOAT and aggfreq != "15min": return None, None # does not exist as starting point for our tests df = pd.read_excel(path, aggfreq, header=[5, 6]) @@ -64,7 +64,7 @@ def prep_df(idx, data, tz): @pytest.mark.only_on_pr @pytest.mark.parametrize("seriesordf", ["series", "df"]) -@pytest.mark.parametrize("aggfreq", ["15T", "H", "D", "MS"]) +@pytest.mark.parametrize("aggfreq", ["15min", "h", "D", "MS"]) @pytest.mark.parametrize( ("tzt_in", "tzt_out"), [ @@ -85,7 +85,7 @@ def conversion_fn(fr): @pytest.mark.only_on_pr @pytest.mark.parametrize("seriesordf", ["series", "df"]) -@pytest.mark.parametrize("aggfreq", ["15T", "H", "D", "MS"]) +@pytest.mark.parametrize("aggfreq", ["15min", "h", "D", "MS"]) @pytest.mark.parametrize( ("tzt_in", "tzt_out"), [ @@ -105,7 +105,7 @@ def conversion_fn(fr): @pytest.mark.only_on_pr @pytest.mark.parametrize("seriesordf", ["series", "df"]) -@pytest.mark.parametrize("aggfreq", ["15T", "H", "D", "MS"]) +@pytest.mark.parametrize("aggfreq", ["15min", "h", "D", "MS"]) @pytest.mark.parametrize( ("tzt_in", "tzt_out"), [ @@ -128,7 +128,7 @@ def conversion_fn(fr): @pytest.mark.parametrize("seriesordf", ["series", "df"]) -@pytest.mark.parametrize("aggfreq", ["15T", "H", "D", "MS"]) +@pytest.mark.parametrize("aggfreq", ["15min", "h", "D", "MS"]) @pytest.mark.parametrize( ("tzt_in", "tzt_out"), [(TzType.A, TzType.B), (TzType.A_FLOAT, TzType.B)] ) @@ -143,7 +143,7 @@ def conversion_fn(fr): @pytest.mark.parametrize("seriesordf", ["series", "df"]) -@pytest.mark.parametrize("aggfreq", ["15T", "H", "D", "MS"]) +@pytest.mark.parametrize("aggfreq", ["15min", "h", "D", "MS"]) @pytest.mark.parametrize(("tzt_in", "tzt_out"), [(TzType.B, TzType.A)]) def test_conversionBtoA_fromexcel(aggfreq, tzt_in, tzt_out, seriesordf): """Test if frames can be correctly converted from type B to type A.""" diff --git a/tests/tools/test_tzone_data.xlsx b/tests/tools/test_tzone_data.xlsx index d2795fa..d76bf30 100644 Binary files a/tests/tools/test_tzone_data.xlsx and b/tests/tools/test_tzone_data.xlsx differ diff --git a/tests/tools/visualize/test_plot.py b/tests/tools/visualize/test_plot.py index 2c66277..66096ef 100644 --- a/tests/tools/visualize/test_plot.py +++ b/tests/tools/visualize/test_plot.py @@ -20,7 +20,7 @@ def test_pfline_plot( index = pd.date_range("2020-01-01", "2021-01-01", freq=freq, tz=None) pfl = pf.dev.get_pfline(index, nlevels=levels, childcount=childcount, kind=kind) pfl.plot(children=children) - # plt.show() + plt.close() @pytest.mark.parametrize("childcount", [1, 2, 3]) @@ -44,6 +44,7 @@ def test_pfstate_plot( ) pfs = PfState(-1 * offtakevolume, unsourcedprice, sourced) pfs.plot(children=children) + plt.close() @pytest.mark.parametrize("children", [True, False]) @@ -51,6 +52,7 @@ def test_flatpfline_plot(children: bool): """Test if plotting flatpfline with children attribute gives an error.""" pfl = pf.dev.get_flatpfline() pfl.plot(children=children) + plt.close() @pytest.mark.parametrize("freq", ["MS", "D"]) @@ -78,3 +80,4 @@ def test_plot_to_ax(levels: int, childcount: int, children: bool, freq: str): pfl_vol.plot_to_ax(axs[0][1], children=children, kind=Kind.VOLUME) pfl_price.plot_to_ax(axs[1][0], children=children, kind=Kind.PRICE) pfl_rev.plot_to_ax(axs[1][1], children=children, kind=Kind.REVENUE) + plt.close() diff --git a/tests/tools2/test_concat_error_cases.py b/tests/tools2/test_concat_error_cases.py index e250fed..01825e4 100644 --- a/tests/tools2/test_concat_error_cases.py +++ b/tests/tools2/test_concat_error_cases.py @@ -22,7 +22,7 @@ def test_general(): def test_diff_freq(): """Test if concatenating of two flat PfLines with different freq raises error.""" index = pd.date_range("2020", "2024", freq="QS", inclusive="left") - index2 = pd.date_range("2024", "2025", freq="AS", inclusive="left") + index2 = pd.date_range("2024", "2025", freq="YS", inclusive="left") pfl = dev.get_flatpfline(index) pfl2 = dev.get_flatpfline(index2) with pytest.raises(TypeError): @@ -41,9 +41,9 @@ def test_diff_sod(): def test_slice_not_sod(): """Test if concatenating of two flat PfLines with different sod raises error.""" - index = pd.date_range("2020-01-01 00:00", "2020-03-01", freq="H", inclusive="left") + index = pd.date_range("2020-01-01 00:00", "2020-03-01", freq="h", inclusive="left") index2 = pd.date_range( - "2020-02-01 06:00", "2020-04-01 06:00", freq="H", inclusive="left" + "2020-02-01 06:00", "2020-04-01 06:00", freq="h", inclusive="left" ) pfl_a = dev.get_flatpfline(index) pfl_b = dev.get_flatpfline(index2) diff --git a/tests/tools2/test_concat_pfline.py b/tests/tools2/test_concat_pfline.py index 5e30722..3ee8df2 100644 --- a/tests/tools2/test_concat_pfline.py +++ b/tests/tools2/test_concat_pfline.py @@ -12,7 +12,7 @@ "QS", "2022-04-01", ), - (("2020", "2022"), "AS", "2021-01-01"), + (("2020", "2022"), "YS", "2021-01-01"), ( ("2020-05-01", "2023-04-01"), "MS", @@ -27,7 +27,7 @@ "QS", ("2022-04-01", "2023-01-01"), ), - (("2020", "2023"), "AS", ("2021-01-01", "2022-01-01")), + (("2020", "2023"), "YS", ("2021-01-01", "2022-01-01")), ( ("2020-05-01", "2023-04-01"), "MS", diff --git a/tests/tools2/test_concat_pfstate.py b/tests/tools2/test_concat_pfstate.py index 6ca378b..9a3bb7c 100644 --- a/tests/tools2/test_concat_pfstate.py +++ b/tests/tools2/test_concat_pfstate.py @@ -12,7 +12,7 @@ "QS", "2022-04-01", ), - (("2020", "2022"), "AS", "2021-01-01"), + (("2020", "2022"), "YS", "2021-01-01"), ( ("2020-05-01", "2023-04-01"), "MS", @@ -27,7 +27,7 @@ "QS", ("2022-04-01", "2023-01-01"), ), - (("2020", "2023"), "AS", ("2021-01-01", "2022-01-01")), + (("2020", "2023"), "YS", ("2021-01-01", "2022-01-01")), ( ("2020-05-01", "2023-04-01"), "MS", diff --git a/tests/tools2/test_indexable.py b/tests/tools2/test_indexable.py index d757e31..517a895 100644 --- a/tests/tools2/test_indexable.py +++ b/tests/tools2/test_indexable.py @@ -189,7 +189,7 @@ def test_intersect_ignore_all_3obj( idx2 = get_idx("2021-01-01", "06:00", None, "MS", "2024-01-01") s2 = pd.Series(range(len(idx2)), idx2) - idx3 = get_idx("2023-01-01", "00:00", "Asia/Kolkata", "AS", "2025-01-01") + idx3 = get_idx("2023-01-01", "00:00", "Asia/Kolkata", "YS", "2025-01-01") s3 = pd.Series(range(len(idx3)), idx3) first = create_obj(s1, first_obj) if first_obj != "series" else s1