openseries 1.6.0__py3-none-any.whl → 1.7.1__py3-none-any.whl

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.
openseries/load_plotly.py CHANGED
@@ -5,18 +5,19 @@ from __future__ import annotations
5
5
  from json import load
6
6
  from logging import warning
7
7
  from pathlib import Path
8
+ from typing import TYPE_CHECKING
8
9
 
9
10
  import requests
10
11
  from requests.exceptions import ConnectionError
11
12
 
12
- from openseries.types import CaptorLogoType, PlotlyLayoutType
13
+ if TYPE_CHECKING:
14
+ from .types import CaptorLogoType, PlotlyLayoutType # pragma: no cover
13
15
 
14
16
  __all__ = ["load_plotly_dict"]
15
17
 
16
18
 
17
19
  def _check_remote_file_existence(url: str) -> bool:
18
- """
19
- Check if remote file exists.
20
+ """Check if remote file exists.
20
21
 
21
22
  Parameters
22
23
  ----------
@@ -44,8 +45,7 @@ def load_plotly_dict(
44
45
  *,
45
46
  responsive: bool = True,
46
47
  ) -> tuple[PlotlyLayoutType, CaptorLogoType]:
47
- """
48
- Load Plotly defaults.
48
+ """Load Plotly defaults.
49
49
 
50
50
  Parameters
51
51
  ----------
