openseries 1.7.1__tar.gz → 1.7.3__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.1
2
2
  Name: openseries
3
- Version: 1.7.1
3
+ Version: 1.7.3
4
4
  Summary: Tools for analyzing financial timeseries.
5
5
  Home-page: https://github.com/CaptorAB/openseries
6
6
  License: BSD-3-Clause
@@ -261,7 +261,6 @@ make lint
261
261
 
262
262
  | Method | Applies to | Description |
263
263
  |:-------------------------|:-----------------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
264
- | `setup_class` | `OpenTimeSeries` | Class method that defines the `domestic` home currency and the `countries` home countries attributes. |
265
264
  | `pandas_df` | `OpenTimeSeries` | Method to create the `tsdf` pandas.DataFrame from the `dates` and `values`. |
266
265
  | `set_new_label` | `OpenTimeSeries` | Method to change the pandas.DataFrame column MultiIndex. |
267
266
  | `running_adjustment` | `OpenTimeSeries` | Adjusts the series performance with a `float` factor. |
@@ -226,7 +226,6 @@ make lint
226
226
 
227
227
  | Method | Applies to | Description |
228
228
  |:-------------------------|:-----------------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
229
- | `setup_class` | `OpenTimeSeries` | Class method that defines the `domestic` home currency and the `countries` home countries attributes. |
230
229
  | `pandas_df` | `OpenTimeSeries` | Method to create the `tsdf` pandas.DataFrame from the `dates` and `values`. |
231
230
  | `set_new_label` | `OpenTimeSeries` | Method to change the pandas.DataFrame column MultiIndex. |
232
231
  | `running_adjustment` | `OpenTimeSeries` | Adjusts the series performance with a `float` factor. |
@@ -18,7 +18,15 @@ if TYPE_CHECKING:
18
18
  from openpyxl.utils.dataframe import dataframe_to_rows
19
19
  from openpyxl.workbook.workbook import Workbook
20
20
  from openpyxl.worksheet.worksheet import Worksheet
21
- from pandas import DataFrame, DatetimeIndex, Index, MultiIndex, Series, date_range
21
+ from pandas import (
22
+ DataFrame,
23
+ DatetimeIndex,
24
+ Index,
25
+ MultiIndex,
26
+ Series,
27
+ date_range,
28
+ to_datetime,
29
+ )
22
30
  from pandas.tseries.offsets import CustomBusinessDay
23
31
  from plotly.graph_objs import Figure # type: ignore[import-untyped,unused-ignore]
24
32
  from plotly.io import to_html # type: ignore[import-untyped,unused-ignore]
@@ -351,9 +359,7 @@ class _CommonModel(BaseModel):
351
359
  """
352
360
  wmdf = self.tsdf.copy()
353
361
  wmdf.index = DatetimeIndex(wmdf.index)
354
- result = (
355
- wmdf.resample("BME").last().pct_change(fill_method=cast(str, None)).min()
356
- )
362
+ result = wmdf.resample("BME").last().pct_change().min()
357
363
 
358
364
  if self.tsdf.shape[1] == 1:
359
365
  return float(result.iloc[0])
@@ -484,9 +490,7 @@ class _CommonModel(BaseModel):
484
490
  "Argument months_offset implies start"
485
491
  "date before first date in series."
486
492
  )
487
- raise ValueError(
488
- msg,
489
- )
493
+ raise ValueError(msg)
490
494
  later = self.last_idx
491
495
  else:
492
496
  if from_dt is not None:
@@ -523,8 +527,8 @@ class _CommonModel(BaseModel):
523
527
  An OpenFrame object
524
528
 
525
529
  """
526
- startyear = DatetimeIndex(self.tsdf.index)[0].year
527
- endyear = DatetimeIndex(self.tsdf.index)[-1].year
530
+ startyear = to_datetime(self.tsdf.index[0]).year
531
+ endyear = to_datetime(self.tsdf.index[-1]).year
528
532
  calendar = holiday_calendar(
529
533
  startyear=startyear,
530
534
  endyear=endyear,
@@ -785,9 +789,7 @@ class _CommonModel(BaseModel):
785
789
  if labels:
786
790
  if len(labels) != self.tsdf.shape[1]:
787
791
  msg = "Must provide same number of labels as items in frame."
788
- raise ValueError(
789
- msg,
790
- )
792
+ raise ValueError(msg)
791
793
  else:
792
794
  labels = list(self.tsdf.columns.get_level_values(0))
793
795
 
@@ -902,9 +904,7 @@ class _CommonModel(BaseModel):
902
904
  if labels:
903
905
  if len(labels) != self.tsdf.shape[1]:
904
906
  msg = "Must provide same number of labels as items in frame."
905
- raise ValueError(
906
- msg,
907
- )
907
+ raise ValueError(msg)
908
908
  else:
909
909
  labels = list(self.tsdf.columns.get_level_values(0))
910
910
 
@@ -1027,9 +1027,7 @@ class _CommonModel(BaseModel):
1027
1027
  time_factor = how_many / fraction
1028
1028
 
1029
1029
  result = (
1030
- self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1031
- .pct_change(fill_method=cast(str, None))
1032
- .mean()
1030
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change().mean()
1033
1031
  * time_factor
1034
1032
  )
1035
1033
 
@@ -1087,9 +1085,7 @@ class _CommonModel(BaseModel):
1087
1085
  time_factor = how_many / fraction
1088
1086
 
1089
1087
  data = self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1090
- result = (
1091
- data.pct_change(fill_method=cast(str, None)).std().mul(sqrt(time_factor))
1092
- )
1088
+ result = data.pct_change().std().mul(sqrt(time_factor))
1093
1089
 
1094
1090
  if self.tsdf.shape[1] == 1:
1095
1091
  return float(cast(SupportsFloat, result.iloc[0]))
@@ -1285,22 +1281,20 @@ class _CommonModel(BaseModel):
1285
1281
  if drift_adjust:
1286
1282
  imp_vol = (-sqrt(time_factor) / norm.ppf(level)) * (
1287
1283
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1288
- .pct_change(fill_method=cast(str, None))
1284
+ .pct_change()
1289
1285
  .quantile(1 - level, interpolation=interpolation)
1290
1286
  - self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1291
- .pct_change(fill_method=cast(str, None))
1287
+ .pct_change()
1292
1288
  .sum()
1293
1289
  / len(
1294
- self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
1295
- fill_method=cast(str, None),
1296
- ),
1290
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(),
1297
1291
  )
1298
1292
  )
