openseries 1.8.2__tar.gz → 1.8.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openseries
3
- Version: 1.8.2
3
+ Version: 1.8.4
4
4
  Summary: Tools for analyzing financial timeseries.
5
5
  License: # BSD 3-Clause License
6
6
 
@@ -50,6 +50,7 @@ Requires-Dist: holidays (>=0.30,<1.0)
50
50
  Requires-Dist: numpy (>=1.23.2,<3.0.0)
51
51
  Requires-Dist: openpyxl (>=3.1.2,<5.0.0)
52
52
  Requires-Dist: pandas (>=2.1.2,<3.0.0)
53
+ Requires-Dist: pandas-market-calendars (>=5.1.0,<7.0.0)
53
54
  Requires-Dist: plotly (>=5.18.0,<7.0.0)
54
55
  Requires-Dist: pyarrow (>=14.0.2,<21.0.0)
55
56
  Requires-Dist: pydantic (>=2.5.2,<3.0.0)
@@ -83,7 +84,6 @@ Description-Content-Type: text/markdown
83
84
  This is a project with tools to analyze financial timeseries of a single
84
85
  asset or a group of assets. It is solely made for daily or less frequent data.
85
86
 
86
-
87
87
  ## Basic Usage
88
88
 
89
89
  To install:
@@ -119,6 +119,7 @@ _,_=series.plot_series()
119
119
  ```
120
120
 
121
121
  ### Sample output using the OpenFrame.all_properties() method:
122
+
122
123
  ```
123
124
  Scilla Global Equity C (simulation+fund) Global Low Volatility index, SEK
124
125
  ValueType.PRICE ValueType.PRICE