@@ -58,13 +58,13 @@ def load_plotly_dict(
58
58
  A dictionary with the Plotly config and layout template
59
59
 
60
60
  """
61
- project_root = Path(__file__).resolve().parent.parent
61
+ project_root = Path(__file__).parent.parent
62
62
  layoutfile = project_root.joinpath("openseries").joinpath("plotly_layouts.json")
63
63
  logofile = project_root.joinpath("openseries").joinpath("plotly_captor_logo.json")
64
64
 
65
- with Path.open(layoutfile, mode="r", encoding="utf-8") as layout_file:
65
+ with layoutfile.open(mode="r", encoding="utf-8") as layout_file:
66
66
  fig = load(layout_file)
67
- with Path.open(logofile, mode="r", encoding="utf-8") as logo_file:
67
+ with logofile.open(mode="r", encoding="utf-8") as logo_file:
68
68
  logo = load(logo_file)
69
69
 
70
70
  if _check_remote_file_existence(url=logo["source"]) is False:
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  from inspect import stack
7
7
  from pathlib import Path
8
- from typing import Callable, Optional, Union, cast
8
+ from typing import TYPE_CHECKING, Callable, cast
9
9
 
10
10
  from numpy import (
11
11
  append,
@@ -13,6 +13,7 @@ from numpy import (
13
13
  dot,
14
14
  float64,
15
15
  inf,
16
+ isnan,
16
17
  linspace,
17
18
  nan,
18
19
  sqrt,
@@ -30,15 +31,14 @@ from pandas import (
30
31
  from plotly.graph_objs import Figure # type: ignore[import-untyped,unused-ignore]
31
32
  from plotly.io import to_html # type: ignore[import-untyped,unused-ignore]
32
33
  from plotly.offline import plot # type: ignore[import-untyped,unused-ignore]
33
- from pydantic import DirectoryPath
34
34
  from scipy.optimize import minimize # type: ignore[import-untyped,unused-ignore]
35
35
 
36
+ from .load_plotly import load_plotly_dict
37
+ from .series import OpenTimeSeries
38
+
36
39
  # noinspection PyProtectedMember
37
- from openseries.frame import OpenFrame
38
- from openseries.load_plotly import load_plotly_dict
39
- from openseries.series import OpenTimeSeries
40
- from openseries.simulation import _random_generator
41
- from openseries.types import (
40
+ from .simulation import _random_generator
41
+ from .types import (
42
42
  LiteralLinePlotMode,
43
43
  LiteralMinimizeMethods,
44
44
  LiteralPlotlyJSlib,
@@ -46,6 +46,11 @@ from openseries.types import (
46
46
  ValueType,
47
47
  )
48
48
 
49
+ if TYPE_CHECKING: # pragma: no cover
50
+ from pydantic import DirectoryPath
51
+
52
+ from .frame import OpenFrame
53
+
49
54
  __all__ = [
50
55
  "constrain_optimized_portfolios",
51
56
  "efficient_frontier",
@@ -60,8 +65,7 @@ def simulate_portfolios(
60
65
  num_ports: int,
61
66
  seed: int,
62
67
  ) -> DataFrame:
63
- """
64
- Generate random weights for simulated portfolios.
68
+ """Generate random weights for simulated portfolios.
65
69
 
66
70
  Parameters
67
71
  ----------
@@ -130,14 +134,13 @@ def efficient_frontier( # noqa: C901
130
134
  eframe: OpenFrame,
131
135
  num_ports: int = 5000,
132
136
  seed: int = 71,
133
- bounds: Optional[tuple[tuple[float]]] = None,
137
+ bounds: tuple[tuple[float]] | None = None,
134
138
  frontier_points: int = 200,
135
139
  minimize_method: LiteralMinimizeMethods = "SLSQP",
136
140
  *,
137
141
  tweak: bool = True,
138
142
  ) -> tuple[DataFrame, DataFrame, NDArray[float64]]:
139
- """
140
- Identify an efficient frontier.
143
+ """Identify an efficient frontier.
141
144
 
142
145
  Parameters
143
146
  ----------
@@ -180,10 +183,11 @@ def efficient_frontier( # noqa: C901
180
183
  simulated = simulate_portfolios(simframe=copi, num_ports=num_ports, seed=seed)
181
184
 
182
185
  frontier_min = simulated.loc[simulated["stdev"].idxmin()]["ret"]
183
- arithmetic_mean = log_ret.mean() * copi.periods_in_a_year
184
- frontier_max = 0.0
185
- if isinstance(arithmetic_mean, Series):
186
- frontier_max = arithmetic_mean.max()
186
+
187
+ arithmetic_means = array(log_ret.mean() * copi.periods_in_a_year)
188
+ cleaned_arithmetic_means = arithmetic_means[~isnan(arithmetic_means)]
189
+
190
+ frontier_max = cleaned_arithmetic_means.max()
187
191
 
188
192
  def _check_sum(weights: NDArray[float64]) -> float64:
189
193
  return cast(float64, npsum(weights) - 1)
@@ -258,7 +262,7 @@ def efficient_frontier( # noqa: C901
258
262
 
259
263
  for possible_return in frontier_y:
260
264
  cons = cast(
261
- dict[str, Union[str, Callable[[float, NDArray[float64]], float64]]],
265
+ dict[str, str | Callable[[float, NDArray[float64]], float64]],
262
266
  (
263
267
  {"type": "eq", "fun": _check_sum},
264
268
  {
@@ -284,7 +288,6 @@ def efficient_frontier( # noqa: C901
284
288
  frontier_x.append(result["fun"])
285
289
  frontier_weights.append(result["x"])
286
290
 
287
- # noinspection PyUnreachableCode
288
291
  line_df = concat(
289
292
  [
290
293
  DataFrame(data=frontier_weights, columns=eframe.columns_lvl_zero),
@@ -319,11 +322,10 @@ def constrain_optimized_portfolios(
319
322
  portfolioname: str = "Current Portfolio",
320
323
  simulations: int = 10000,
321
324
  curve_points: int = 200,
322
- bounds: Optional[tuple[tuple[float]]] = None,
325
+ bounds: tuple[tuple[float]] | None = None,
323
326
  minimize_method: LiteralMinimizeMethods = "SLSQP",
324
327
  ) -> tuple[OpenFrame, OpenTimeSeries, OpenFrame, OpenTimeSeries]:
325
- """
326
- Constrain optimized portfolios to those that improve on the current one.
328
+ """Constrain optimized portfolios to those that improve on the current one.
327
329
 
328
330
  Parameters
329
331
  ----------
@@ -391,8 +393,7 @@ def prepare_plot_data(
391
393
  current: OpenTimeSeries,
392
394
  optimized: NDArray[float64],
393
395
  ) -> DataFrame:
394
- """
395
- Prepare date to be used as point_frame in the sharpeplot function.
396
+ """Prepare date to be used as point_frame in the sharpeplot function.
396
397
 
397
398
  Parameters
398
399
  ----------
@@ -443,13 +444,13 @@ def prepare_plot_data(
443
444
 
444
445
 
445
446
  def sharpeplot( # noqa: C901
446
- sim_frame: Optional[DataFrame] = None,
447
- line_frame: Optional[DataFrame] = None,
448
- point_frame: Optional[DataFrame] = None,
447
+ sim_frame: DataFrame | None = None,
448
+ line_frame: DataFrame | None = None,
449
+ point_frame: DataFrame | None = None,
449
450
  point_frame_mode: LiteralLinePlotMode = "markers",
450
- filename: Optional[str] = None,
451
- directory: Optional[DirectoryPath] = None,
452
- titletext: Optional[str] = None,
451
+ filename: str | None = None,
452
+ directory: DirectoryPath | None = None,
453
+ titletext: str | None = None,
453
454
  output_type: LiteralPlotlyOutput = "file",
454
455
  include_plotlyjs: LiteralPlotlyJSlib = "cdn",
455
456
  *,
@@ -457,8 +458,7 @@ def sharpeplot( # noqa: C901
457
458
  add_logo: bool = True,
458
459
  auto_open: bool = True,
459
460
  ) -> tuple[Figure, str]:
460
- """
461
- Create scatter plot coloured by Sharpe Ratio.
461
+ """Create scatter plot coloured by Sharpe Ratio.
462
462
 
463
463
  Parameters
464
464
  ----------
@@ -510,6 +510,10 @@ def sharpeplot( # noqa: C901
510
510
  fig, logo = load_plotly_dict()
511
511
  figure = Figure(fig)
512
512
 
513
+ if sim_frame is None and line_frame is None and point_frame is None:
514
+ msg = "One of sim_frame, line_frame or point_frame must be provided."
515
+ raise ValueError(msg)
516
+
513
517
  if sim_frame is not None:
514
518
  returns.extend(list(sim_frame.loc[:, "ret"]))
515
519
  risk.extend(list(sim_frame.loc[:, "stdev"]))
@@ -544,11 +548,11 @@ def sharpeplot( # noqa: C901
544
548
  name="Efficient frontier",
545
549
  )
546
550
 
547
- colorway = fig["layout"].get("colorway")[ # type: ignore[union-attr]
548
- : len(cast(DataFrame, point_frame).columns)
549
- ]
550
-
551
551
  if point_frame is not None:
552
+ colorway = cast(
553
+ dict[str, str | int | float | bool | list[str]],
554
+ fig["layout"],
555
+ ).get("colorway")[: len(point_frame.columns)]
552
556
  for col, clr in zip(point_frame.columns, colorway):
553
557
  returns.extend([point_frame.loc["ret", col]])
554
558
  risk.extend([point_frame.loc["stdev", col]])
openseries/series.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import datetime as dt
6
6
  from copy import deepcopy
7
7
  from logging import warning
8
- from typing import Any, Optional, TypeVar, Union, cast
8
+ from typing import Any, TypeVar, cast
9
9
 
10
10
  from numpy import (
11
11
  append,
@@ -25,12 +25,12 @@ from pandas import (
25
25
  Series,
26
26
  date_range,
27
27
  )
28
- from pydantic import model_validator
28
+ from pydantic import field_validator, model_validator
29
29
  from typing_extensions import Self
30
30
 
31
- from openseries._common_model import _CommonModel
32
- from openseries.datefixer import date_fix, do_resample_to_business_period_ends
33
- from openseries.types import (
31
+ from ._common_model import _CommonModel
32
+ from .datefixer import _do_resample_to_business_period_ends, date_fix
33
+ from .types import (
34
34
  Countries,
35
35
  CountriesType,
36
36
  Currency,
@@ -53,9 +53,7 @@ TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
53
53
 
54
54
  # noinspection PyUnresolvedReferences
55
55
  class OpenTimeSeries(_CommonModel):
56
-
57
- """
58
- OpenTimeSeries objects are at the core of the openseries package.
56
+ """OpenTimeSeries objects are at the core of the openseries package.
59
57
 
60
58
  The intended use is to allow analyses of financial timeseries.
61
59
  It is only intended for daily or less frequent data samples.
@@ -104,8 +102,22 @@ class OpenTimeSeries(_CommonModel):
104
102
  currency: CurrencyStringType
105
103
  domestic: CurrencyStringType = "SEK"
106
104
  countries: CountriesType = "SE"
107
- isin: Optional[str] = None
108
- label: Optional[str] = None
105
+ isin: str | None = None
106
+ label: str | None = None
107
+
108
+ @field_validator("domestic", mode="before")
109
+ @classmethod
110
+ def validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType:
111
+ """Pydantic validator to ensure domestic field is validated."""
112
+ _ = Currency(ccy=value)
113
+ return value
114
+
115
+ @field_validator("countries", mode="before")
116
+ @classmethod
117
+ def validate_countries(cls, value: CountriesType) -> CountriesType:
118
+ """Pydantic validator to ensure countries field is validated."""
119
+ _ = Countries(countryinput=value)
120
+ return value
109
121
 
110
122
  @model_validator(mode="after") # type: ignore[misc,unused-ignore]
111
123
  def dates_and_values_validate(self: Self) -> Self:
@@ -128,29 +140,6 @@ class OpenTimeSeries(_CommonModel):
128
140
  raise ValueError(msg)
129
141
  return self
130
142
 
131
- @classmethod
132
- def setup_class(
133
- cls: type[OpenTimeSeries],
134
- domestic_ccy: CurrencyStringType = "SEK",
135
- countries: CountriesType = "SE",
136
- ) -> None:
137
- """
138
- Set the domestic currency and calendar of the user.
139
-
140
- Parameters
141
- ----------
142
- domestic_ccy : CurrencyStringType, default: "SEK"
143
- Currency code according to ISO 4217
144
- countries: CountriesType, default: "SE"
145
- (List of) country code(s) according to ISO 3166-1 alpha-2
146
-
147
- """
148
- _ = Currency(ccy=domestic_ccy)
149
- _ = Countries(countryinput=countries)
150
-
151
- cls.domestic = domestic_ccy
152
- cls.countries = countries
153
-
154
143
  @classmethod
155
144
  def from_arrays(
156
145
  cls: type[OpenTimeSeries],
@@ -160,13 +149,12 @@ class OpenTimeSeries(_CommonModel):
160
149
  valuetype: ValueType = ValueType.PRICE,
161
150
  timeseries_id: DatabaseIdStringType = "",
162
151
  instrument_id: DatabaseIdStringType = "",
163
- isin: Optional[str] = None,
152
+ isin: str | None = None,
164
153
  baseccy: CurrencyStringType = "SEK",
165
154
  *,
166
155
  local_ccy: bool = True,
167
156
  ) -> OpenTimeSeries:
168
- """
169
- Create series from a Pandas DataFrame or Series.
157
+ """Create series from a Pandas DataFrame or Series.
170
158
 
171
159
  Parameters
172
160
  ----------
@@ -217,19 +205,18 @@ class OpenTimeSeries(_CommonModel):
217
205
  @classmethod
218
206
  def from_df(
219
207
  cls: type[OpenTimeSeries],
220
- dframe: Union[DataFrame, Series[float]],
208
+ dframe: DataFrame | Series[float],
221
209
  column_nmbr: int = 0,
222
210
  valuetype: ValueType = ValueType.PRICE,
223
211
  baseccy: CurrencyStringType = "SEK",
224
212
  *,
225
213
  local_ccy: bool = True,
226
214
  ) -> OpenTimeSeries:
227
- """
228
- Create series from a Pandas DataFrame or Series.
215
+ """Create series from a Pandas DataFrame or Series.
229
216
 
230
217
  Parameters
231
218
  ----------
232
- dframe: Union[DataFrame, Series[float]]
219
+ dframe: DataFrame | Series[float]
233
220
  Pandas DataFrame or Series
234
221
  column_nmbr : int, default: 0
235
222
  Using iloc[:, column_nmbr] to pick column
@@ -252,8 +239,8 @@ class OpenTimeSeries(_CommonModel):
252
239
  else:
253
240
  label = dframe.name
254
241
  values = dframe.to_numpy().tolist()
255
- else:
256
- values = cast(DataFrame, dframe).iloc[:, column_nmbr].tolist()
242
+ elif isinstance(dframe, DataFrame):
243
+ values = dframe.iloc[:, column_nmbr].to_list()
257
244
  if isinstance(dframe.columns, MultiIndex):
258
245
  if _check_if_none(
259
246
  dframe.columns.get_level_values(0).to_numpy()[column_nmbr],
@@ -275,6 +262,10 @@ class OpenTimeSeries(_CommonModel):
275
262
  ]
276
263
  else:
277
264
  label = cast(MultiIndex, dframe.columns).to_numpy()[column_nmbr]
265
+ else:
266
+ msg = "Argument dframe must be pandas Series or DataFrame."
267
+ raise TypeError(msg)
268
+
278
269
  dates = [date_fix(d).strftime("%Y-%m-%d") for d in dframe.index]
279
270
 
280
271
  return cls(
@@ -299,17 +290,16 @@ class OpenTimeSeries(_CommonModel):
299
290
  def from_fixed_rate(
300
291
  cls: type[OpenTimeSeries],
301
292
  rate: float,
302
- d_range: Optional[DatetimeIndex] = None,
303
- days: Optional[int] = None,
304
- end_dt: Optional[dt.date] = None,
293
+ d_range: DatetimeIndex | None = None,
294
+ days: int | None = None,
295
+ end_dt: dt.date | None = None,
305
296
  label: str = "Series",
306
297
  valuetype: ValueType = ValueType.PRICE,
307
298
  baseccy: CurrencyStringType = "SEK",
308
299
  *,
309
300
  local_ccy: bool = True,
310
301
  ) -> OpenTimeSeries:
311
- """
312
- Create series from values accruing with a given fixed rate return.
302
+ """Create series from values accruing with a given fixed rate return.
313
303
 
314
304
  Providing a date_range of type Pandas DatetimeIndex takes priority over
315
305
  providing a combination of days and an end date.
@@ -380,8 +370,7 @@ class OpenTimeSeries(_CommonModel):
380
370
  )
381
371
 
382
372
  def from_deepcopy(self: Self) -> Self:
383
- """
384
- Create copy of OpenTimeSeries object.
373
+ """Create copy of OpenTimeSeries object.
385
374
 
386
375
  Returns
387
376
  -------
@@ -392,8 +381,7 @@ class OpenTimeSeries(_CommonModel):
392
381
  return deepcopy(self)
393
382
 
394
383
  def pandas_df(self: Self) -> Self:
395
- """
396
- Populate .tsdf Pandas DataFrame from the .dates and .values lists.
384
+ """Populate .tsdf Pandas DataFrame from the .dates and .values lists.
397
385
 
398
386
  Returns
399
387
  -------
@@ -413,10 +401,9 @@ class OpenTimeSeries(_CommonModel):
413
401
 
414
402
  def all_properties(
415
403
  self: Self,
416
- properties: Optional[list[LiteralSeriesProps]] = None,
404
+ properties: list[LiteralSeriesProps] | None = None,
417
405
  ) -> DataFrame:
418
- """
419
- Calculate chosen properties.
406
+ """Calculate chosen properties.
420
407
 
421
408
  Parameters
422
409
  ----------
@@ -441,8 +428,7 @@ class OpenTimeSeries(_CommonModel):
441
428
  return pdf
442
429
 
443
430
  def value_to_ret(self: Self) -> Self:
444
- """
445
- Convert series of values into series of returns.
431
+ """Convert series of values into series of returns.
446
432
 
447
433
  Returns
448
434
  -------
@@ -450,7 +436,7 @@ class OpenTimeSeries(_CommonModel):
450
436
  The returns of the values in the series
451
437
 
452
438
  """
453
- self.tsdf = self.tsdf.pct_change(fill_method=cast(str, None))
439
+ self.tsdf = self.tsdf.pct_change(fill_method=None) # type: ignore[arg-type]
454
440
  self.tsdf.iloc[0] = 0
455
441
  self.valuetype = ValueType.RTRN
456
442
  self.tsdf.columns = MultiIndex.from_arrays(
@@ -462,8 +448,7 @@ class OpenTimeSeries(_CommonModel):
462
448
  return self
463
449
 
464
450
  def value_to_diff(self: Self, periods: int = 1) -> Self:
465
- """
466
- Convert series of values to series of their period differences.
451
+ """Convert series of values to series of their period differences.
467
452
 
468
453
  Parameters
469
454
  ----------
@@ -489,8 +474,7 @@ class OpenTimeSeries(_CommonModel):
489
474
  return self
490
475
 
491
476
  def to_cumret(self: Self) -> Self:
492
- """
493
- Convert series of returns into cumulative series of values.
477
+ """Convert series of returns into cumulative series of values.
494
478
 
495
479
  Returns
496
480
  -------
@@ -520,8 +504,7 @@ class OpenTimeSeries(_CommonModel):
520
504
  days_in_year: int = 365,
521
505
  divider: float = 1.0,
522
506
  ) -> Self:
523
- """
524
- Convert series of 1-day rates into series of cumulative values.
507
+ """Convert series of 1-day rates into series of cumulative values.
525
508
 
526
509
  Parameters
527
510
  ----------
@@ -555,14 +538,13 @@ class OpenTimeSeries(_CommonModel):
555
538
 
556
539
  def resample(
557
540
  self: Self,
558
- freq: Union[LiteralBizDayFreq, str] = "BME",
541
+ freq: LiteralBizDayFreq | str = "BME",
559
542
  ) -> Self:
560
- """
561
- Resamples the timeseries frequency.
543
+ """Resamples the timeseries frequency.
562
544
 
563
545
  Parameters
564
546
  ----------
565
- freq: Union[LiteralBizDayFreq, str], default "BME"
547
+ freq: LiteralBizDayFreq | str, default "BME"
566
548
  The date offset string that sets the resampled frequency
567
549
 
568
550
  Returns
@@ -581,8 +563,7 @@ class OpenTimeSeries(_CommonModel):
581
563
  freq: LiteralBizDayFreq = "BME",
582
564
  method: LiteralPandasReindexMethod = "nearest",
583
565
  ) -> Self:
584
- """
585
- Resamples timeseries frequency to the business calendar month end dates.
566
+ """Resamples timeseries frequency to the business calendar month end dates.
586
567
 
587
568
  Stubs left in place. Stubs will be aligned to the shortest stub.
588
569
 
@@ -599,12 +580,8 @@ class OpenTimeSeries(_CommonModel):
599
580
  An OpenTimeSeries object
600
581
 
601
582
  """
602
- head = self.tsdf.iloc[0].copy()
603
- tail = self.tsdf.iloc[-1].copy()
604
- dates = do_resample_to_business_period_ends(
583
+ dates = _do_resample_to_business_period_ends(
605
584
  data=self.tsdf,
606
- head=head,
607
- tail=tail,
608
585
  freq=freq,
609
586
  countries=self.countries,
610
587
  )
@@ -616,13 +593,12 @@ class OpenTimeSeries(_CommonModel):
616
593
  lmbda: float = 0.94,
617
594
  day_chunk: int = 11,
618
595
  dlta_degr_freedms: int = 0,
619
- months_from_last: Optional[int] = None,
620
- from_date: Optional[dt.date] = None,
621
- to_date: Optional[dt.date] = None,
622
- periods_in_a_year_fixed: Optional[DaysInYearType] = None,
596
+ months_from_last: int | None = None,
597
+ from_date: dt.date | None = None,
598
+ to_date: dt.date | None = None,
599
+ periods_in_a_year_fixed: DaysInYearType | None = None,
623
600
  ) -> Series[float]:
624
- """
625
- Exponentially Weighted Moving Average Model for Volatility.
601
+ """Exponentially Weighted Moving Average Model for Volatility.
626
602
 
627
603
  https://www.investopedia.com/articles/07/ewma.asp.
628
604
 
@@ -694,8 +670,7 @@ class OpenTimeSeries(_CommonModel):
694
670
  adjustment: float,
695
671
  days_in_year: int = 365,
696
672
  ) -> Self:
697
- """
698
- Add or subtract a fee from the timeseries return.
673
+ """Add or subtract a fee from the timeseries return.
699
674
 
700
675
  Parameters
701
676
  ----------
@@ -721,7 +696,7 @@ class OpenTimeSeries(_CommonModel):
721
696
  returns_input = True
722
697
  else:
723
698
  values = [cast(float, self.tsdf.iloc[0, 0])]
724
- ra_df = self.tsdf.pct_change(fill_method=cast(str, None))
699
+ ra_df = self.tsdf.pct_change(fill_method=None) # type: ignore[arg-type]
725
700
  returns_input = False
726
701
  ra_df = ra_df.dropna()
727
702
 
@@ -754,13 +729,12 @@ class OpenTimeSeries(_CommonModel):
754
729
 
755
730
  def set_new_label(
756
731
  self: Self,
757
- lvl_zero: Optional[str] = None,
758
- lvl_one: Optional[ValueType] = None,
732
+ lvl_zero: str | None = None,
733
+ lvl_one: ValueType | None = None,
759
734
  *,
760
735
  delete_lvl_one: bool = False,
761
736
  ) -> Self:
762
- """
763
- Set the column labels of the .tsdf Pandas Dataframe.
737
+ """Set the column labels of the .tsdf Pandas Dataframe.
764
738
 
765
739
  Parameters
766
740
  ----------
@@ -799,9 +773,8 @@ def timeseries_chain(
799
773
  front: TypeOpenTimeSeries,
800
774
  back: TypeOpenTimeSeries,
801
775
  old_fee: float = 0.0,
802
- ) -> Union[TypeOpenTimeSeries, OpenTimeSeries]:
803
- """
804
- Chain two timeseries together.
776
+ ) -> TypeOpenTimeSeries | OpenTimeSeries:
777
+ """Chain two timeseries together.
805
778
 
806
779
  The function assumes that the two series have at least one date in common.
807
780
 
@@ -816,7 +789,7 @@ def timeseries_chain(
816
789
 
817
790
  Returns
818
791
  -------
819
- Union[TypeOpenTimeSeries, OpenTimeSeries]
792
+ TypeOpenTimeSeries | OpenTimeSeries
820
793
  An OpenTimeSeries object or a subclass thereof
821
794
 
822
795
  """
@@ -887,8 +860,7 @@ def timeseries_chain(
887
860
 
888
861
 
889
862
  def _check_if_none(item: Any) -> bool: # noqa: ANN401
890
- """
891
- Check if a variable is None or equivalent.
863
+ """Check if a variable is None or equivalent.
892
864
 
893
865
  Parameters
894
866
  ----------