1299
1293
  else:
1300
1294
  imp_vol = (
1301
1295
  -sqrt(time_factor)
1302
1296
  * self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1303
- .pct_change(fill_method=cast(str, None))
1297
+ .pct_change()
1304
1298
  .quantile(1 - level, interpolation=interpolation)
1305
1299
  / norm.ppf(level)
1306
1300
  )
@@ -1363,14 +1357,14 @@ class _CommonModel(BaseModel):
1363
1357
  cvar_df = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy(deep=True)
1364
1358
  result = [
1365
1359
  cvar_df.loc[:, x] # type: ignore[call-overload,index]
1366
- .pct_change(fill_method=cast(str, None))
1360
+ .pct_change()
1367
1361
  .sort_values()
1368
1362
  .iloc[
1369
1363
  : int(
1370
1364
  ceil(
1371
1365
  (1 - level)
1372
1366
  * cvar_df.loc[:, x] # type: ignore[index]
1373
- .pct_change(fill_method=cast(str, None))
1367
+ .pct_change()
1374
1368
  .count(),
1375
1369
  ),
1376
1370
  )
@@ -1430,7 +1424,7 @@ class _CommonModel(BaseModel):
1430
1424
  )
1431
1425
  how_many = (
1432
1426
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1433
- .pct_change(fill_method=cast(str, None))
1427
+ .pct_change()
1434
1428
  .count(numeric_only=True)
1435
1429
  )
1436
1430
  if periods_in_a_year_fixed:
@@ -1445,7 +1439,7 @@ class _CommonModel(BaseModel):
1445
1439
 
1446
1440
  dddf = (
1447
1441
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1448
- .pct_change(fill_method=cast(str, None))
1442
+ .pct_change()
1449
1443
  .sub(min_accepted_return / time_factor)
1450
1444
  )
1451
1445
 
@@ -1548,7 +1542,7 @@ class _CommonModel(BaseModel):
1548
1542
  )
1549
1543
  result: NDArray[float64] = skew(
1550
1544
  a=self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1551
- .pct_change(fill_method=cast(str, None))
1545
+ .pct_change()
1552
1546
  .to_numpy(),
1553
1547
  bias=True,
1554
1548
  nan_policy="omit",
@@ -1595,9 +1589,7 @@ class _CommonModel(BaseModel):
1595
1589
  to_dt=to_date,
1596
1590
  )
1597
1591
  result: NDArray[float64] = kurtosis(
1598
- self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
1599
- fill_method=cast(str, None),
1600
- ),
1592
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(),
1601
1593
  fisher=True,
1602
1594
  bias=True,
1603
1595
  nan_policy="omit",
@@ -1693,19 +1685,13 @@ class _CommonModel(BaseModel):
1693
1685
  )
1694
1686
  pos = (
1695
1687
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1696
- .pct_change(fill_method=cast(str, None))[1:][
1697
- self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
1698
- fill_method=cast(str, None),
1699
- )[1:]
1688
+ .pct_change()[1:][
1689
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()[1:]
1700
1690
  > zero
1701
1691
  ]
1702
1692
  .count()
1703
1693
  )
1704
- tot = (
1705
- self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1706
- .pct_change(fill_method=cast(str, None))[1:]
1707
- .count()
1708
- )
1694
+ tot = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change().count()
1709
1695
  share = pos / tot
1710
1696
  if self.tsdf.shape[1] == 1:
1711
1697
  return float(share.iloc[0])
@@ -1881,9 +1867,7 @@ class _CommonModel(BaseModel):
1881
1867
  from_dt=from_date,
1882
1868
  to_dt=to_date,
1883
1869
  )
1884
- retdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
1885
- fill_method=cast(str, None),
1886
- )
1870
+ retdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()
1887
1871
  pos = retdf[retdf > min_accepted_return].sub(min_accepted_return).sum()
1888
1872
  neg = retdf[retdf < min_accepted_return].sub(min_accepted_return).sum()
1889
1873
  ratio = pos / -neg
@@ -1932,9 +1916,7 @@ class _CommonModel(BaseModel):
1932
1916
  "Simple return cannot be calculated due to "
1933
1917
  f"an initial value being zero. ({self.tsdf.head(3)})"
1934
1918
  )
1935
- raise ValueError(
1936
- msg,
1937
- )
1919
+ raise ValueError(msg)
1938
1920
 
1939
1921
  result = self.tsdf.loc[later] / self.tsdf.loc[earlier] - 1
1940
1922
 
@@ -1973,7 +1955,7 @@ class _CommonModel(BaseModel):
1973
1955
  period = "-".join([str(year), str(month).zfill(2)])
1974
1956
  vrdf = self.tsdf.copy()
1975
1957
  vrdf.index = DatetimeIndex(vrdf.index)
1976
- resultdf = DataFrame(vrdf.pct_change(fill_method=None)) # type: ignore[arg-type]
1958
+ resultdf = DataFrame(vrdf.pct_change())
1977
1959
  result = resultdf.loc[period] + 1
1978
1960
  cal_period = result.cumprod(axis="index").iloc[-1] - 1
1979
1961
  if self.tsdf.shape[1] == 1:
@@ -2025,7 +2007,7 @@ class _CommonModel(BaseModel):
2025
2007
  )
2026
2008
  result = (
2027
2009
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
2028
- .pct_change(fill_method=cast(str, None))
2010
+ .pct_change()
2029
2011
  .quantile(1 - level, interpolation=interpolation)
2030
2012
  )
2031
2013
 
@@ -2073,7 +2055,7 @@ class _CommonModel(BaseModel):
2073
2055
  )
2074
2056
  result = (
2075
2057
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
2076
- .pct_change(fill_method=cast(str, None))
2058
+ .pct_change()
2077
2059
  .rolling(observations, min_periods=observations)
2078
2060
  .sum()
2079
2061
  .min()
@@ -2119,9 +2101,7 @@ class _CommonModel(BaseModel):
2119
2101
  from_dt=from_date,
2120
2102
  to_dt=to_date,
2121
2103
  )
2122
- zscframe = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(
2123
- fill_method=cast(str, None),
2124
- )
2104
+ zscframe = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()
2125
2105
  result = (zscframe.iloc[-1] - zscframe.mean()) / zscframe.std()
2126
2106
 
2127
2107
  if self.tsdf.shape[1] == 1:
@@ -2190,7 +2170,7 @@ class _CommonModel(BaseModel):
2190
2170
  ret_label = cast(tuple[str], self.tsdf.iloc[:, column].name)[0]
2191
2171
  retseries = (
2192
2172
  self.tsdf.iloc[:, column]
2193
- .pct_change(fill_method=cast(str, None))
2173
+ .pct_change()
2194
2174
  .rolling(observations, min_periods=observations)
2195
2175
  .sum()
2196
2176
  )
@@ -2267,7 +2247,7 @@ class _CommonModel(BaseModel):
2267
2247
  else:
2268
2248
  time_factor = self.periods_in_a_year
2269
2249
  vol_label = cast(tuple[str, ValueType], self.tsdf.iloc[:, column].name)[0]
2270
- dframe = self.tsdf.iloc[:, column].pct_change(fill_method=cast(str, None))
2250
+ dframe = self.tsdf.iloc[:, column].pct_change()
2271
2251
  volseries = dframe.rolling(
2272
2252
  observations,
2273
2253
  min_periods=observations,
@@ -92,9 +92,7 @@ def holiday_calendar(
92
92
  "Argument countries must be a string country code or "
93
93
  "a list of string country codes according to ISO 3166-1 alpha-2."
94
94
  )
95
- raise ValueError(
96
- msg,
97
- )
95
+ raise ValueError(msg)
98
96
 
99
97
  return busdaycalendar(holidays=hols)
100
98
 
@@ -115,6 +113,7 @@ def date_fix(
115
113
  Parsed date
116
114
 
117
115
  """
116
+ msg = f"Unknown date format {fixerdate!s} of type {type(fixerdate)!s} encountered"
118
117
  if isinstance(fixerdate, (Timestamp, dt.datetime)):
119
118
  return fixerdate.date()
120
119
  if isinstance(fixerdate, dt.date):
@@ -125,10 +124,7 @@ def date_fix(
125
124
  )
126
125
  if isinstance(fixerdate, str):
127
126
  return dt.datetime.strptime(fixerdate, "%Y-%m-%d").astimezone().date()
128
- msg = f"Unknown date format {fixerdate!s} of type {type(fixerdate)!s} encountered"
129
- raise TypeError(
130
- msg,
131
- )
127
+ raise TypeError(msg)
132
128
 
133
129
 
134
130
  def date_offset_foll(
@@ -340,7 +336,7 @@ def generate_calendar_date_range(
340
336
  )
341
337
  calendar = holiday_calendar(
342
338
  startyear=start.year,
343
- endyear=tmp_range[-1].year,
339
+ endyear=date_fix(tmp_range[-1]).year,
344
340
  countries=countries,
345
341
  )
346
342
  return [
@@ -355,7 +351,7 @@ def generate_calendar_date_range(
355
351
  if end and not start:
356
352
  tmp_range = date_range(end=end, periods=trading_days * 365 // 252, freq="D")
357
353
  calendar = holiday_calendar(
358
- startyear=tmp_range[0].year,
354
+ startyear=date_fix(tmp_range[0]).year,
359
355
  endyear=end.year,
360
356
  countries=countries,
361
357
  )
@@ -372,12 +368,9 @@ def generate_calendar_date_range(
372
368
  "Provide one of start or end date, but not both. "
373
369
  "Date range is inferred from number of trading days."
374
370
  )
375
- raise ValueError(
376
- msg,
377
- )
371
+ raise ValueError(msg)
378
372
 
379
373
 
380
- # noinspection PyUnusedLocal
381
374
  def _do_resample_to_business_period_ends(
382
375
  data: DataFrame,
383
376
  freq: LiteralBizDayFreq,
@@ -427,4 +420,4 @@ def _do_resample_to_business_period_ends(
427
420
  ]
428
421
  + [copydata.index[-1]],
429
422
  )
430
- return dates.drop_duplicates()
423
+ return DatetimeIndex(dates.drop_duplicates())
@@ -13,7 +13,6 @@ if TYPE_CHECKING:
13
13
 
14
14
  import statsmodels.api as sm # type: ignore[import-untyped,unused-ignore]
15
15
  from numpy import (
16
- array,
17
16
  cov,
18
17
  cumprod,
19
18
  divide,
@@ -337,7 +336,7 @@ class OpenFrame(_CommonModel):
337
336
  The returns of the values in the series
338
337
 
339
338
  """
340
- self.tsdf = self.tsdf.pct_change(fill_method=None)
339
+ self.tsdf = self.tsdf.pct_change()
341
340
  self.tsdf.iloc[0] = 0
342
341
  new_labels = [ValueType.RTRN] * self.item_count
343
342
  arrays = [self.tsdf.columns.get_level_values(0), new_labels]
@@ -450,7 +449,8 @@ class OpenFrame(_CommonModel):
450
449
  countries=countries,
451
450
  )
452
451
  xerie.tsdf = xerie.tsdf.reindex(
453
- [deyt.date() for deyt in dates], method=method,
452
+ [deyt.date() for deyt in dates],
453
+ method=method,
454
454
  )
455
455
 
456
456
  self._set_tsdf()
@@ -589,7 +589,7 @@ class OpenFrame(_CommonModel):
589
589
  Correlation matrix
590
590
 
591
591
  """
592
- corr_matrix = self.tsdf.pct_change(fill_method=None).corr(
592
+ corr_matrix = self.tsdf.pct_change().corr(
593
593
  method="pearson",
594
594
  min_periods=1,
595
595
  )
@@ -616,7 +616,6 @@ class OpenFrame(_CommonModel):
616
616
 
617
617
  """
618
618
  self.constituents += [new_series]
619
- # noinspection PyUnreachableCode
620
619
  self.tsdf = concat([self.tsdf, new_series.tsdf], axis="columns", sort=True)
621
620
  return self
622
621
 
@@ -777,6 +776,7 @@ class OpenFrame(_CommonModel):
777
776
  earlier, later = self.calc_range(months_from_last, from_date, to_date)
778
777
  fraction = (later - earlier).days / 365.25
779
778
 
779
+ msg = "base_column should be a tuple[str, ValueType] or an integer."
780
780
  if isinstance(base_column, tuple):
781
781
  shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
782
782
  :,
@@ -798,10 +798,7 @@ class OpenFrame(_CommonModel):
798
798
  ].name
799
799
  short_label = cast(tuple[str, str], self.tsdf.iloc[:, base_column].name)[0]
800
800
  else:
801
- msg = "base_column should be a tuple[str, ValueType] or an integer."
802
- raise TypeError(
803
- msg,
804
- )
801
+ raise TypeError(msg)
805
802
 
806
803
  if periods_in_a_year_fixed:
807
804
  time_factor = float(periods_in_a_year_fixed)
@@ -817,10 +814,9 @@ class OpenFrame(_CommonModel):
817
814
  :,
818
815
  item,
819
816
  ]
820
- # noinspection PyTypeChecker
821
817
  relative = 1.0 + longdf - shortdf
822
818
  vol = float(
823
- relative.pct_change(fill_method=None).std() * sqrt(time_factor),
819
+ relative.pct_change().std() * sqrt(time_factor),
824
820
  )
825
821
  terrors.append(vol)
826
822
 
@@ -870,6 +866,7 @@ class OpenFrame(_CommonModel):
870
866
  earlier, later = self.calc_range(months_from_last, from_date, to_date)
871
867
  fraction = (later - earlier).days / 365.25
872
868
 
869
+ msg = "base_column should be a tuple[str, ValueType] or an integer."
873
870
  if isinstance(base_column, tuple):
874
871
  shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
875
872
  :,
@@ -891,10 +888,7 @@ class OpenFrame(_CommonModel):
891
888
  ].name
892
889
  short_label = cast(tuple[str, str], self.tsdf.iloc[:, base_column].name)[0]
893
890
  else:
894
- msg = "base_column should be a tuple[str, ValueType] or an integer."
895
- raise TypeError(
896
- msg,
897
- )
891
+ raise TypeError(msg)
898
892
 
899
893
  if periods_in_a_year_fixed:
900
894
  time_factor = float(periods_in_a_year_fixed)
@@ -910,13 +904,12 @@ class OpenFrame(_CommonModel):
910
904
  :,
911
905
  item,
912
906
  ]
913
- # noinspection PyTypeChecker
914
907
  relative = 1.0 + longdf - shortdf
915
908
  ret = float(
916
- relative.pct_change(fill_method=None).mean() * time_factor,
909
+ relative.pct_change().mean() * time_factor,
917
910
  )
918
911
  vol = float(
919
- relative.pct_change(fill_method=None).std() * sqrt(time_factor),
912
+ relative.pct_change().std() * sqrt(time_factor),
920
913
  )
921
914
  ratios.append(ret / vol)
922
915
 
@@ -974,6 +967,7 @@ class OpenFrame(_CommonModel):
974
967
  earlier, later = self.calc_range(months_from_last, from_date, to_date)
975
968
  fraction = (later - earlier).days / 365.25
976
969
 
970
+ msg = "base_column should be a tuple[str, ValueType] or an integer."
977
971
  if isinstance(base_column, tuple):
978
972
  shortdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].loc[
979
973
  :,
@@ -995,10 +989,7 @@ class OpenFrame(_CommonModel):
995
989
  ].name
996
990
  short_label = cast(tuple[str, str], self.tsdf.iloc[:, base_column].name)[0]
997
991
  else:
998
- msg = "base_column should be a tuple[str, ValueType] or an integer."
999
- raise TypeError(
1000
- msg,
1001
- )
992
+ raise TypeError(msg)
1002
993
 
1003
994
  if periods_in_a_year_fixed:
1004
995
  time_factor = float(periods_in_a_year_fixed)
@@ -1014,20 +1005,19 @@ class OpenFrame(_CommonModel):
1014
1005
  :,
1015
1006
  item,
1016
1007
  ]
1008
+ msg = "ratio must be one of 'up', 'down' or 'both'."
1017
1009
  if ratio == "up":
