openseries 1.9.5__py3-none-any.whl → 1.9.6__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/datefixer.py CHANGED
@@ -144,7 +144,7 @@ def holiday_calendar(
144
144
  for country in countries:
145
145
  staging = country_holidays(country=country, years=years)
146
146
  countryholidays += list(staging)
147
- hols = list(countryholidays)
147
+ hols = cast("list[dt.date]", list(countryholidays))
148
148
  else:
149
149
  msg = (
150
150
  "Argument countries must be a string country code or "
@@ -154,7 +154,9 @@ def holiday_calendar(
154
154
 
155
155
  if markets:
156
156
  market_hols = market_holidays(
157
- startyear=startyear, endyear=endyear, markets=markets
157
+ startyear=startyear,
158
+ endyear=endyear,
159
+ markets=markets,
158
160
  )
159
161
  dt_mkt_hols = [date_fix(fixerdate=ddate) for ddate in market_hols]
160
162
  hols.extend(dt_mkt_hols)
openseries/frame.py CHANGED
@@ -14,14 +14,11 @@ from functools import reduce
14
14
  from logging import getLogger
15
15
  from typing import TYPE_CHECKING, Any, cast
16
16
 
17
- if TYPE_CHECKING: # pragma: no cover
18
- import datetime as dt
19
-
20
- from numpy import dtype, int64, ndarray
21
-
22
17
  from numpy import (
23
18
  array,
19
+ concatenate,
24
20
  cov,
21
+ diff,
25
22
  divide,
26
23
  isinf,
27
24
  log,
@@ -38,6 +35,17 @@ from pandas import (
38
35
  concat,
39
36
  merge,
40
37
  )
38
+
39
+ if TYPE_CHECKING: # pragma: no cover
40
+ import datetime as dt
41
+
42
+ from pandas import Series as _Series
43
+ from pandas import Timestamp
44
+
45
+ SeriesFloat = _Series[float]
46
+ else:
47
+ SeriesFloat = Series
48
+
41
49
  from pydantic import field_validator
42
50
  from sklearn.linear_model import LinearRegression # type: ignore[import-untyped]
43
51
 
@@ -70,7 +78,7 @@ __all__ = ["OpenFrame"]
70
78
 
71
79
 
72
80
  # noinspection PyUnresolvedReferences,PyTypeChecker
73
- class OpenFrame(_CommonModel):
81
+ class OpenFrame(_CommonModel[SeriesFloat]):
74
82
  """OpenFrame objects hold OpenTimeSeries in the list constituents.
75
83
 
76
84
  The intended use is to allow comparisons across these timeseries.
@@ -94,9 +102,9 @@ class OpenFrame(_CommonModel):
94
102
  weights: list[float] | None = None
95
103
 
96
104
  # noinspection PyMethodParameters
97
- @field_validator("constituents") # type: ignore[misc]
105
+ @field_validator("constituents")
98
106
  def _check_labels_unique(
99
- cls: OpenFrame, # noqa: N805
107
+ cls: type[OpenFrame], # noqa: N805
100
108
  tseries: list[OpenTimeSeries],
101
109
  ) -> list[OpenTimeSeries]:
102
110
  """Pydantic validator ensuring that OpenFrame labels are unique."""
@@ -130,7 +138,7 @@ class OpenFrame(_CommonModel):
130
138
  """
131
139
  copied_constituents = [ts.from_deepcopy() for ts in constituents]
132
140
 
133
- super().__init__(
141
+ super().__init__( # type: ignore[call-arg]
134
142
  constituents=copied_constituents,
135
143
  weights=weights,
136
144
  )
@@ -241,7 +249,7 @@ class OpenFrame(_CommonModel):
241
249
 
242
250
  """
243
251
  return Series(
244
- data=[self.tsdf.loc[:, d].count() for d in self.tsdf],
252
+ data=[self.tsdf[col].count() for col in self.tsdf.columns],
245
253
  index=self.tsdf.columns,
246
254
  name="observations",
247
255
  ).astype(int)
@@ -557,12 +565,14 @@ class OpenFrame(_CommonModel):
557
565
 
558
566
  """
559
567
  earlier, later = self.calc_range(
560
- months_offset=months_from_last, from_dt=from_date, to_dt=to_date
568
+ months_offset=months_from_last,
569
+ from_dt=from_date,
570
+ to_dt=to_date,
561
571
  )
562
572
  if periods_in_a_year_fixed is None:
563
573
  fraction = (later - earlier).days / 365.25
564
574
  how_many = (
565
- self.tsdf.loc[cast("int", earlier) : cast("int", later)]
575
+ self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)]
566
576
  .count()
567
577
  .iloc[0]
568
578
  )
@@ -580,33 +590,36 @@ class OpenFrame(_CommonModel):
580
590
  cast("tuple[str, str]", self.tsdf.iloc[:, second_column].name)[0],
581
591
  ]
582
592
 
583
- data = self.tsdf.loc[cast("int", earlier) : cast("int", later)].copy()
593
+ data = self.tsdf.loc[
594
+ cast("Timestamp", earlier) : cast("Timestamp", later)
595
+ ].copy()
584
596
 
585
597
  for rtn in cols:
586
- data[rtn, ValueType.RTRN] = log(data.loc[:, (rtn, ValueType.PRICE)]).diff()
598
+ arr = concatenate([array([nan]), diff(log(data[(rtn, ValueType.PRICE)]))])
599
+ data[rtn, ValueType.RTRN] = arr
587
600
 
588
601
  raw_one = [
589
- data.loc[:, (cols[0], ValueType.RTRN)]
602
+ data[(cols[0], ValueType.RTRN)]
590
603
  .iloc[1:day_chunk]
591
604
  .std(ddof=dlta_degr_freedms)
592
605
  * sqrt(time_factor),
593
606
  ]
594
607
  raw_two = [
595
- data.loc[:, (cols[1], ValueType.RTRN)]
608
+ data[(cols[1], ValueType.RTRN)]
596
609
  .iloc[1:day_chunk]
597
610
  .std(ddof=dlta_degr_freedms)
598
611
  * sqrt(time_factor),
599
612
  ]
600
613
  raw_cov = [
601
614
  cov(
602
- m=data.loc[:, (cols[0], ValueType.RTRN)].iloc[1:day_chunk].to_numpy(),
603
- y=data.loc[:, (cols[1], ValueType.RTRN)].iloc[1:day_chunk].to_numpy(),
615
+ m=data[(cols[0], ValueType.RTRN)].iloc[1:day_chunk].to_numpy(),
616
+ y=data[(cols[1], ValueType.RTRN)].iloc[1:day_chunk].to_numpy(),
604
617
  ddof=dlta_degr_freedms,
605
618
  )[0][1],
606
619
  ]
607
620
 
608
- r1 = data.loc[:, (cols[0], ValueType.RTRN)]
609
- r2 = data.loc[:, (cols[1], ValueType.RTRN)]
621
+ r1 = data[(cols[0], ValueType.RTRN)]
622
+ r2 = data[(cols[1], ValueType.RTRN)]
610
623
 
611
624
  alpha = 1.0 - lmbda
612
625
 
@@ -838,30 +851,29 @@ class OpenFrame(_CommonModel):
838
851
 
839
852
  """
840
853
  earlier, later = self.calc_range(
841
- months_offset=months_from_last, from_dt=from_date, to_dt=to_date
854
+ months_offset=months_from_last,
855
+ from_dt=from_date,
856
+ to_dt=to_date,
842
857
  )
843
- fraction = (later - earlier).days / 365.25
858
+ fraction: float = (later - earlier).days / 365.25
844
859
 
845
860
  msg = "base_column should be a tuple[str, ValueType] or an integer."
846
861
  if isinstance(base_column, tuple):
847
- shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
848
- :,
849
- base_column,
850
- ]
862
+ shortdf = self.tsdf.loc[
863
+ cast("Timestamp", earlier) : cast("Timestamp", later)
864
+ ][base_column]
851
865
  short_item = base_column
852
866
  short_label = cast(
853
867
  "tuple[str, ValueType]",
854
- self.tsdf.loc[:, base_column].name,
868
+ self.tsdf[base_column].name,
855
869
  )[0]
856
870
  elif isinstance(base_column, int):
857
- shortdf = cast(
858
- "DataFrame",
859
- self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
860
- :, base_column
861
- ],
862
- )
871
+ shortdf = self.tsdf.loc[
872
+ cast("Timestamp", earlier) : cast("Timestamp", later)
873
+ ].iloc[:, base_column]
863
874
  short_item = cast(
864
- "tuple[str, ValueType]", self.tsdf.iloc[:, base_column].name
875
+ "tuple[str, ValueType]",
876
+ self.tsdf.iloc[:, base_column].name,
865
877
  )
866
878
  short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
867
879
  0
@@ -872,17 +884,16 @@ class OpenFrame(_CommonModel):
872
884
  if periods_in_a_year_fixed:
873
885
  time_factor = float(periods_in_a_year_fixed)
874
886
  else:
875
- time_factor = float(cast("int64", shortdf.count()) / fraction)
887
+ time_factor = shortdf.count() / fraction
876
888
 
877
889
  terrors = []
878
890
  for item in self.tsdf:
879
891
  if item == short_item:
880
892
  terrors.append(0.0)
881
893
  else:
882
- longdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
883
- :,
884
- item,
885
- ]
894
+ longdf = self.tsdf.loc[
895
+ cast("Timestamp", earlier) : cast("Timestamp", later)
896
+ ][item]
886
897
  relative = longdf.ffill().pct_change() - shortdf.ffill().pct_change()
887
898
  vol = float(relative.std() * sqrt(time_factor))
888
899
  terrors.append(vol)
@@ -931,29 +942,26 @@ class OpenFrame(_CommonModel):
931
942
 
932
943
  """
933
944
  earlier, later = self.calc_range(
934
- months_offset=months_from_last, from_dt=from_date, to_dt=to_date
945
+ months_offset=months_from_last,
946
+ from_dt=from_date,
947
+ to_dt=to_date,
935
948
  )
936
- fraction = (later - earlier).days / 365.25
949
+ fraction: float = (later - earlier).days / 365.25
937
950
 
938
951
  msg = "base_column should be a tuple[str, ValueType] or an integer."
939
952
  if isinstance(base_column, tuple):
940
- shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
941
- :,
942
- base_column,
943
- ]
953
+ shortdf = self.tsdf.loc[
954
+ cast("Timestamp", earlier) : cast("Timestamp", later)
955
+ ][base_column]
944
956
  short_item = base_column
945
957
  short_label = cast(
946
958
  "tuple[str, str]",
947
- self.tsdf.loc[:, base_column].name,
959
+ self.tsdf[base_column].name,
948
960
  )[0]
949
961
  elif isinstance(base_column, int):
950
- shortdf = cast(
951
- "DataFrame",
952
- self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
953
- :,
954
- base_column,
955
- ],
956
- )
962
+ shortdf = self.tsdf.loc[
963
+ cast("Timestamp", earlier) : cast("Timestamp", later)
964
+ ].iloc[:, base_column]
957
965
  short_item = cast(
958
966
  "tuple[str, ValueType]",
959
967
  self.tsdf.iloc[
@@ -970,17 +978,16 @@ class OpenFrame(_CommonModel):
970
978
  if periods_in_a_year_fixed:
971
979
  time_factor = float(periods_in_a_year_fixed)
972
980
  else:
973
- time_factor = float(shortdf.count() / fraction) # type: ignore[arg-type]
981
+ time_factor = shortdf.count() / fraction
974
982
 
975
983
  ratios = []
976
984
  for item in self.tsdf:
977
985
  if item == short_item:
978
986
  ratios.append(0.0)
979
987
  else:
980
- longdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
981
- :,
982
- item,
983
- ]
988
+ longdf = self.tsdf.loc[
989
+ cast("Timestamp", earlier) : cast("Timestamp", later)
990
+ ][item]
984
991
  relative = longdf.ffill().pct_change() - shortdf.ffill().pct_change()
985
992
  ret = float(relative.mean() * time_factor)
986
993
  vol = float(relative.std() * sqrt(time_factor))
@@ -1038,29 +1045,26 @@ class OpenFrame(_CommonModel):
1038
1045
  """
1039
1046
  loss_limit: float = 0.0
1040
1047
  earlier, later = self.calc_range(
1041
- months_offset=months_from_last, from_dt=from_date, to_dt=to_date
1048
+ months_offset=months_from_last,
1049
+ from_dt=from_date,
1050
+ to_dt=to_date,
1042
1051
  )
1043
- fraction = (later - earlier).days / 365.25
1052
+ fraction: float = (later - earlier).days / 365.25
1044
1053
 
1045
1054
  msg = "base_column should be a tuple[str, ValueType] or an integer."
1046
1055
  if isinstance(base_column, tuple):
1047
- shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
1048
- :,
1049
- base_column,
1050
- ]
1056
+ shortdf = self.tsdf.loc[
1057
+ cast("Timestamp", earlier) : cast("Timestamp", later)
1058
+ ][base_column]
1051
1059
  short_item = base_column
1052
1060
  short_label = cast(
1053
1061
  "tuple[str, str]",
1054
- self.tsdf.loc[:, base_column].name,
1062
+ self.tsdf[base_column].name,
1055
1063
  )[0]
1056
1064
  elif isinstance(base_column, int):
1057
- shortdf = cast(
1058
- "DataFrame",
1059
- self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
1060
- :,
1061
- base_column,
1062
- ],
1063
- )
1065
+ shortdf = self.tsdf.loc[
1066
+ cast("Timestamp", earlier) : cast("Timestamp", later)
1067
+ ].iloc[:, base_column]
1064
1068
  short_item = cast(
1065
1069
  "tuple[str, ValueType]",
1066
1070
  self.tsdf.iloc[
@@ -1077,17 +1081,16 @@ class OpenFrame(_CommonModel):
1077
1081
  if periods_in_a_year_fixed:
1078
1082
  time_factor = float(periods_in_a_year_fixed)
1079
1083
  else:
1080
- time_factor = float(shortdf.count() / fraction) # type: ignore[arg-type]
1084
+ time_factor = shortdf.count() / fraction
1081
1085
 
1082
1086
  ratios = []
1083
1087
  for item in self.tsdf:
1084
1088
  if item == short_item:
1085
1089
  ratios.append(0.0)
1086
1090
  else:
1087
- longdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].loc[
1088
- :,
1089
- item,
1090
- ]
1091
+ longdf = self.tsdf.loc[
1092
+ cast("Timestamp", earlier) : cast("Timestamp", later)
1093
+ ][item]
1091
1094
  msg = "ratio must be one of 'up', 'down' or 'both'."
1092
1095
  if ratio == "up":
1093
1096
  uparray = (
@@ -1230,38 +1233,33 @@ class OpenFrame(_CommonModel):
1230
1233
  if all(vtypes):
1231
1234
  msg = "asset should be a tuple[str, ValueType] or an integer."
1232
1235
  if isinstance(asset, tuple):
1233
- y_value = self.tsdf.loc[:, asset]
1236
+ y_value = self.tsdf[asset]
1234
1237
  elif isinstance(asset, int):
1235
- y_value = cast("DataFrame", self.tsdf.iloc[:, asset])
1238
+ y_value = self.tsdf.iloc[:, asset]
1236
1239
  else:
1237
1240
  raise TypeError(msg)
1238
1241
 
1239
1242
  msg = "market should be a tuple[str, ValueType] or an integer."
1240
1243
  if isinstance(market, tuple):
1241
- x_value = self.tsdf.loc[:, market]
1244
+ x_value = self.tsdf[market]
1242
1245
  elif isinstance(market, int):
1243
- x_value = cast("DataFrame", self.tsdf.iloc[:, market])
1246
+ x_value = self.tsdf.iloc[:, market]
1244
1247
  else:
1245
1248
  raise TypeError(msg)
1246
1249
  elif not any(vtypes):
1247
1250
  msg = "asset should be a tuple[str, ValueType] or an integer."
1248
1251
  if isinstance(asset, tuple):
1249
- y_value = self.tsdf.loc[:, asset].ffill().pct_change().iloc[1:]
1252
+ y_value = self.tsdf[asset].ffill().pct_change().iloc[1:]
1250
1253
  elif isinstance(asset, int):
1251
- y_value = cast(
1252
- "DataFrame", self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
1253
- )
1254
+ y_value = self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
1254
1255
  else:
1255
1256
  raise TypeError(msg)
1256
1257
  msg = "market should be a tuple[str, ValueType] or an integer."
1257
1258
 
1258
1259
  if isinstance(market, tuple):
1259
- x_value = self.tsdf.loc[:, market].ffill().pct_change().iloc[1:]
1260
+ x_value = self.tsdf[market].ffill().pct_change().iloc[1:]
1260
1261
  elif isinstance(market, int):
1261
- x_value = cast(
1262
- "DataFrame",
1263
- self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:],
1264
- )
1262
+ x_value = self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:]
1265
1263
  else:
1266
1264
  raise TypeError(msg)
1267
1265
  else:
@@ -1302,26 +1300,23 @@ class OpenFrame(_CommonModel):
1302
1300
  """
1303
1301
  msg = "y_column should be a tuple[str, ValueType] or an integer."
1304
1302
  if isinstance(y_column, tuple):
1305
- y_value = self.tsdf.loc[:, y_column].to_numpy()
1303
+ y_value = self.tsdf[y_column].to_numpy()
1306
1304
  y_label = cast(
1307
1305
  "tuple[str, str]",
1308
- self.tsdf.loc[:, y_column].name,
1306
+ self.tsdf[y_column].name,
1309
1307
  )[0]
1310
1308
  elif isinstance(y_column, int):
1311
- y_value = cast(
1312
- "ndarray[tuple[int, int], dtype[Any]]",
1313
- self.tsdf.iloc[:, y_column].to_numpy(),
1314
- )
1309
+ y_value = self.tsdf.iloc[:, y_column].to_numpy()
1315
1310
  y_label = cast("tuple[str, str]", self.tsdf.iloc[:, y_column].name)[0]
1316
1311
  else:
1317
1312
  raise TypeError(msg)
1318
1313
 
1319
1314
  msg = "x_column should be a tuple[str, ValueType] or an integer."
1320
1315
  if isinstance(x_column, tuple):
1321
- x_value = self.tsdf.loc[:, x_column].to_numpy().reshape(-1, 1)
1316
+ x_value = self.tsdf[x_column].to_numpy().reshape(-1, 1)
1322
1317
  x_label = cast(
1323
1318
  "tuple[str, str]",
1324
- self.tsdf.loc[:, x_column].name,
1319
+ self.tsdf[x_column].name,
1325
1320
  )[0]
1326
1321
  elif isinstance(x_column, int):
1327
1322
  x_value = self.tsdf.iloc[:, x_column].to_numpy().reshape(-1, 1)
@@ -1376,45 +1371,40 @@ class OpenFrame(_CommonModel):
1376
1371
  if not any(vtypes):
1377
1372
  msg = "asset should be a tuple[str, ValueType] or an integer."
1378
1373
  if isinstance(asset, tuple):
1379
- asset_rtn = self.tsdf.loc[:, asset].ffill().pct_change().iloc[1:]
1374
+ asset_rtn = self.tsdf[asset].ffill().pct_change().iloc[1:]
1380
1375
  asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
1381
1376
  elif isinstance(asset, int):
1382
- asset_rtn = cast(
1383
- "DataFrame", self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
1384
- )
1377
+ asset_rtn = self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
1385
1378
  asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
1386
1379
  else:
1387
1380
  raise TypeError(msg)
1388
1381
 
1389
1382
  msg = "market should be a tuple[str, ValueType] or an integer."
1390
1383
  if isinstance(market, tuple):
1391
- market_rtn = self.tsdf.loc[:, market].ffill().pct_change().iloc[1:]
1384
+ market_rtn = self.tsdf[market].ffill().pct_change().iloc[1:]
1392
1385
  market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
1393
1386
  elif isinstance(market, int):
1394
- market_rtn = cast(
1395
- "DataFrame",
1396
- self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:],
1397
- )
1387
+ market_rtn = self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:]
1398
1388
  market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