@@ -156,7 +157,6 @@ The OpenTimeSeries and OpenFrame classes are both subclasses of
156
157
  the [Pydantic BaseModel](https://docs.pydantic.dev/usage/models/). Please refer to its documentation for information
157
158
  on any attributes or methods inherited from this model.
158
159
 
159
-
160
160
  ### Windows Powershell
161
161
 
162
162
  ```powershell
@@ -202,7 +202,6 @@ make lint
202
202
 
203
203
  ```
204
204
 
205
-
206
205
  ## Table of Contents
207
206
 
208
207
  - [Basic Usage](#basic-usage)
@@ -239,34 +238,35 @@ make lint
239
238
 
240
239
  ### Non-numerical or "helper" properties that apply only to the [OpenTimeSeries](https://github.com/CaptorAB/openseries/blob/master/openseries/series.py) class.
241
240
 
242
- | Property | type | Applies to | Description |
243
- |:----------------|:----------------|:-----------------|:---------------------------------------------------------------------------------------------------------------------------------------------|
244
- | `timeseries_id` | `str` | `OpenTimeSeries` | Placeholder for database identifier for the timeseries. Can be left as empty string. |
245
- | `instrument_id` | `str` | `OpenTimeSeries` | Placeholder for database identifier for the instrument associated with the timeseries. Can be left as empty string. |
246
- | `dates` | `list[str]` | `OpenTimeSeries` | Dates of the timeseries. Not edited by any method to allow reversion to original. |
247
- | `values` | `list[float]` | `OpenTimeSeries` | Values of the timeseries. Not edited by any method to allow reversion to original. |
248
- | `currency` | `str` | `OpenTimeSeries` | Currency of the timeseries. Only used if conversion/hedging methods are added. |
249
- | `domestic` | `str` | `OpenTimeSeries` | Domestic currency of the user / investor. Only used if conversion/hedging methods are added. |
250
- | `local_ccy` | `bool` | `OpenTimeSeries` | Indicates if series should be in its local currency or the domestic currency of the user. Only used if conversion/hedging methods are added. |
251
- | `name` | `str` | `OpenTimeSeries` | An identifier field. |
252
- | `isin` | `str` | `OpenTimeSeries` | ISIN code of the associated instrument. If any. |
253
- | `label` | `str` | `OpenTimeSeries` | Field used in outputs. Derived from name as default. |
254
- | `countries` | `list` or `str` | `OpenTimeSeries` | (List of) country code(s) according to ISO 3166-1 alpha-2 used to generate business days. |
255
- | `valuetype` | `ValueType` | `OpenTimeSeries` | Field identifies the type of values in the series. ValueType is an Enum. |
241
+ | Property | type | Applies to | Description |
242
+ |:----------------|:---------------------|:-----------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------|
243
+ | `timeseries_id` | `str` | `OpenTimeSeries` | Placeholder for database identifier for the timeseries. Can be left as empty string. |
244
+ | `instrument_id` | `str` | `OpenTimeSeries` | Placeholder for database identifier for the instrument associated with the timeseries. Can be left as empty string. |
245
+ | `dates` | `list[str]` | `OpenTimeSeries` | Dates of the timeseries. Not edited by any method to allow reversion to original. |
246
+ | `values` | `list[float]` | `OpenTimeSeries` | Values of the timeseries. Not edited by any method to allow reversion to original. |
247
+ | `currency` | `str` | `OpenTimeSeries` | Currency of the timeseries. Only used if conversion/hedging methods are added. |
248
+ | `domestic` | `str` | `OpenTimeSeries` | Domestic currency of the user / investor. Only used if conversion/hedging methods are added. |
249
+ | `local_ccy` | `bool` | `OpenTimeSeries` | Indicates if series should be in its local currency or the domestic currency of the user. Only used if conversion/hedging methods are added. |
250
+ | `name` | `str` | `OpenTimeSeries` | An identifier field. |
251
+ | `isin` | `str` | `OpenTimeSeries` | ISIN code of the associated instrument. If any. |
252
+ | `label` | `str` | `OpenTimeSeries` | Field used in outputs. Derived from name as default. |
253
+ | `countries` | `list[str]` or `str` | `OpenTimeSeries` | (List of) country code(s) according to ISO 3166-1 alpha-2 used in the [holidays](https://github.com/vacanza/holidays/) package to generate business days. |
254
+ | `markets` | `list[str]` or `str` | `OpenTimeSeries` | (List of) markets code(s) according to market code(s) input for the [pandas-market-calendars](https://pandas-market-calendars.readthedocs.io/en/latest/) package. |
255
+ | `valuetype` | `ValueType` | `OpenTimeSeries` | Field identifies the type of values in the series. ValueType is an Enum. |
256
256
 
257
257
  ### Non-numerical or "helper" properties that apply only to the [OpenFrame](https://github.com/CaptorAB/openseries/blob/master/openseries/frame.py) class.
258
258
 
259
- | Property | type | Applies to | Description |
260
- |:-------------------|:-----------------------|:------------|:-------------------------------------------------------------------------|
261
- | `constituents` | `list[OpenTimeSeries]` | `OpenFrame` | A list of the OpenTimeSeries that make up an OpenFrame. |
262
- | `columns_lvl_zero` | `list` | `OpenFrame` | A list of the level zero column names in the OpenFrame pandas.DataFrame. |
263
- | `columns_lvl_one` | `list` | `OpenFrame` | A list of the level one column names in the OpenFrame pandas.DataFrame. |
264
- | `item_count` | `int` | `OpenFrame` | Number of columns in the OpenFrame pandas.DataFrame. |
265
- | `weights` | `list[float]` | `OpenFrame` | Weights used in the method `make_portfolio`. |
266
- | `first_indices` | `pandas.Series` | `OpenFrame` | First dates of all the series in the OpenFrame. |
267
- | `last_indices` | `pandas.Series` | `OpenFrame` | Last dates of all the series in the OpenFrame. |
268
- | `lengths_of_items` | `pandas.Series` | `OpenFrame` | Number of items in each of the series in the OpenFrame. |
269
- | `span_of_days_all` | `pandas.Series` | `OpenFrame` | Number of days from the first to the last in each of the series. |
259
+ | Property | type | Applies to | Description |
260
+ |:-------------------|:---------------------------------|:------------|:-------------------------------------------------------------------------|
261
+ | `constituents` | `list[OpenTimeSeries]` | `OpenFrame` | A list of the OpenTimeSeries that make up an OpenFrame. |
262
+ | `columns_lvl_zero` | `list[str]` | `OpenFrame` | A list of the level zero column names in the OpenFrame pandas.DataFrame. |
263
+ | `columns_lvl_one` | `list[ValueType]` or `list[str]` | `OpenFrame` | A list of the level one column names in the OpenFrame pandas.DataFrame. |
264
+ | `item_count` | `int` | `OpenFrame` | Number of columns in the OpenFrame pandas.DataFrame. |
265
+ | `weights` | `list[float]` | `OpenFrame` | Weights used in the method `make_portfolio`. |
266
+ | `first_indices` | `pandas.Series[dt.date]` | `OpenFrame` | First dates of all the series in the OpenFrame. |
267
+ | `last_indices` | `pandas.Series[dt.date]` | `OpenFrame` | Last dates of all the series in the OpenFrame. |
268
+ | `lengths_of_items` | `pandas.Series[int]` | `OpenFrame` | Number of items in each of the series in the OpenFrame. |
269
+ | `span_of_days_all` | `pandas.Series[int]` | `OpenFrame` | Number of days from the first to the last in each of the series. |
270
270
 
271
271
  ### Non-numerical or "helper" properties that apply to both the [OpenTimeSeries](https://github.com/CaptorAB/openseries/blob/master/openseries/series.py) and the [OpenFrame](https://github.com/CaptorAB/openseries/blob/master/openseries/frame.py) class.
272
272
 
@@ -290,7 +290,7 @@ make lint
290
290
  | `running_adjustment` | `OpenTimeSeries` | Adjusts the series performance with a `float` factor. |
291
291
  | `ewma_vol_func` | `OpenTimeSeries` | Returns a `pandas.Series` with volatility based on [Exponentially Weighted Moving Average](https://www.investopedia.com/articles/07/ewma.asp). |
292
292
  | `from_1d_rate_to_cumret` | `OpenTimeSeries` | Converts a series of 1-day rates into a cumulative valueseries. |
293
- |
293
+ |
294
294
 
295
295
  ### Methods that apply only to the [OpenFrame](https://github.com/CaptorAB/openseries/blob/master/openseries/frame.py) class.
296
296
 
@@ -328,10 +328,11 @@ make lint
328
328
  | `to_xlsx` | `OpenTimeSeries`, `OpenFrame` | Method to save the data in the .tsdf DataFrame to an Excel file. |
329
329
  | `value_to_ret` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a percentage return series. |
330
330
  | `value_to_diff` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a series of differences. |
331
- | `value_to_log` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a logarithmic return series. |
331
+ | `value_to_log` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a cumulative log return series, useful for plotting growth relative to the starting point. |
332
332
  | `value_ret_calendar_period` | `OpenTimeSeries`, `OpenFrame` | Returns the series simple return for a specific calendar period. |
333
- | `plot_series` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Scatter](https://plotly.com/python/line-and-scatter/) plot of the series in a browser window. |
334
- | `plot_bars` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Bar](https://plotly.com/python/bar-charts/) plot of the series in a browser window. |
333
+ | `plot_series` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Scatter](https://plotly.com/python/line-and-scatter/) plot of the serie(s) in a browser window. |
334
+ | `plot_bars` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Bar](https://plotly.com/python/bar-charts/) plot of the serie(s) in a browser window. |
335
+ | `plot_histogram` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Histogram](https://plotly.com/python/histograms/) plot of the serie(s) in a browser window. |
335
336
  | `to_drawdown_series` | `OpenTimeSeries`, `OpenFrame` | Converts the series into drawdown series. |
336
337
  | `rolling_return` | `OpenTimeSeries`, `OpenFrame` | Returns a pandas.DataFrame with rolling returns. |
337
338
  | `rolling_vol` | `OpenTimeSeries`, `OpenFrame` | Returns a pandas.DataFrame with rolling volatilities. |
@@ -18,7 +18,6 @@
18
18
  This is a project with tools to analyze financial timeseries of a single
19
19
  asset or a group of assets. It is solely made for daily or less frequent data.
20
20
 
21
-
22
21
  ## Basic Usage
23
22
 
24
23
  To install:
@@ -54,6 +53,7 @@ _,_=series.plot_series()
54
53
  ```
55
54
 
56
55
  ### Sample output using the OpenFrame.all_properties() method:
56
+
57
57
  ```
58
58
  Scilla Global Equity C (simulation+fund) Global Low Volatility index, SEK
59
59
  ValueType.PRICE ValueType.PRICE
@@ -91,7 +91,6 @@ The OpenTimeSeries and OpenFrame classes are both subclasses of
91
91
  the [Pydantic BaseModel](https://docs.pydantic.dev/usage/models/). Please refer to its documentation for information
92
92
  on any attributes or methods inherited from this model.
93
93
 
94
-
95
94
  ### Windows Powershell
96
95
 
97
96
  ```powershell
@@ -137,7 +136,6 @@ make lint
137
136
 
138
137
  ```
139
138
 
140
-
141
139
  ## Table of Contents
142
140
 
143
141
  - [Basic Usage](#basic-usage)
@@ -174,34 +172,35 @@ make lint
174
172
 
175
173
  ### Non-numerical or "helper" properties that apply only to the [OpenTimeSeries](https://github.com/CaptorAB/openseries/blob/master/openseries/series.py) class.
176
174
 
177
- | Property | type | Applies to | Description |
178
- |:----------------|:----------------|:-----------------|:---------------------------------------------------------------------------------------------------------------------------------------------|
179
- | `timeseries_id` | `str` | `OpenTimeSeries` | Placeholder for database identifier for the timeseries. Can be left as empty string. |
180
- | `instrument_id` | `str` | `OpenTimeSeries` | Placeholder for database identifier for the instrument associated with the timeseries. Can be left as empty string. |
181
- | `dates` | `list[str]` | `OpenTimeSeries` | Dates of the timeseries. Not edited by any method to allow reversion to original. |
182
- | `values` | `list[float]` | `OpenTimeSeries` | Values of the timeseries. Not edited by any method to allow reversion to original. |
183
- | `currency` | `str` | `OpenTimeSeries` | Currency of the timeseries. Only used if conversion/hedging methods are added. |
184
- | `domestic` | `str` | `OpenTimeSeries` | Domestic currency of the user / investor. Only used if conversion/hedging methods are added. |
185
- | `local_ccy` | `bool` | `OpenTimeSeries` | Indicates if series should be in its local currency or the domestic currency of the user. Only used if conversion/hedging methods are added. |
186
- | `name` | `str` | `OpenTimeSeries` | An identifier field. |
187
- | `isin` | `str` | `OpenTimeSeries` | ISIN code of the associated instrument. If any. |
188
- | `label` | `str` | `OpenTimeSeries` | Field used in outputs. Derived from name as default. |
189
- | `countries` | `list` or `str` | `OpenTimeSeries` | (List of) country code(s) according to ISO 3166-1 alpha-2 used to generate business days. |
190
- | `valuetype` | `ValueType` | `OpenTimeSeries` | Field identifies the type of values in the series. ValueType is an Enum. |
175
+ | Property | type | Applies to | Description |
176
+ |:----------------|:---------------------|:-----------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------|
177
+ | `timeseries_id` | `str` | `OpenTimeSeries` | Placeholder for database identifier for the timeseries. Can be left as empty string. |
178
+ | `instrument_id` | `str` | `OpenTimeSeries` | Placeholder for database identifier for the instrument associated with the timeseries. Can be left as empty string. |
179
+ | `dates` | `list[str]` | `OpenTimeSeries` | Dates of the timeseries. Not edited by any method to allow reversion to original. |
180
+ | `values` | `list[float]` | `OpenTimeSeries` | Values of the timeseries. Not edited by any method to allow reversion to original. |
181
+ | `currency` | `str` | `OpenTimeSeries` | Currency of the timeseries. Only used if conversion/hedging methods are added. |
182
+ | `domestic` | `str` | `OpenTimeSeries` | Domestic currency of the user / investor. Only used if conversion/hedging methods are added. |
183
+ | `local_ccy` | `bool` | `OpenTimeSeries` | Indicates if series should be in its local currency or the domestic currency of the user. Only used if conversion/hedging methods are added. |
184
+ | `name` | `str` | `OpenTimeSeries` | An identifier field. |
185
+ | `isin` | `str` | `OpenTimeSeries` | ISIN code of the associated instrument. If any. |
186
+ | `label` | `str` | `OpenTimeSeries` | Field used in outputs. Derived from name as default. |
187
+ | `countries` | `list[str]` or `str` | `OpenTimeSeries` | (List of) country code(s) according to ISO 3166-1 alpha-2 used in the [holidays](https://github.com/vacanza/holidays/) package to generate business days. |
188
+ | `markets` | `list[str]` or `str` | `OpenTimeSeries` | (List of) markets code(s) according to market code(s) input for the [pandas-market-calendars](https://pandas-market-calendars.readthedocs.io/en/latest/) package. |
189
+ | `valuetype` | `ValueType` | `OpenTimeSeries` | Field identifies the type of values in the series. ValueType is an Enum. |
191
190
 
192
191
  ### Non-numerical or "helper" properties that apply only to the [OpenFrame](https://github.com/CaptorAB/openseries/blob/master/openseries/frame.py) class.
193
192
 
194
- | Property | type | Applies to | Description |
195
- |:-------------------|:-----------------------|:------------|:-------------------------------------------------------------------------|
196
- | `constituents` | `list[OpenTimeSeries]` | `OpenFrame` | A list of the OpenTimeSeries that make up an OpenFrame. |
197
- | `columns_lvl_zero` | `list` | `OpenFrame` | A list of the level zero column names in the OpenFrame pandas.DataFrame. |
198
- | `columns_lvl_one` | `list` | `OpenFrame` | A list of the level one column names in the OpenFrame pandas.DataFrame. |
199
- | `item_count` | `int` | `OpenFrame` | Number of columns in the OpenFrame pandas.DataFrame. |
200
- | `weights` | `list[float]` | `OpenFrame` | Weights used in the method `make_portfolio`. |
201
- | `first_indices` | `pandas.Series` | `OpenFrame` | First dates of all the series in the OpenFrame. |
202
- | `last_indices` | `pandas.Series` | `OpenFrame` | Last dates of all the series in the OpenFrame. |
203
- | `lengths_of_items` | `pandas.Series` | `OpenFrame` | Number of items in each of the series in the OpenFrame. |
204
- | `span_of_days_all` | `pandas.Series` | `OpenFrame` | Number of days from the first to the last in each of the series. |
193
+ | Property | type | Applies to | Description |
194
+ |:-------------------|:---------------------------------|:------------|:-------------------------------------------------------------------------|
195
+ | `constituents` | `list[OpenTimeSeries]` | `OpenFrame` | A list of the OpenTimeSeries that make up an OpenFrame. |
196
+ | `columns_lvl_zero` | `list[str]` | `OpenFrame` | A list of the level zero column names in the OpenFrame pandas.DataFrame. |
197
+ | `columns_lvl_one` | `list[ValueType]` or `list[str]` | `OpenFrame` | A list of the level one column names in the OpenFrame pandas.DataFrame. |
198
+ | `item_count` | `int` | `OpenFrame` | Number of columns in the OpenFrame pandas.DataFrame. |
199
+ | `weights` | `list[float]` | `OpenFrame` | Weights used in the method `make_portfolio`. |
200
+ | `first_indices` | `pandas.Series[dt.date]` | `OpenFrame` | First dates of all the series in the OpenFrame. |
201
+ | `last_indices` | `pandas.Series[dt.date]` | `OpenFrame` | Last dates of all the series in the OpenFrame. |
202
+ | `lengths_of_items` | `pandas.Series[int]` | `OpenFrame` | Number of items in each of the series in the OpenFrame. |
203
+ | `span_of_days_all` | `pandas.Series[int]` | `OpenFrame` | Number of days from the first to the last in each of the series. |
205
204
 
206
205
  ### Non-numerical or "helper" properties that apply to both the [OpenTimeSeries](https://github.com/CaptorAB/openseries/blob/master/openseries/series.py) and the [OpenFrame](https://github.com/CaptorAB/openseries/blob/master/openseries/frame.py) class.
207
206
 
@@ -225,7 +224,7 @@ make lint
225
224
  | `running_adjustment` | `OpenTimeSeries` | Adjusts the series performance with a `float` factor. |
226
225
  | `ewma_vol_func` | `OpenTimeSeries` | Returns a `pandas.Series` with volatility based on [Exponentially Weighted Moving Average](https://www.investopedia.com/articles/07/ewma.asp). |
227
226
  | `from_1d_rate_to_cumret` | `OpenTimeSeries` | Converts a series of 1-day rates into a cumulative valueseries. |
228
- |
227
+ |
229
228
 
230
229
  ### Methods that apply only to the [OpenFrame](https://github.com/CaptorAB/openseries/blob/master/openseries/frame.py) class.
231
230
 
@@ -263,10 +262,11 @@ make lint
263
262
  | `to_xlsx` | `OpenTimeSeries`, `OpenFrame` | Method to save the data in the .tsdf DataFrame to an Excel file. |
264
263
  | `value_to_ret` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a percentage return series. |
265
264
  | `value_to_diff` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a series of differences. |
266
- | `value_to_log` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a logarithmic return series. |
265
+ | `value_to_log` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a cumulative log return series, useful for plotting growth relative to the starting point. |
267
266
  | `value_ret_calendar_period` | `OpenTimeSeries`, `OpenFrame` | Returns the series simple return for a specific calendar period. |
268
- | `plot_series` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Scatter](https://plotly.com/python/line-and-scatter/) plot of the series in a browser window. |
269
- | `plot_bars` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Bar](https://plotly.com/python/bar-charts/) plot of the series in a browser window. |
267
+ | `plot_series` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Scatter](https://plotly.com/python/line-and-scatter/) plot of the serie(s) in a browser window. |
268
+ | `plot_bars` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Bar](https://plotly.com/python/bar-charts/) plot of the serie(s) in a browser window. |
269
+ | `plot_histogram` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Histogram](https://plotly.com/python/histograms/) plot of the serie(s) in a browser window. |
270
270
  | `to_drawdown_series` | `OpenTimeSeries`, `OpenFrame` | Converts the series into drawdown series. |
271
271
  | `rolling_return` | `OpenTimeSeries`, `OpenFrame` | Returns a pandas.DataFrame with rolling returns. |
272
272
  | `rolling_vol` | `OpenTimeSeries`, `OpenFrame` | Returns a pandas.DataFrame with rolling volatilities. |
@@ -33,6 +33,10 @@ if TYPE_CHECKING: # pragma: no cover
33
33
  LiteralLinePlotMode,
34
34
  LiteralNanMethod,
35
35
  LiteralPandasReindexMethod,
36
+ LiteralPlotlyHistogramBarMode,
37
+ LiteralPlotlyHistogramCurveType,
38
+ LiteralPlotlyHistogramHistNorm,
39
+ LiteralPlotlyHistogramPlotType,
36
40
  LiteralPlotlyJSlib,
37
41
  LiteralPlotlyOutput,
38
42
  LiteralQuantileInterp,
@@ -50,10 +54,11 @@ from pandas import (
50
54
  to_datetime,
51
55
  )
52
56
  from pandas.tseries.offsets import CustomBusinessDay
57
+ from plotly.figure_factory import create_distplot # type: ignore[import-untyped]
53
58
  from plotly.graph_objs import Figure # type: ignore[import-untyped]
54
59
  from plotly.io import to_html # type: ignore[import-untyped]
55
60
  from plotly.offline import plot # type: ignore[import-untyped]
56
- from pydantic import BaseModel, ConfigDict, DirectoryPath
61
+ from pydantic import BaseModel, ConfigDict, DirectoryPath, ValidationError
57
62
  from scipy.stats import ( # type: ignore[import-untyped]
58
63
  kurtosis,
59
64
  norm,
@@ -372,13 +377,23 @@ class _CommonModel(BaseModel): # type: ignore[misc]
372
377
 
373
378
  """
374
379
  method: LiteralPandasReindexMethod = "nearest"
375
- countries = "SE"
380
+
381
+ try:
382
+ countries = self.countries
383
+ markets = self.markets
384
+ except AttributeError:
385
+ countries = self.constituents[0].countries
386
+ markets = self.constituents[0].markets
387
+
376
388
  wmdf = self.tsdf.copy()
389
+
377
390
  dates = _do_resample_to_business_period_ends(
378
391
  data=wmdf,
379
392
  freq="BME",
380
393
  countries=countries,
394
+ markets=markets,
381
395
  )
396
+
382
397
  wmdf = wmdf.reindex(index=[deyt.date() for deyt in dates], method=method)
383
398
  wmdf.index = DatetimeIndex(wmdf.index)
384
399
  result = wmdf.ffill().pct_change().min()
@@ -534,14 +549,20 @@ class _CommonModel(BaseModel): # type: ignore[misc]
534
549
 
535
550
  def align_index_to_local_cdays(
536
551
  self: Self,
537
- countries: CountriesType = "SE",
552
+ countries: CountriesType | None = None,
553
+ markets: list[str] | str | None = None,
554
+ custom_holidays: list[str] | str | None = None,
538
555
  ) -> Self:
539
556
  """Align the index of .tsdf with local calendar business days.
540
557
 
541
558
  Parameters
542
559
  ----------
543
- countries: CountriesType, default: "SE"
560
+ countries: CountriesType, optional
544
561
  (List of) country code(s) according to ISO 3166-1 alpha-2
562
+ markets: list[str] | str, optional
563
+ (List of) markets code(s) according to pandas-market-calendars
564
+ custom_holidays: list[str] | str, optional
565
+ Argument where missing holidays can be added
545
566
 
546
567
  Returns:
547
568
  -------
@@ -551,10 +572,37 @@ class _CommonModel(BaseModel): # type: ignore[misc]
551
572
  """
552
573
  startyear = cast("int", to_datetime(self.tsdf.index[0]).year)
553
574
  endyear = cast("int", to_datetime(self.tsdf.index[-1]).year)
575
+
576
+ if countries:
577
+ try:
578
+ self.countries = countries
579
+ except ValidationError:
580
+ for serie in self.constituents:
581
+ serie.countries = countries
582
+ else:
583
+ try:
584
+ countries = self.countries
585
+ except AttributeError:
586
+ countries = self.constituents[0].countries
587
+
588
+ if markets:
589
+ try:
590
+ self.markets = markets
591
+ except ValidationError:
592
+ for serie in self.constituents:
593
+ serie.markets = markets
594
+ else:
595
+ try:
596
+ markets = self.markets
597
+ except AttributeError:
598
+ markets = self.constituents[0].markets
599
+
554
600
  calendar = holiday_calendar(
555
601
  startyear=startyear,
556
602
  endyear=endyear,
557
603
  countries=countries,
604
+ markets=markets,
605
+ custom_holidays=custom_holidays,
558
606
  )
559
607
 
560
608
  d_range = [
@@ -1005,6 +1053,174 @@ class _CommonModel(BaseModel): # type: ignore[misc]
1005
1053
 
1006
1054
  return figure, string_output
1007
1055
 
1056
+ def plot_histogram(
1057
+ self: Self,
1058
+ plot_type: LiteralPlotlyHistogramPlotType = "bars",
1059
+ histnorm: LiteralPlotlyHistogramHistNorm = "percent",
1060
+ barmode: LiteralPlotlyHistogramBarMode = "overlay",
1061
+ xbins_size: float | None = None,
1062
+ opacity: float = 0.75,
1063
+ bargap: float = 0.0,
1064
+ bargroupgap: float = 0.0,
1065
+ curve_type: LiteralPlotlyHistogramCurveType = "kde",
1066
+ x_fmt: str | None = None,
1067
+ y_fmt: str | None = None,
1068
+ filename: str | None = None,
1069
+ directory: DirectoryPath | None = None,
1070
+ labels: list[str] | None = None,
1071
+ output_type: LiteralPlotlyOutput = "file",
1072
+ include_plotlyjs: LiteralPlotlyJSlib = "cdn",
1073
+ *,
1074
+ cumulative: bool = False,
1075
+ show_rug: bool = False,
1076
+ auto_open: bool = True,
1077
+ add_logo: bool = True,
1078
+ ) -> tuple[Figure, str]:
1079
+ """Create a Plotly Histogram Figure.
1080
+
1081
+ Parameters
1082
+ ----------
1083
+ plot_type: LiteralPlotlyHistogramPlotType, default: bars
1084
+ Type of plot
1085
+ histnorm: LiteralPlotlyHistogramHistNorm, default: percent
1086
+ Sets the normalization mode
1087
+ barmode: LiteralPlotlyHistogramBarMode, default: overlay
1088
+ Specifies how bar traces are displayed relative to one another
1089
+ xbins_size: float, optional
1090
+ Explicitly sets the width of each bin along the x-axis in data units
1091
+ opacity: float, default: 0.75
1092
+ Sets the trace opacity, must be between 0 (fully transparent) and 1
1093
+ bargap: float, default: 0.0
1094
+ Sets the gap between bars of adjacent location coordinates
1095
+ bargroupgap: float, default: 0.0
1096
+ Sets the gap between bar “groups” at the same location coordinate
1097
+ curve_type: LiteralPlotlyHistogramCurveType, default: kde
1098
+ Specifies the type of distribution curve to overlay on the histogram
1099
+ y_fmt: str, optional
1100
+ None, '%', '.1%' depending on number of decimals to show on the y-axis
1101
+ x_fmt: str, optional
1102
+ None, '%', '.1%' depending on number of decimals to show on the x-axis
1103
+ filename: str, optional
1104
+ Name of the Plotly html file
1105
+ directory: DirectoryPath, optional
1106
+ Directory where Plotly html file is saved
1107
+ labels: list[str], optional
1108
+ A list of labels to manually override using the names of
1109
+ the input self.tsdf
1110
+ output_type: LiteralPlotlyOutput, default: "file"
1111
+ Determines output type
1112
+ include_plotlyjs: LiteralPlotlyJSlib, default: "cdn"
1113
+ Determines how the plotly.js library is included in the output
1114
+ cumulative: bool, default: False
1115
+ Determines whether to compute a cumulative histogram
1116
+ show_rug: bool, default: False
1117
+ Determines whether to draw a rug plot alongside the distribution
1118
+ auto_open: bool, default: True
1119
+ Determines whether to open a browser window with the plot
1120
+ add_logo: bool, default: True
1121
+ If True a Captor logo is added to the plot
1122
+
1123
+ Returns:
1124
+ -------
1125
+ tuple[plotly.go.Figure, str]
1126
+ Plotly Figure and a div section or a html filename with location
1127
+
1128
+ """
1129
+ if labels:
1130
+ if len(labels) != self.tsdf.shape[1]:
1131
+ msg = "Must provide same number of labels as items in frame."
1132
+ raise NumberOfItemsAndLabelsNotSameError(msg)
1133
+ else:
1134
+ labels = list(self.tsdf.columns.get_level_values(0))
1135
+
1136
+ if directory:
1137
+ dirpath = Path(directory).resolve()
1138
+ elif Path.home().joinpath("Documents").exists():
1139
+ dirpath = Path.home().joinpath("Documents")
1140
+ else:
1141
+ dirpath = Path(stack()[1].filename).parent
1142
+
1143
+ if not filename:
1144
+ filename = "".join(choice(ascii_letters) for _ in range(6)) + ".html"
1145
+ plotfile = dirpath.joinpath(filename)
1146
+
1147
+ fig_dict, logo = load_plotly_dict()
1148
+
1149
+ hovertemplate = f"Count: %{{y:{y_fmt}}}" if y_fmt else "Count: %{y}"
1150
+
1151
+ if x_fmt:
1152
+ hovertemplate += f"<br>%{{x:{x_fmt}}}"
1153
+ else:
1154
+ hovertemplate += "<br>%{x}"
1155
+
1156
+ msg = "plot_type must be 'bars' or 'lines'."
1157
+ if plot_type == "bars":
1158
+ figure = Figure(fig_dict)
1159
+ for item in range(self.tsdf.shape[1]):
1160
+ figure.add_histogram(
1161
+ x=self.tsdf.iloc[:, item],
1162
+ cumulative={"enabled": cumulative},
1163
+ histnorm=histnorm,
1164
+ name=labels[item],
1165
+ xbins={"size": xbins_size},
1166
+ opacity=opacity,
1167
+ hovertemplate=hovertemplate,
1168
+ )
1169
+ figure.update_layout(
1170
+ barmode=barmode,
1171
+ bargap=bargap,
1172
+ bargroupgap=bargroupgap,
1173
+ )
1174
+ elif plot_type == "lines":
1175
+ hist_data = [
1176
+ cast("Series[float]", self.tsdf.loc[:, ds]).dropna().tolist()
1177
+ for ds in self.tsdf
1178
+ ]
1179
+ figure = create_distplot(
1180
+ hist_data=hist_data,
1181
+ curve_type=curve_type,
1182
+ group_labels=labels,
1183
+ show_hist=False,
1184
+ show_rug=show_rug,
1185
+ histnorm=histnorm,
1186
+ )
1187
+ figure.update_layout(dict1=fig_dict["layout"])
1188
+ else:
1189
+ raise TypeError(msg)
1190
+
1191
+ figure.update_layout(xaxis={"tickformat": x_fmt}, yaxis={"tickformat": y_fmt})
1192
+
1193
+ figure.update_xaxes(zeroline=True, zerolinewidth=2, zerolinecolor="lightgrey")
1194
+ figure.update_yaxes(zeroline=True, zerolinewidth=2, zerolinecolor="lightgrey")
1195
+
1196
+ if add_logo:
1197
+ figure.add_layout_image(logo)
1198
+
1199
+ if output_type == "file":
1200
+ plot(
1201
+ figure_or_data=figure,
1202
+ filename=str(plotfile),
1203
+ auto_open=auto_open,
1204
+ auto_play=False,
1205
+ link_text="",
1206
+ include_plotlyjs=cast("bool", include_plotlyjs),
1207
+ config=fig_dict["config"],
1208
+ output_type=output_type,
1209
+ )
1210
+ string_output = str(plotfile)
1211
+ else:
1212
+ div_id = filename.rsplit(".", 1)[0]
1213
+ string_output = to_html(
1214
+ fig=figure,
1215
+ config=fig_dict["config"],
1216
+ auto_play=False,
1217
+ include_plotlyjs=cast("bool", include_plotlyjs),
1218
+ full_html=False,
1219
+ div_id=div_id,
1220
+ )
1221
+
1222
+ return figure, string_output
1223
+
1008
1224
  def arithmetic_ret_func(
1009
1225
  self: Self,
1010
1226
  months_from_last: int | None = None,
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import datetime as dt
6
6
  from typing import TYPE_CHECKING, cast
7
7
 
8
+ import pandas_market_calendars as mcal
8
9
  from dateutil.relativedelta import relativedelta
9
10
  from holidays import (
10
11
  country_holidays,
@@ -24,6 +25,7 @@ from pandas.tseries.offsets import CustomBusinessDay
24
25
  from .owntypes import (
25
26
  BothStartAndEndError,
26
27
  CountriesNotStringNorListStrError,
28
+ MarketsNotStringNorListStrError,
27
29
  TradingDaysNotAboveZeroError,
28
30
  )
29
31
 
@@ -31,7 +33,6 @@ if TYPE_CHECKING:
31
33
  from .owntypes import ( # pragma: no cover
32
34
  CountriesType,
33
35
  DateType,
34
- HolidayType,
35
36
  LiteralBizDayFreq,
36
37
  )
37
38
 
@@ -45,11 +46,59 @@ __all__ = [
45
46
  ]
46
47
 
47
48
 
49
+ def market_holidays(
50
+ startyear: int,
51
+ endyear: int,
52
+ markets: str | list[str],
53
+ ) -> list[str]:
54
+ """Return a dict of holiday dates mapping to list of markets closed.
55
+
56
+ Parameters
57
+ ----------
58
+ startyear: int
59
+ First year (inclusive) to consider.
60
+ endyear: int
61
+ Last year (inclusive) to consider.
62
+ markets: str | list[str]
63
+ String or list of market codes supported by pandas_market_calendars.
64
+
65
+ Returns:
66
+ -------
67
+ list[str]
68
+ list of holiday dates.
69
+ """
70
+ market_list = [markets] if isinstance(markets, str) else list(markets)
71
+
72
+ supported = mcal.get_calendar_names()
73
+
74
+ if not all(m in supported for m in market_list):
75
+ msg = (
76
+ "Argument markets must be a string market code or a list of market "
77
+ "codes supported by pandas_market_calendars."
78
+ )
79
+ raise MarketsNotStringNorListStrError(msg)
80
+
81
+ holidays: list[str] = []
82
+ for m in market_list:
83
+ cal = mcal.get_calendar(m)
84
+ # noinspection PyUnresolvedReferences
85
+ cal_hols = cal.holidays().calendar.holidays
86
+ my_hols: list[str] = [
87
+ str(date)
88
+ for date in cal_hols
89
+ if (startyear <= int(str(date)[:4]) <= endyear)
90
+ ]
91
+ holidays.extend(my_hols)
92
+
93
+ return list(set(holidays))
94
+
95
+
48
96
  def holiday_calendar(
49
97
  startyear: int,
50
98
  endyear: int,
51
99
  countries: CountriesType = "SE",
52
- custom_holidays: HolidayType | None = None,
100
+ markets: list[str] | str | None = None,
101
+ custom_holidays: list[str] | str | None = None,
53
102
  ) -> busdaycalendar:
54
103
  """Generate a business calendar.
55
104
 
@@ -61,9 +110,10 @@ def holiday_calendar(
61
110
  Last year in date range generated
62
111
  countries: CountriesType, default: "SE"
63
112
  (List of) country code(s) according to ISO 3166-1 alpha-2
64
- custom_holidays: HolidayType, optional
65
- Argument where missing holidays can be added as
66
- {"2021-02-12": "Jack's birthday"} or ["2021-02-12"]
113
+ markets: list[str] | str, optional
114
+ (List of) markets code(s) according to pandas-market-calendars
115
+ custom_holidays: list[str] | str, optional
116
+ Argument where missing holidays can be added
67
117
 
68
118
  Returns:
69
119
  -------
@@ -79,20 +129,16 @@ def holiday_calendar(
79
129
 
80
130
  if isinstance(countries, str) and countries in list_supported_countries():
81
131
  staging = country_holidays(country=countries, years=years)
82
- if custom_holidays is not None:
83
- staging.update(custom_holidays)
84
- hols = array(sorted(staging.keys()), dtype="datetime64[D]")
85
- elif isinstance(countries, list) and all(
132
+ hols = list(staging.keys())
133
+ elif isinstance(countries, (list, set)) and all(
86
134
  country in list_supported_countries() for country in countries
87
135
  ):
88
136
  country: str
89
137
  countryholidays: list[dt.date | str] = []
90
- for i, country in enumerate(countries):
138
+ for country in countries:
91
139
  staging = country_holidays(country=country, years=years)
92
- if i == 0 and custom_holidays is not None:
93
- staging.update(custom_holidays)
94
140
  countryholidays += list(staging)
95
- hols = array(sorted(set(countryholidays)), dtype="datetime64[D]")
141
+ hols = list(countryholidays)
96
142
  else:
97
143
  msg = (
98
144
  "Argument countries must be a string country code or "
@@ -100,7 +146,22 @@ def holiday_calendar(
100
146
  )
101
147
  raise CountriesNotStringNorListStrError(msg)
102
148
 
103
- return busdaycalendar(holidays=hols)
149
+ if markets:
150
+ market_hols = market_holidays(
151
+ startyear=startyear, endyear=endyear, markets=markets
152
+ )
153
+ dt_mkt_hols = [date_fix(fixerdate=ddate) for ddate in market_hols]
154
+ hols.extend(dt_mkt_hols)
155
+
156
+ if custom_holidays:
157
+ custom_list = (
158
+ [custom_holidays]
159
+ if isinstance(custom_holidays, str)
160
+ else list(custom_holidays) # type: ignore[arg-type]
161
+ )
162
+ hols.extend([date_fix(fixerdate=ddate) for ddate in custom_list])
163
+
164
+ return busdaycalendar(holidays=array(sorted(set(hols)), dtype="datetime64[D]"))
104
165
 
105
166
 
106
167
  def date_fix(
@@ -137,7 +198,8 @@ def date_offset_foll(
137
198
  raw_date: DateType,
138
199
  months_offset: int = 12,
139
200
  countries: CountriesType = "SE",
140
- custom_holidays: HolidayType | None = None,
201
+ markets: list[str] | str | None = None,
202
+ custom_holidays: list[str] | str | None = None,
141
203
  *,
142
204
  adjust: bool = False,
143
205
  following: bool = True,
@@ -152,9 +214,10 @@ def date_offset_foll(
152
214
  Number of months as integer
153
215
  countries: CountriesType, default: "SE"
154
216
  (List of) country code(s) according to ISO 3166-1 alpha-2
155
- custom_holidays: HolidayType, optional
156
- Argument where missing holidays can be added as
157
- {"2021-02-12": "Jack's birthday"} or ["2021-02-12"]
217
+ markets: list[str] | str, optional
218
+ (List of) markets code(s) according to pandas-market-calendars
219
+ custom_holidays: list[str] | str, optional
220
+ Argument where missing holidays can be added
158
221
  adjust: bool, default: False
159
222
  Determines if offset should adjust for business days
160
223
  following: bool, default: True
@@ -180,6 +243,7 @@ def date_offset_foll(
180
243
  startyear=startyear,
181
244
  endyear=endyear,
182
245
  countries=countries,
246
+ markets=markets,
183
247
  custom_holidays=custom_holidays,
184
248
  )
185
249
  while not is_busday(dates=new_date, busdaycal=calendar):
@@ -191,7 +255,8 @@ def date_offset_foll(
191
255
  def get_previous_business_day_before_today(
192
256
  today: dt.date | None = None,
193
257
  countries: CountriesType = "SE",
194
- custom_holidays: HolidayType | None = None,
258
+ markets: list[str] | str | None = None,
259
+ custom_holidays: list[str] | str | None = None,
195
260
  ) -> dt.date:
196
261
  """Bump date backwards to find the previous business day.
197
262
 
@@ -201,9 +266,10 @@ def get_previous_business_day_before_today(
201
266
  Manual input of the day from where the previous business day is found
202
267
  countries: CountriesType, default: "SE"
203
268
  (List of) country code(s) according to ISO 3166-1 alpha-2
204
- custom_holidays: HolidayType, optional
205
- Argument where missing holidays can be added as
206
- {"2021-02-12": "Jack's birthday"} or ["2021-02-12"]
269
+ markets: list[str] | str, optional
270
+ (List of) markets code(s) according to pandas-market-calendars
271
+ custom_holidays: list[str] | str, optional
272
+ Argument where missing holidays can be added
207
273
 
208
274
  Returns:
209
275
  -------
@@ -215,10 +281,11 @@ def get_previous_business_day_before_today(
215
281
  today = dt.datetime.now().astimezone().date()
216
282
 
217
283
  return date_offset_foll(
218
- today - dt.timedelta(days=1),
284
+ raw_date=today - dt.timedelta(days=1),
285
+ months_offset=0,
219
286
  countries=countries,
287
+ markets=markets,
220
288
  custom_holidays=custom_holidays,
221
- months_offset=0,
222
289
  adjust=True,
223
290
  following=False,
224
291
  )
@@ -228,7 +295,8 @@ def offset_business_days(
228
295
  ddate: dt.date,
229
296
  days: int,
230
297
  countries: CountriesType = "SE",
231
- custom_holidays: HolidayType | None = None,
298
+ markets: list[str] | str | None = None,
299
+ custom_holidays: list[str] | str | None = None,
232
300
  ) -> dt.date:
233
301
  """Bump date by business days.
234
302
 
@@ -244,9 +312,10 @@ def offset_business_days(
244
312
  If days is set as anything other than an integer its value is set to zero
245
313
  countries: CountriesType, default: "SE"
246
314
  (List of) country code(s) according to ISO 3166-1 alpha-2
247
- custom_holidays: HolidayType, optional
248
- Argument where missing holidays can be added as
249
- {"2021-02-12": "Jack's birthday"} or ["2021-02-12"]
315
+ markets: list[str] | str, optional
316
+ (List of) markets code(s) according to pandas-market-calendars
317
+ custom_holidays: list[str] | str, optional
318
+ Argument where missing holidays can be added
250
319
 
251
320
  Returns:
252
321
  -------
@@ -266,6 +335,7 @@ def offset_business_days(
266
335
  startyear=ndate.year,
267
336
  endyear=ddate.year,
268
337
  countries=countries,
338
+ markets=markets,
269
339
  custom_holidays=custom_holidays,
270
340
  )
271
341
  local_bdays: list[dt.date] = [
@@ -283,6 +353,7 @@ def offset_business_days(
283
353
  startyear=ddate.year,
284
354
  endyear=ndate.year,
285
355
  countries=countries,
356
+ markets=markets,
286
357
  custom_holidays=custom_holidays,
287
358
  )
288
359
  local_bdays = [
@@ -310,6 +381,8 @@ def generate_calendar_date_range(
310
381
  start: dt.date | None = None,
311
382
  end: dt.date | None = None,
312
383
  countries: CountriesType = "SE",
384
+ markets: list[str] | str | None = None,
385
+ custom_holidays: list[str] | str | None = None,
313
386
  ) -> list[dt.date]:
314
387
  """Generate a list of business day calendar dates.
315
388
 
@@ -323,6 +396,10 @@ def generate_calendar_date_range(
323
396
  Date when the range ends
324
397
  countries: CountriesType, default: "SE"
325
398
  (List of) country code(s) according to ISO 3166-1 alpha-2
399
+ markets: list[str] | str, optional
400
+ (List of) markets code(s) according to pandas-market-calendars
401
+ custom_holidays: list[str] | str, optional
402
+ Argument where missing holidays can be added
326
403
 
327
404
  Returns:
328
405
  -------
@@ -344,6 +421,8 @@ def generate_calendar_date_range(
344
421
  startyear=start.year,
345
422
  endyear=date_fix(tmp_range.tolist()[-1]).year,
346
423
  countries=countries,
424
+ markets=markets,
425
+ custom_holidays=custom_holidays,
347
426
  )
348
427
  return [
349
428
  d.date()
@@ -360,6 +439,8 @@ def generate_calendar_date_range(
360
439
  startyear=date_fix(tmp_range.tolist()[0]).year,
361
440
  endyear=end.year,
362
441
  countries=countries,
442
+ markets=markets,
443
+ custom_holidays=custom_holidays,
363
444
  )
364
445
  return [
365
446
  d.date()
@@ -381,6 +462,8 @@ def _do_resample_to_business_period_ends(
381
462
  data: DataFrame,
382
463
  freq: LiteralBizDayFreq,
383
464
  countries: CountriesType,
465
+ markets: list[str] | str | None = None,
466
+ custom_holidays: list[str] | str | None = None,
384
467
  ) -> DatetimeIndex:
385
468
  """Resample timeseries frequency to business calendar month end dates.
386
469
 
@@ -394,7 +477,10 @@ def _do_resample_to_business_period_ends(
394
477
  The date offset string that sets the resampled frequency
395
478
  countries: CountriesType
396
479
  (List of) country code(s) according to ISO 3166-1 alpha-2
397
- to create a business day calendar used for date adjustments
480
+ markets: list[str] | str, optional
481
+ (List of) markets code(s) according to pandas-market-calendars
482
+ custom_holidays: list[str] | str, optional
483
+ Argument where missing holidays can be added
398
484
 
399
485
  Returns:
400
486
  -------
@@ -413,12 +499,14 @@ def _do_resample_to_business_period_ends(
413
499
  dates = DatetimeIndex(
414
500
  [copydata.index[0]]
415
501
  + [
416
- date_offset_foll(
417
- dt.date(d.year, d.month, 1)
502
+ date_offset_foll( # type: ignore[misc]
503
+ raw_date=dt.date(d.year, d.month, 1)
418
504
  + relativedelta(months=1)
419
505
  - dt.timedelta(days=1),
420
- countries=countries,
421
506
  months_offset=0,
507
+ countries=countries,
508
+ markets=markets,
509
+ custom_holidays=custom_holidays,
422
510
  adjust=True,
423
511
  following=False,
424
512
  )
@@ -41,7 +41,6 @@ from pydantic import field_validator
41
41
  from ._common_model import _CommonModel
42
42
  from .datefixer import _do_resample_to_business_period_ends
43
43
  from .owntypes import (
44
- CountriesType,
45
44
  DaysInYearType,
46
45
  LabelsNotUniqueError,
47
46
  LiteralBizDayFreq,
@@ -429,7 +428,6 @@ class OpenFrame(_CommonModel):
429
428
  def resample_to_business_period_ends(
430
429
  self: Self,
431
430
  freq: LiteralBizDayFreq = "BME",
432
- countries: CountriesType = "SE",
433
431
  method: LiteralPandasReindexMethod = "nearest",
434
432
  ) -> Self:
435
433
  """Resamples timeseries frequency to the business calendar month end dates.
@@ -440,9 +438,6 @@ class OpenFrame(_CommonModel):
440
438
  ----------
441
439
  freq: LiteralBizDayFreq, default "BME"
442
440
  The date offset string that sets the resampled frequency
443
- countries: CountriesType, default: "SE"
444
- (List of) country code(s) according to ISO 3166-1 alpha-2
445
- to create a business day calendar used for date adjustments
446
441
  method: LiteralPandasReindexMethod, default: nearest
447
442
  Controls the method used to align values across columns
448
443
 
@@ -456,7 +451,8 @@ class OpenFrame(_CommonModel):
456
451
  dates = _do_resample_to_business_period_ends(
457
452
  data=xerie.tsdf,
458
453
  freq=freq,
459
- countries=countries,
454
+ countries=xerie.countries,
455
+ markets=xerie.markets,
460
456
  )
461
457
  xerie.tsdf = xerie.tsdf.reindex(
462
458
  [deyt.date() for deyt in dates],
@@ -86,16 +86,6 @@ DaysInYearType = Annotated[int, Field(strict=True, ge=1, le=366)]
86
86
 
87
87
  DateType = str | dt.date | dt.datetime | datetime64 | Timestamp
88
88
 
89
- HolidayType = (
90
- dict[dt.date | dt.datetime | str | float | int, str]
91
- | list[dt.date | dt.datetime | str | float | int]
92
- | dt.date
93
- | dt.datetime
94
- | str
95
- | float
96
- | int
97
- )
98
-
99
89
  PlotlyLayoutType = dict[
100
90
  str,
101
91
  str
@@ -135,6 +125,15 @@ LiteralCaptureRatio = Literal["up", "down", "both"]
135
125
  LiteralBarPlotMode = Literal["stack", "group", "overlay", "relative"]
136
126
  LiteralPlotlyOutput = Literal["file", "div"]
137
127
  LiteralPlotlyJSlib = Literal[True, False, "cdn"]
128
+ LiteralPlotlyHistogramPlotType = Literal["bars", "lines"]
129
+ LiteralPlotlyHistogramBarMode = Literal["stack", "group", "overlay", "relative"]
130
+ LiteralPlotlyHistogramCurveType = Literal["normal", "kde"]
131
+ LiteralPlotlyHistogramHistNorm = Literal[
132
+ "percent",
133
+ "probability",
134
+ "density",
135
+ "probability density",
136
+ ]
138
137
  LiteralOlsFitMethod = Literal["pinv", "qr"]
139
138
  LiteralPortfolioWeightings = Literal["eq_weights", "inv_vol"]
140
139
  LiteralOlsFitCovType = Literal[
@@ -347,6 +346,10 @@ class CountriesNotStringNorListStrError(Exception):
347
346
  """Raised when countries argument is not provided in correct format."""
348
347
 
349
348
 
349
+ class MarketsNotStringNorListStrError(Exception):
350
+ """Raised when markets argument is not provided in correct format."""
351
+
352
+
350
353
  class TradingDaysNotAboveZeroError(Exception):
351
354
  """Raised when trading days argument is not above zero."""
352
355
 
@@ -43,7 +43,7 @@
43
43
  "size": 14
44
44
  },
45
45
  "legend": {
46
- "bgcolor": "rgba(255,255,255,1)",
46
+ "bgcolor": "rgba(0,0,0,0)",
47
47
  "orientation": "h",
48
48
  "x": 0.98,
49
49
  "xanchor": "right",
@@ -45,6 +45,7 @@ from .owntypes import (
45
45
  LiteralBizDayFreq,
46
46
  LiteralPandasReindexMethod,
47
47
  LiteralSeriesProps,
48
+ MarketsNotStringNorListStrError,
48
49
  OpenTimeSeriesPropertiesList,
49
50
  Self,
50
51
  ValueListType,
@@ -91,6 +92,8 @@ class OpenTimeSeries(_CommonModel):
91
92
  ISO 4217 currency code of the user's home currency
92
93
  countries: CountriesType, default: "SE"
93
94
  (List of) country code(s) according to ISO 3166-1 alpha-2
95
+ markets: list[str] | str, optional
96
+ (List of) markets code(s) according to pandas-market-calendars
94
97
  isin : str, optional
95
98
  ISO 6166 identifier code of the associated instrument
96
99
  label : str, optional
@@ -109,6 +112,7 @@ class OpenTimeSeries(_CommonModel):
109
112
  currency: CurrencyStringType
110
113
  domestic: CurrencyStringType = "SEK"
111
114
  countries: CountriesType = "SE"
115
+ markets: list[str] | str | None = None # type: ignore[assignment]
112
116
  isin: str | None = None
113
117
  label: str | None = None
114
118
 
@@ -126,6 +130,25 @@ class OpenTimeSeries(_CommonModel):
126
130
  _ = Countries(countryinput=value)
127
131
  return value
128
132
 
133
+ @field_validator("markets", mode="before") # type: ignore[misc]
134
+ @classmethod
135
+ def _validate_markets(
136
+ cls, value: list[str] | str | None
137
+ ) -> list[str] | str | None:
138
+ """Pydantic validator to ensure markets field is validated."""
139
+ msg = (
140
+ "'markets' must be a string or list of strings, "
141
+ f"got {type(value).__name__!r}"
142
+ )
143
+ if value is None or isinstance(value, str):
144
+ return value
145
+ if isinstance(value, list):
146
+ if all(isinstance(item, str) for item in value) and len(value) != 0:
147
+ return value
148
+ item_msg = "All items in 'markets' must be strings."
149
+ raise MarketsNotStringNorListStrError(item_msg)
150
+ raise MarketsNotStringNorListStrError(msg)
151
+
129
152
  @model_validator(mode="after") # type: ignore[misc,unused-ignore]
130
153
  def _dates_and_values_validate(self: Self) -> Self:
131
154
  """Pydantic validator to ensure dates and values are validated."""
@@ -587,6 +610,7 @@ class OpenTimeSeries(_CommonModel):
587
610
  data=self.tsdf,
588
611
  freq=freq,
589
612
  countries=self.countries,
613
+ markets=self.markets,
590
614
  )
591
615
  self.tsdf = self.tsdf.reindex([deyt.date() for deyt in dates], method=method)
592
616
  return self
@@ -417,6 +417,7 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
417
417
  start: dt.date | None = None,
418
418
  end: dt.date | None = None,
419
419
  countries: CountriesType = "SE",
420
+ markets: list[str] | str | None = None,
420
421
  ) -> DataFrame:
421
422
  """Create a pandas.DataFrame from simulation(s).
422
423
 
@@ -430,6 +431,8 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
430
431
  Date when the simulation ends
431
432
  countries: CountriesType, default: "SE"
432
433
  (List of) country code(s) according to ISO 3166-1 alpha-2
434
+ markets: list[str] | str, optional
435
+ (List of) markets code(s) according to pandas-market-calendars
433
436
 
434
437
  Returns:
435
438
  -------
@@ -442,6 +445,7 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
442
445
  start=start,
443
446
  end=end,
444
447
  countries=countries,
448
+ markets=markets,
445
449
  )
446
450
 
447
451
  if self.number_of_sims == 1:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openseries"
3
- version = "1.8.2"
3
+ version = "1.8.4"
4
4
  description = "Tools for analyzing financial timeseries."
5
5
  authors = [
6
6
  { name = "Martin Karrin", email = "martin.karrin@captor.se" },
@@ -50,7 +50,8 @@ dependencies = [
50
50
  "python-dateutil (>=2.8.2,<4.0.0)",
51
51
  "requests (>=2.20.0,<3.0.0)",
52
52
  "scipy (>=1.11.4,<2.0.0)",
53
- "statsmodels (>=0.14.0,!=0.14.2,<1.0.0)"
53
+ "statsmodels (>=0.14.0,!=0.14.2,<1.0.0)",
54
+ "pandas-market-calendars (>=5.1.0,<7.0.0)"
54
55
  ]
55
56
 
56
57
  [project.urls]
@@ -63,17 +64,17 @@ dependencies = [
63
64
  black = ">=24.4.2,<27.0.0"
64
65
  coverage = ">=7.2.7,<9.0.0"
65
66
  genbadge = { version = ">=1.1.1,<2.0.0", extras = ["coverage"] }
66
- mypy = "1.15.0"
67
+ mypy = "1.16.0"
67
68
  pandas-stubs = ">=2.1.2,<3.0.0"
68
69
  pre-commit = ">=3.7.1,<6.0.0"
69
70
  pytest = ">=8.2.2,<9.0.0"
70
- ruff = "0.11.8"
71
+ ruff = "0.11.12"
71
72
  types-openpyxl = ">=3.1.2,<5.0.0"
72
73
  types-python-dateutil = ">=2.8.2,<4.0.0"
73
74
  types-requests = ">=2.20.0,<3.0.0"
74
75
 
75
76
  [build-system]
76
- requires = ["poetry-core>=2.1.2"]
77
+ requires = ["poetry-core>=2.1.3"]
77
78
  build-backend = "poetry.core.masonry.api"
78
79
 
79
80
  [tool.coverage.run]
@@ -131,7 +132,7 @@ ignore = ["COM812", "D203", "D213"]
131
132
  fixable = ["ALL"]
132
133
  mccabe = { max-complexity = 15 }
133
134
  pydocstyle = { convention = "google" }
134
- pylint = { max-args = 12, max-branches = 23, max-statements = 59 }
135
+ pylint = { max-args = 19, max-branches = 23, max-statements = 59 }
135
136
 
136
137
  [tool.pytest.ini_options]
137
138
  testpaths = "tests"
File without changes