1018
1010
  uparray = (
1019
- longdf.pct_change(fill_method=None)[
1020
- shortdf.pct_change(fill_method=None).to_numpy()
1021
- > loss_limit
1011
+ longdf.pct_change()[
1012
+ shortdf.pct_change().to_numpy() > loss_limit
1022
1013
  ]
1023
1014
  .add(1)
1024
1015
  .to_numpy()
1025
1016
  )
1026
1017
  up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
1027
1018
  upidxarray = (
1028
- shortdf.pct_change(fill_method=None)[
1029
- shortdf.pct_change(fill_method=None).to_numpy()
1030
- > loss_limit
1019
+ shortdf.pct_change()[
1020
+ shortdf.pct_change().to_numpy() > loss_limit
1031
1021
  ]
1032
1022
  .add(1)
1033
1023
  .to_numpy()
@@ -1038,9 +1028,8 @@ class OpenFrame(_CommonModel):
1038
1028
  ratios.append(up_rtrn / up_idx_return)
1039
1029
  elif ratio == "down":
1040
1030
  downarray = (
1041
- longdf.pct_change(fill_method=None)[
1042
- shortdf.pct_change(fill_method=None).to_numpy()
1043
- < loss_limit
1031
+ longdf.pct_change()[
1032
+ shortdf.pct_change().to_numpy() < loss_limit
1044
1033
  ]
1045
1034
  .add(1)
1046
1035
  .to_numpy()
@@ -1049,9 +1038,8 @@ class OpenFrame(_CommonModel):
1049
1038
  downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
1050
1039
  )
1051
1040
  downidxarray = (
1052
- shortdf.pct_change(fill_method=None)[
1053
- shortdf.pct_change(fill_method=None).to_numpy()
1054
- < loss_limit
1041
+ shortdf.pct_change()[
1042
+ shortdf.pct_change().to_numpy() < loss_limit
1055
1043
  ]
1056
1044
  .add(1)
1057
1045
  .to_numpy()
@@ -1063,18 +1051,16 @@ class OpenFrame(_CommonModel):
1063
1051
  ratios.append(down_return / down_idx_return)
1064
1052
  elif ratio == "both":
1065
1053
  uparray = (
1066
- longdf.pct_change(fill_method=None)[
1067
- shortdf.pct_change(fill_method=None).to_numpy()
1068
- > loss_limit
1054
+ longdf.pct_change()[
1055
+ shortdf.pct_change().to_numpy() > loss_limit
1069
1056
  ]
1070
1057
  .add(1)
1071
1058
  .to_numpy()
1072
1059
  )
1073
1060
  up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
1074
1061
  upidxarray = (
1075
- shortdf.pct_change(fill_method=None)[
1076
- shortdf.pct_change(fill_method=None).to_numpy()
1077
- > loss_limit
1062
+ shortdf.pct_change()[
1063
+ shortdf.pct_change().to_numpy() > loss_limit
1078
1064
  ]
1079
1065
  .add(1)
1080
1066
  .to_numpy()
@@ -1083,9 +1069,8 @@ class OpenFrame(_CommonModel):
1083
1069
  upidxarray.prod() ** (1 / (len(upidxarray) / time_factor)) - 1
1084
1070
  )
1085
1071
  downarray = (
1086
- longdf.pct_change(fill_method=None)[
1087
- shortdf.pct_change(fill_method=None).to_numpy()
1088
- < loss_limit
1072
+ longdf.pct_change()[
1073
+ shortdf.pct_change().to_numpy() < loss_limit
1089
1074
  ]
1090
1075
  .add(1)
1091
1076
  .to_numpy()
@@ -1094,9 +1079,8 @@ class OpenFrame(_CommonModel):
1094
1079
  downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
1095
1080
  )
1096
1081
  downidxarray = (
1097
- shortdf.pct_change(fill_method=None)[
1098
- shortdf.pct_change(fill_method=None).to_numpy()
1099
- < loss_limit
1082
+ shortdf.pct_change()[
1083
+ shortdf.pct_change().to_numpy() < loss_limit
1100
1084
  ]
1101
1085
  .add(1)
1102
1086
  .to_numpy()
@@ -1109,7 +1093,6 @@ class OpenFrame(_CommonModel):
1109
1093
  (up_rtrn / up_idx_return) / (down_return / down_idx_return),
1110
1094
  )
1111
1095
  else:
1112
- msg = "ratio must be one of 'up', 'down' or 'both'."
1113
1096
  raise ValueError(msg)
1114
1097
 
1115
1098
  if ratio == "up":
@@ -1156,25 +1139,23 @@ class OpenFrame(_CommonModel):
1156
1139
  x_value == ValueType.RTRN
1157
1140
  for x_value in self.tsdf.columns.get_level_values(1).to_numpy()
1158
1141
  ):
1142
+ msg = "asset should be a tuple[str, ValueType] or an integer."
1159
1143
  if isinstance(asset, tuple):
1160
1144
  y_value = self.tsdf.loc[:, asset]
1161
1145
  elif isinstance(asset, int):
1162
1146
  y_value = self.tsdf.iloc[:, asset]
1163
1147
  else:
1164
- msg = "asset should be a tuple[str, ValueType] or an integer."
1165
- raise TypeError(
1166
- msg,
1167
- )
1148
+ raise TypeError(msg)
1149
+
1150
+ msg = "market should be a tuple[str, ValueType] or an integer."
1168
1151
  if isinstance(market, tuple):
1169
1152
  x_value = self.tsdf.loc[:, market]
1170
1153
  elif isinstance(market, int):
1171
1154
  x_value = self.tsdf.iloc[:, market]
1172
1155
  else:
1173
- msg = "market should be a tuple[str, ValueType] or an integer."
1174
- raise TypeError(
1175
- msg,
1176
- )
1156
+ raise TypeError(msg)
1177
1157
  else:
1158
+ msg = "asset should be a tuple[str, ValueType] or an integer."
1178
1159
  if isinstance(asset, tuple):
1179
1160
  y_value = log(
1180
1161
  self.tsdf.loc[:, asset] / self.tsdf.loc[:, asset].iloc[0],
@@ -1184,10 +1165,9 @@ class OpenFrame(_CommonModel):
1184
1165
  self.tsdf.iloc[:, asset] / cast(float, self.tsdf.iloc[0, asset]),
1185
1166
  )
1186
1167
  else:
1187
- msg = "asset should be a tuple[str, ValueType] or an integer."
1188
- raise TypeError(
1189
- msg,
1190
- )
1168
+ raise TypeError(msg)
1169
+
1170
+ msg = "market should be a tuple[str, ValueType] or an integer."
1191
1171
  if isinstance(market, tuple):
1192
1172
  x_value = log(
1193
1173
  self.tsdf.loc[:, market] / self.tsdf.loc[:, market].iloc[0],
@@ -1197,10 +1177,7 @@ class OpenFrame(_CommonModel):
1197
1177
  self.tsdf.iloc[:, market] / cast(float, self.tsdf.iloc[0, market]),
1198
1178
  )
1199
1179
  else:
1200
- msg = "market should be a tuple[str, ValueType] or an integer."
1201
- raise TypeError(
1202
- msg,
1203
- )
1180
+ raise TypeError(msg)
1204
1181
 
1205
1182
  covariance = cov(y_value, x_value, ddof=dlta_degr_freedms)
1206
1183
  beta = covariance[0, 1] / covariance[1, 1]
@@ -1241,6 +1218,7 @@ class OpenFrame(_CommonModel):
1241
1218
  The Statsmodels regression output
1242
1219
 
1243
1220
  """
1221
+ msg = "y_column should be a tuple[str, ValueType] or an integer."
1244
1222
  if isinstance(y_column, tuple):
1245
1223
  y_value = self.tsdf.loc[:, y_column]
1246
1224
  y_label = cast(
@@ -1251,11 +1229,9 @@ class OpenFrame(_CommonModel):
1251
1229
  y_value = self.tsdf.iloc[:, y_column]
1252
1230
  y_label = cast(tuple[str, str], self.tsdf.iloc[:, y_column].name)[0]
1253
1231
  else:
1254
- msg = "y_column should be a tuple[str, ValueType] or an integer."
1255
- raise TypeError(
1256
- msg,
1257
- )
1232
+ raise TypeError(msg)
1258
1233
 
1234
+ msg = "x_column should be a tuple[str, ValueType] or an integer."
1259
1235
  if isinstance(x_column, tuple):
1260
1236
  x_value = self.tsdf.loc[:, x_column]
1261
1237
  x_label = cast(
@@ -1266,10 +1242,7 @@ class OpenFrame(_CommonModel):
1266
1242
  x_value = self.tsdf.iloc[:, x_column]
1267
1243
  x_label = cast(tuple[str, str], self.tsdf.iloc[:, x_column].name)[0]
1268
1244
  else:
1269
- msg = "x_column should be a tuple[str, ValueType] or an integer."
1270
- raise TypeError(
1271
- msg,
1272
- )
1245
+ raise TypeError(msg)
1273
1246
 
1274
1247
  results = sm.OLS(y_value, x_value).fit(method=method, cov_type=cov_type)
1275
1248
  if fitted_series:
@@ -1315,6 +1288,7 @@ class OpenFrame(_CommonModel):
1315
1288
  x == ValueType.RTRN
1316
1289
  for x in self.tsdf.columns.get_level_values(1).to_numpy()
1317
1290
  ):
1291
+ msg = "asset should be a tuple[str, ValueType] or an integer."
1318
1292
  if isinstance(asset, tuple):
1319
1293
  asset_log = self.tsdf.loc[:, asset]
1320
1294
  asset_cagr = asset_log.mean()
@@ -1322,10 +1296,9 @@ class OpenFrame(_CommonModel):
1322
1296
  asset_log = self.tsdf.iloc[:, asset]
1323
1297
  asset_cagr = asset_log.mean()
1324
1298
  else:
1325
- msg = "asset should be a tuple[str, ValueType] or an integer."
1326
- raise TypeError(
1327
- msg,
1328
- )
1299
+ raise TypeError(msg)
1300
+
1301
+ msg = "market should be a tuple[str, ValueType] or an integer."
1329
1302
  if isinstance(market, tuple):
1330
1303
  market_log = self.tsdf.loc[:, market]
1331
1304
  market_cagr = market_log.mean()
@@ -1333,11 +1306,9 @@ class OpenFrame(_CommonModel):
1333
1306
  market_log = self.tsdf.iloc[:, market]
1334
1307
  market_cagr = market_log.mean()
1335
1308
  else:
1336
- msg = "market should be a tuple[str, ValueType] or an integer."
1337
- raise TypeError(
1338
- msg,
1339
- )
1309
+ raise TypeError(msg)
1340
1310
  else:
1311
+ msg = "asset should be a tuple[str, ValueType] or an integer."
1341
1312
  if isinstance(asset, tuple):
1342
1313
  asset_log = log(
1343
1314
  self.tsdf.loc[:, asset] / self.tsdf.loc[:, asset].iloc[0],
@@ -1369,10 +1340,9 @@ class OpenFrame(_CommonModel):
1369
1340
  - 1
1370
1341
  )
1371
1342
  else:
1372
- msg = "asset should be a tuple[str, ValueType] or an integer."
1373
- raise TypeError(
1374
- msg,
1375
- )
1343
+ raise TypeError(msg)
1344
+
1345
+ msg = "market should be a tuple[str, ValueType] or an integer."
1376
1346
  if isinstance(market, tuple):
1377
1347
  market_log = log(
1378
1348
  self.tsdf.loc[:, market] / self.tsdf.loc[:, market].iloc[0],
@@ -1404,10 +1374,7 @@ class OpenFrame(_CommonModel):
1404
1374
  - 1
1405
1375
  )
1406
1376
  else:
1407
- msg = "market should be a tuple[str, ValueType] or an integer."
1408
- raise TypeError(
1409
- msg,
1410
- )
1377
+ raise TypeError(msg)
1411
1378
 
1412
1379
  covariance = cov(asset_log, market_log, ddof=dlta_degr_freedms)
1413
1380
  beta = covariance[0, 1] / covariance[1, 1]
@@ -1439,16 +1406,16 @@ class OpenFrame(_CommonModel):
1439
1406
  "OpenFrame weights property must be provided "
1440
1407
  "to run the make_portfolio method."
1441
1408
  )
1442
- raise ValueError(
1443
- msg,
1444
- )
1409
+ raise ValueError(msg)
1445
1410
  dframe = self.tsdf.copy()
1446
1411
  if not any(
1447
1412
  x == ValueType.RTRN
1448
1413
  for x in self.tsdf.columns.get_level_values(1).to_numpy()
1449
1414
  ):
1450
- dframe = dframe.pct_change(fill_method=None)
1415
+ dframe = dframe.pct_change()
1451
1416
  dframe.iloc[0] = 0
1417
+
1418
+ msg = "Weight strategy not implemented"
1452
1419
  if weight_strat:
1453
1420
  if weight_strat == "eq_weights":
1454
1421
  self.weights = [1.0 / self.item_count] * self.item_count
@@ -1457,10 +1424,10 @@ class OpenFrame(_CommonModel):
1457
1424
  vol[isinf(vol)] = nan
1458
1425
  self.weights = list(divide(vol, vol.sum()))
1459
1426
  else:
1460
- msg = "Weight strategy not implemented"
1461
1427
  raise NotImplementedError(msg)
1428
+
1462
1429
  return DataFrame(
1463
- data=dframe.dot(other=array(self.weights)).add(1.0).cumprod(),
1430
+ data=(dframe @ self.weights).add(1.0).cumprod(),
1464
1431
  index=self.tsdf.index,
1465
1432
  columns=[[name], [ValueType.PRICE]],
1466
1433
  dtype="float64",
@@ -1515,13 +1482,11 @@ class OpenFrame(_CommonModel):
1515
1482
  )
1516
1483
 
1517
1484
  retseries = (
1518
- relative.pct_change(fill_method=None)
1519
- .rolling(observations, min_periods=observations)
1520
- .sum()
1485
+ relative.pct_change().rolling(observations, min_periods=observations).sum()
1521
1486
  )
1522
1487
  retdf = retseries.dropna().to_frame()
1523
1488
 
1524
- voldf = relative.pct_change(fill_method=None).rolling(
1489
+ voldf = relative.pct_change().rolling(
1525
1490
  observations,
1526
1491
  min_periods=observations,
1527
1492
  ).std() * sqrt(time_factor)
@@ -1565,7 +1530,7 @@ class OpenFrame(_CommonModel):
1565
1530
  asset_label = cast(tuple[str, str], self.tsdf.iloc[:, asset_column].name)[0]
1566
1531
  beta_label = f"{asset_label} / {market_label}"
1567
1532
 
1568
- rolling = self.tsdf.pct_change(fill_method=None).rolling(
1533
+ rolling = self.tsdf.pct_change().rolling(
1569
1534
  observations,
1570
1535
  min_periods=observations,
1571
1536
  )
@@ -1622,12 +1587,10 @@ class OpenFrame(_CommonModel):
1622
1587
  )
1623
1588
  first_series = (
1624
1589
  self.tsdf.iloc[:, first_column]
1625
- .pct_change(fill_method=None)[1:]
1590
+ .pct_change()[1:]
1626
1591
  .rolling(observations, min_periods=observations)
1627
1592
  )
1628
- second_series = self.tsdf.iloc[:, second_column].pct_change(
1629
- fill_method=None,
1630
- )[1:]
1593
+ second_series = self.tsdf.iloc[:, second_column].pct_change()[1:]
1631
1594
  corrdf = first_series.corr(other=second_series).dropna().to_frame()
1632
1595
  corrdf.columns = MultiIndex.from_arrays(
1633
1596
  [
@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, Callable, cast
10
10
  from numpy import (
11
11
  append,
12
12
  array,
13
- dot,
14
13
  float64,
15
14
  inf,
16
15
  isnan,
@@ -107,17 +106,13 @@ def simulate_portfolios(
107
106
  all_weights[x, :] = weights
108
107
 
109
108
  vol_arr[x] = sqrt(
110
- dot(
111
- weights.T,
112
- dot(log_ret.cov() * simframe.periods_in_a_year, weights),
113
- ),
109
+ weights.T @ (log_ret.cov() * simframe.periods_in_a_year @ weights),
114
110
  )
115
111
 
116
112
  ret_arr[x] = npsum(log_ret.mean() * weights * simframe.periods_in_a_year)
117
113
 
118
114
  sharpe_arr[x] = ret_arr[x] / vol_arr[x]
119
115
 
120
- # noinspection PyUnreachableCode
121
116
  simdf = concat(
122
117
  [
123
118
  DataFrame({"stdev": vol_arr, "ret": ret_arr, "sharpe": sharpe_arr}),
@@ -198,7 +193,7 @@ def efficient_frontier( # noqa: C901
198
193
  per_in_yr: float,
199
194
  ) -> NDArray[float64]:
200
195
  ret = npsum(lg_ret.mean() * weights) * per_in_yr
201
- volatility = sqrt(dot(weights.T, dot(lg_ret.cov() * per_in_yr, weights)))
196
+ volatility = sqrt(weights.T @ (lg_ret.cov() * per_in_yr @ weights))
202
197
  sr = ret / volatility
203
198
  return cast(NDArray[float64], array([ret, volatility, sr]))
204
199
 
@@ -107,20 +107,20 @@ class OpenTimeSeries(_CommonModel):
107
107
 
108
108
  @field_validator("domestic", mode="before")
109
109
  @classmethod
110
- def validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType:
110
+ def _validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType:
111
111
  """Pydantic validator to ensure domestic field is validated."""
112
112
  _ = Currency(ccy=value)
113
113
  return value
114
114
 
115
115
  @field_validator("countries", mode="before")
116
116
  @classmethod
117
- def validate_countries(cls, value: CountriesType) -> CountriesType:
117
+ def _validate_countries(cls, value: CountriesType) -> CountriesType:
118
118
  """Pydantic validator to ensure countries field is validated."""
119
119
  _ = Countries(countryinput=value)
120
120
  return value
121
121
 
122
122
  @model_validator(mode="after") # type: ignore[misc,unused-ignore]
123
- def dates_and_values_validate(self: Self) -> Self:
123
+ def _dates_and_values_validate(self: Self) -> Self:
124
124
  """Pydantic validator to ensure dates and values are validated."""
125
125
  values_list_length = len(self.values)
126
126
  dates_list_length = len(self.dates)
@@ -205,7 +205,7 @@ class OpenTimeSeries(_CommonModel):
205
205
  @classmethod
206
206
  def from_df(
207
207
  cls: type[OpenTimeSeries],
208
- dframe: DataFrame | Series[float],
208
+ dframe: Series[float] | DataFrame,
209
209
  column_nmbr: int = 0,
210
210
  valuetype: ValueType = ValueType.PRICE,
211
211
  baseccy: CurrencyStringType = "SEK",
@@ -233,6 +233,7 @@ class OpenTimeSeries(_CommonModel):
233
233
  An OpenTimeSeries object
234
234
 
235
235
  """
236
+ msg = "Argument dframe must be pandas Series or DataFrame."
236
237
  if isinstance(dframe, Series):
237
238
  if isinstance(dframe.name, tuple):
238
239
  label, _ = dframe.name
@@ -263,7 +264,6 @@ class OpenTimeSeries(_CommonModel):
263
264
  else:
264
265
  label = cast(MultiIndex, dframe.columns).to_numpy()[column_nmbr]
265
266
  else:
266
- msg = "Argument dframe must be pandas Series or DataFrame."
267
267
  raise TypeError(msg)
268
268
 
269
269
  dates = [date_fix(d).strftime("%Y-%m-%d") for d in dframe.index]
@@ -337,9 +337,7 @@ class OpenTimeSeries(_CommonModel):
337
337
  )
338
338
  elif not isinstance(d_range, DatetimeIndex) and not all([days, end_dt]):
339
339
  msg = "If d_range is not provided both days and end_dt must be."
340
- raise ValueError(
341
- msg,
342
- )
340
+ raise ValueError(msg)
343
341
 
344
342
  deltas = array(
345
343
  [
@@ -436,7 +434,7 @@ class OpenTimeSeries(_CommonModel):
436
434
  The returns of the values in the series
437
435
 
438
436
  """
439
- self.tsdf = self.tsdf.pct_change(fill_method=None) # type: ignore[arg-type]
437
+ self.tsdf = self.tsdf.pct_change()
440
438
  self.tsdf.iloc[0] = 0
441
439
  self.valuetype = ValueType.RTRN
442
440
  self.tsdf.columns = MultiIndex.from_arrays(
@@ -696,7 +694,7 @@ class OpenTimeSeries(_CommonModel):
696
694
  returns_input = True
697
695
  else:
698
696
  values = [cast(float, self.tsdf.iloc[0, 0])]
699
- ra_df = self.tsdf.pct_change(fill_method=None) # type: ignore[arg-type]
697
+ ra_df = self.tsdf.pct_change()
700
698
  returns_input = False
701
699
  ra_df = ra_df.dropna()
702
700
 
@@ -122,10 +122,7 @@ class ReturnSimulation(BaseModel):
122
122
  """
123
123
  return cast(
124
124
  float,
125
- (
126
- self.results.pct_change(fill_method=cast(str, None)).mean()
127
- * self.trading_days_in_year
128
- ).iloc[0],
125
+ (self.results.pct_change().mean() * self.trading_days_in_year).iloc[0],
129
126
  )
130
127
 
131
128
  @property
@@ -140,10 +137,9 @@ class ReturnSimulation(BaseModel):
140
137
  """
141
138
  return cast(
142
139
  float,
143
- (
144
- self.results.pct_change(fill_method=cast(str, None)).std()
145
- * sqrt(self.trading_days_in_year)
146
- ).iloc[0],
140
+ (self.results.pct_change().std() * sqrt(self.trading_days_in_year)).iloc[
141
+ 0
142
+ ],
147
143
  )
148
144
 
149
145
  @classmethod
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import datetime as dt
6
6
  from enum import Enum
7
+ from pprint import pformat
7
8
  from typing import Annotated, ClassVar, Literal, Union
8
9
 
9
10
  from numpy import datetime64
@@ -225,8 +226,8 @@ LiteralFrameProps = Literal[
225
226
  ]
226
227
 
227
228
 
228
- class OpenTimeSeriesPropertiesList(list[str]):
229
- """Allowed property arguments for the OpenTimeSeries class."""
229
+ class PropertiesList(list[str]):
230
+ """Base class for allowed property arguments definition."""
230
231
 
231
232
  allowed_strings: ClassVar[set[str]] = {
232
233
  "value_ret",
@@ -246,9 +247,38 @@ class OpenTimeSeriesPropertiesList(list[str]):
246
247
  "vol_from_var",
247
248
  "worst",
248
249
  "worst_month",
249
- "max_drawdown_cal_year",
250
250
  "max_drawdown",
251
251
  "max_drawdown_date",
252
+ "max_drawdown_cal_year",
253
+ }
254
+
255
+ def _validate(self: Self) -> None:
256
+ """Validate the string input of the all_properties method."""
257
+ seen = set()
258
+ invalids = set()
259
+ duplicates = set()
260
+ msg = ""
261
+ for item in self:
262
+ if item not in self.allowed_strings:
263
+ invalids.add(item)
264
+ if item in seen:
265
+ duplicates.add(item)
266
+ seen.add(item)
267
+ if len(invalids) != 0:
268
+ msg += (
269
+ f"Invalid string(s): {list(invalids)}.\nAllowed strings are:"
270
+ f"\n{pformat(self.allowed_strings)}\n"
271
+ )
272
+ if len(duplicates) != 0:
273
+ msg += f"Duplicate string(s): {list(duplicates)}."
274
+ if len(msg) != 0:
275
+ raise ValueError(msg)
276
+
277
+
278
+ class OpenTimeSeriesPropertiesList(PropertiesList):
279
+ """Allowed property arguments for the OpenTimeSeries class."""
280
+
281
+ allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | {
252
282
  "first_idx",
253
283
  "last_idx",
254
284
  "length",
@@ -265,46 +295,11 @@ class OpenTimeSeriesPropertiesList(list[str]):
265
295
  super().__init__(args)
266
296
  self._validate()
267
297
 
268
- def _validate(self: Self) -> None:
269
- seen = set()
270
- for item in self:
271
- if item not in self.allowed_strings:
272
- msg = (
273
- f"Invalid string: {item}. Allowed strings: {self.allowed_strings}"
274
- )
275
- raise ValueError(
276
- msg,
277
- )
278
- if item in seen:
279
- msg = f"Duplicate string: {item}"
280
- raise ValueError(msg)
281
- seen.add(item)
282
-
283
298
 
284
- class OpenFramePropertiesList(list[str]):
299
+ class OpenFramePropertiesList(PropertiesList):
285
300
  """Allowed property arguments for the OpenFrame class."""
286
301
 
287
- allowed_strings: ClassVar[set[str]] = {
288
- "value_ret",
289
- "geo_ret",
290
- "arithmetic_ret",
291
- "vol",
292
- "downside_deviation",
293
- "ret_vol_ratio",
294
- "sortino_ratio",
295
- "omega_ratio",
296
- "z_score",
297
- "skew",
298
- "kurtosis",
299
- "positive_share",
300
- "var_down",
301
- "cvar_down",
302
- "vol_from_var",
303
- "worst",
304
- "worst_month",
305
- "max_drawdown",
306
- "max_drawdown_date",
307
- "max_drawdown_cal_year",
302
+ allowed_strings: ClassVar[set[str]] = PropertiesList.allowed_strings | {
308
303
  "first_indices",
309
304
  "last_indices",
310
305
  "lengths_of_items",
@@ -316,21 +311,6 @@ class OpenFramePropertiesList(list[str]):
316
311
  super().__init__(args)
317
312
  self._validate()
318
313
 
319
- def _validate(self: Self) -> None:
320
- seen = set()
321
- for item in self:
322
- if item not in self.allowed_strings:
323
- msg = (
324
- f"Invalid string: {item}. Allowed strings: {self.allowed_strings}"
325
- )
326
- raise ValueError(
327
- msg,
328
- )
329
- if item in seen:
330
- msg = f"Duplicate string: {item}"
331
- raise ValueError(msg)
332
- seen.add(item)
333
-
334
314
 
335
315
  class ValueType(str, Enum):
336
316
  """Enum types of OpenTimeSeries to identify the output."""
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "openseries"
3
- version = "1.7.1"
3
+ version = "1.7.3"
4
4
  description = "Tools for analyzing financial timeseries."
5
5
  authors = ["Martin Karrin <martin.karrin@captor.se>"]
6
6
  repository = "https://github.com/CaptorAB/openseries"
@@ -49,11 +49,11 @@ statsmodels = ">=0.14.0,<1.0.0"
49
49
  black = ">=24.4.2,<25.0.0"
50
50
  coverage = ">=7.6.0,<8.0.0"
51
51
  genbadge = {version = ">=1.1.1,<2.0.0", extras = ["coverage"]}
52
- mypy = "^1.11.1"
52
+ mypy = "^1.11.2"
53
53
  pandas-stubs = ">=2.1.2,<3.0.0"
54
54
  pre-commit = ">=3.7.1,<4.0.0"
55
55
  pytest = ">=8.2.2,<9.0.0"
56
- ruff = "^0.5.7"
56
+ ruff = "^0.6.5"
57
57
  types-openpyxl = ">=3.1.2,<4.0.0"
58
58
  types-python-dateutil = ">=2.8.2,<3.0.0"
59
59
  types-requests = ">=2.20.0,<3.0.0"
@@ -79,7 +79,7 @@ output = "coverage.xml"
79
79
  exclude = ["venv/*"]
80
80
  strict = true
81
81
  pretty = true
82
- warn_unreachable = false
82
+ warn_unreachable = true
83
83
  warn_redundant_casts = true
84
84
  warn_unused_ignores = true
85
85
  disallow_any_generics = true
File without changes