1399
1389
  else:
1400
1390
  raise TypeError(msg)
1401
1391
  elif all(vtypes):
1402
1392
  msg = "asset should be a tuple[str, ValueType] or an integer."
1403
1393
  if isinstance(asset, tuple):
1404
- asset_rtn = self.tsdf.loc[:, asset]
1394
+ asset_rtn = self.tsdf[asset]
1405
1395
  asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
1406
1396
  elif isinstance(asset, int):
1407
- asset_rtn = cast("DataFrame", self.tsdf.iloc[:, asset])
1397
+ asset_rtn = self.tsdf.iloc[:, asset]
1408
1398
  asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
1409
1399
  else:
1410
1400
  raise TypeError(msg)
1411
1401
 
1412
1402
  msg = "market should be a tuple[str, ValueType] or an integer."
1413
1403
  if isinstance(market, tuple):
1414
- market_rtn = self.tsdf.loc[:, market]
1404
+ market_rtn = self.tsdf[market]
1415
1405
  market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
1416
1406
  elif isinstance(market, int):
1417
- market_rtn = cast("DataFrame", self.tsdf.iloc[:, market])
1407
+ market_rtn = self.tsdf.iloc[:, market]
1418
1408
  market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
1419
1409
  else:
1420
1410
  raise TypeError(msg)
@@ -1426,7 +1416,7 @@ class OpenFrame(_CommonModel):
1426
1416
  beta = covariance[0, 1] / covariance[1, 1]
1427
1417
 
1428
1418
  return float(
1429
- asset_rtn_mean - riskfree_rate - beta * (market_rtn_mean - riskfree_rate)
1419
+ asset_rtn_mean - riskfree_rate - beta * (market_rtn_mean - riskfree_rate),
1430
1420
  )
1431
1421
 
1432
1422
  def make_portfolio(
@@ -1612,7 +1602,7 @@ class OpenFrame(_CommonModel):
1612
1602
  rollbeta.index = rollbeta.index.droplevel(level=1)
1613
1603
  rollbeta.columns = MultiIndex.from_arrays([[beta_label], ["Beta"]])
1614
1604
 
1615
- return cast("DataFrame", rollbeta)
1605
+ return rollbeta
1616
1606
 
1617
1607
  def rolling_corr(
1618
1608
  self: Self,
openseries/owntypes.py CHANGED
@@ -12,14 +12,22 @@ from __future__ import annotations
12
12
  import datetime as dt
13
13
  from enum import Enum
14
14
  from pprint import pformat
15
- from typing import Annotated, ClassVar, Literal, Union
15
+ from typing import TYPE_CHECKING, Annotated, ClassVar, Literal, TypeAlias, TypeVar
16
16
 
17
+ from annotated_types import MinLen
17
18
  from numpy import datetime64
18
- from pandas import Timestamp
19
- from pydantic import BaseModel, Field, StringConstraints, conlist, conset
19
+ from pandas import Series, Timestamp
20
+ from pydantic import BaseModel, Field, StringConstraints
21
+
22
+ if TYPE_CHECKING:
23
+ from pandas import Series as _Series
24
+
25
+ SeriesFloat = _Series[float]
26
+ else:
27
+ SeriesFloat = Series
20
28
 
21
29
  try:
22
- from typing import Self # type: ignore[attr-defined,unused-ignore]
30
+ from typing import Self
23
31
  except ImportError: # pragma: no cover
24
32
  from typing_extensions import Self
25
33
 
@@ -27,6 +35,9 @@ except ImportError: # pragma: no cover
27
35
  __all__ = ["Self", "ValueType"]
28
36
 
29
37
 
38
+ Combo_co = TypeVar("Combo_co", float, SeriesFloat, covariant=True)
39
+
40
+
30
41
  CountryStringType = Annotated[
31
42
  str,
32
43
  StringConstraints(
@@ -38,14 +49,11 @@ CountryStringType = Annotated[
38
49
  strict=True,
39
50
  ),
40
51
  ]
41
- CountryListType = conset(
42
- item_type=CountryStringType,
43
- min_length=1,
44
- )
45
- CountriesType = Union[CountryListType, CountryStringType] # type: ignore[valid-type] # noqa: UP007
52
+ CountrySetType: TypeAlias = Annotated[set[CountryStringType], MinLen(1)]
53
+ CountriesType: TypeAlias = CountrySetType | CountryStringType
46
54
 
47
55
 
48
- class Countries(BaseModel): # type: ignore[misc]
56
+ class Countries(BaseModel):
49
57
  """Declare Countries."""
50
58
 
51
59
  countryinput: CountriesType
@@ -64,69 +72,62 @@ CurrencyStringType = Annotated[
64
72
  ]
65
73
 
66
74
 
67
- class Currency(BaseModel): # type: ignore[misc]
75
+ class Currency(BaseModel):
68
76
  """Declare Currency."""
69
77
 
70
78
  ccy: CurrencyStringType
71
79
 
72
80
 
73
- DateListType = Annotated[
74
- list[str],
75
- conset(
76
- Annotated[
77
- str,
78
- StringConstraints(
79
- pattern=r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$",
80
- strip_whitespace=True,
81
- strict=True,
82
- min_length=10,
83
- max_length=10,
84
- ),
85
- ],
86
- min_length=1,
81
+ DateStringType = Annotated[
82
+ str,
83
+ StringConstraints(
84
+ pattern=r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$",
85
+ strip_whitespace=True,
86
+ strict=True,
87
+ min_length=10,
88
+ max_length=10,
87
89
  ),
88
90
  ]
91
+ DateListType: TypeAlias = Annotated[list[DateStringType], MinLen(1)]
89
92
 
90
- ValueListType = Annotated[list[float], conlist(float, min_length=1)]
93
+ ValueListType: TypeAlias = Annotated[list[float], MinLen(1)]
91
94
 
92
95
  DaysInYearType = Annotated[int, Field(strict=True, ge=1, le=366)]
93
96
 
94
97
  DateType = str | dt.date | dt.datetime | datetime64 | Timestamp
95
98
 
96
- PlotlyLayoutType = dict[
97
- str,
99
+ PlotlyConfigType = (
98
100
  str
99
101
  | int
100
102
  | float
101
103
  | bool
102
104
  | list[str]
103
- | dict[str, str | int | float | bool | list[str]],
104
- ]
105
+ | dict[str, str | int | float | bool | list[str]]
106
+ )
107
+
108
+ PlotlyLayoutType = dict[str, PlotlyConfigType]
105
109
 
106
110
  CaptorLogoType = dict[str, str | float]
107
111
 
108
112
  LiteralJsonOutput = Literal["values", "tsdf"]
109
113
  LiteralTrunc = Literal["before", "after", "both"]
110
- LiteralLinePlotMode = Literal[
111
- "lines",
112
- "markers",
113
- "lines+markers",
114
- "lines+text",
115
- "markers+text",
116
- "lines+markers+text",
117
- None,
118
- ]
114
+ LiteralLinePlotMode = (
115
+ Literal[
116
+ "lines",
117
+ "markers",
118
+ "lines+markers",
119
+ "lines+text",
120
+ "markers+text",
121
+ "lines+markers+text",
122
+ ]
123
+ | None
124
+ )
119
125
  LiteralHowMerge = Literal["outer", "inner"]
120
126
  LiteralQuantileInterp = Literal["linear", "lower", "higher", "midpoint", "nearest"]
121
127
  LiteralBizDayFreq = Literal["B", "BME", "BQE", "BYE"]
122
- LiteralPandasReindexMethod = Literal[
123
- None,
124
- "pad",
125
- "ffill",
126
- "backfill",
127
- "bfill",
128
- "nearest",
129
- ]
128
+ LiteralPandasReindexMethod = (
129
+ Literal["pad", "ffill", "backfill", "bfill", "nearest"] | None
130
+ )
130
131
  LiteralNanMethod = Literal["fill", "drop"]
131
132
  LiteralCaptureRatio = Literal["up", "down", "both"]
132
133
  LiteralBarPlotMode = Literal["stack", "group", "overlay", "relative"]
@@ -366,7 +366,7 @@ def constrain_optimized_portfolios(
366
366
  if not bounds:
367
367
  bounds = tuple((0.0, 1.0) for _ in range(data.item_count))
368
368
 
369
- front_frame, sim_frame, optimal = efficient_frontier(
369
+ front_frame, _, _ = efficient_frontier(
370
370
  eframe=data,
371
371
  num_ports=simulations,
372
372
  frontier_points=curve_points,
@@ -440,14 +440,13 @@ def prepare_plot_data(
440
440
  for wgt, nm in zip(optimized[3:], assets.columns_lvl_zero, strict=True)
441
441
  ]
442
442
  opt_text = "<br><br>Weights:<br>" + "<br>".join(opt_text_list)
443
- vol = cast("Series[float]", assets.vol)
444
443
  plotframe = DataFrame(
445
444
  data=[
446
445
  assets.arithmetic_ret,
447
- vol,
446
+ assets.vol,
448
447
  Series(
449
448
  data=[""] * assets.item_count,
450
- index=vol.index,
449
+ index=assets.vol.index,
451
450
  ),
452
451
  ],
453
452
  index=["ret", "stdev", "text"],