openseries 1.9.4__tar.gz → 1.9.5__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.9.4
3
+ Version: 1.9.5
4
4
  Summary: Tools for analyzing financial timeseries.
5
5
  License: # BSD 3-Clause License
6
6
 
@@ -7,7 +7,6 @@ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
7
  SPDX-License-Identifier: BSD-3-Clause
8
8
  """
9
9
 
10
- # mypy: disable-error-code="no-any-return"
11
10
  from __future__ import annotations
12
11
 
13
12
  import datetime as dt
@@ -67,7 +66,7 @@ from plotly.graph_objs import Figure # type: ignore[import-untyped]
67
66
  from plotly.io import to_html # type: ignore[import-untyped]
68
67
  from plotly.offline import plot # type: ignore[import-untyped]
69
68
  from pydantic import BaseModel, ConfigDict, DirectoryPath, ValidationError
70
- from scipy.stats import ( # type: ignore[import-untyped]
69
+ from scipy.stats import (
71
70
  kurtosis,
72
71
  norm,
73
72
  skew,
@@ -1658,17 +1657,16 @@ class _CommonModel(BaseModel): # type: ignore[misc]
1658
1657
  deep=True
1659
1658
  )
1660
1659
  result = [
1661
- cvar_df.loc[:, x] # type: ignore[call-overload,index]
1660
+ cvar_df.loc[:, x] # type: ignore[call-overload]
1662
1661
  .ffill()
1663
1662
  .pct_change()
1664
1663
  .sort_values()
1665
1664
  .iloc[
1666
1665
  : ceil(
1667
- (1 - level)
1668
- * cvar_df.loc[:, x] # type: ignore[index]
1669
- .ffill()
1670
- .pct_change()
1671
- .count(),
1666
+ cast(
1667
+ "int",
1668
+ (1 - level) * cvar_df.loc[:, x].ffill().pct_change().count(),
1669
+ )
1672
1670
  ),
1673
1671
  ]
1674
1672
  .mean()
@@ -1807,12 +1805,7 @@ class _CommonModel(BaseModel): # type: ignore[misc]
1807
1805
  )
1808
1806
  fraction = (later - earlier).days / 365.25
1809
1807
 
1810
- any_below_zero = any(
1811
- self.tsdf.loc[[earlier, later]] # type: ignore[index]
1812
- .lt(0.0)
1813
- .any()
1814
- .to_numpy()
1815
- )
1808
+ any_below_zero = any(self.tsdf.loc[[earlier, later]].lt(0.0).any().to_numpy())
1816
1809
  if zero in self.tsdf.loc[earlier].to_numpy() or any_below_zero:
1817
1810
  msg = (
1818
1811
  "Geometric return cannot be calculated due to "
@@ -12,7 +12,7 @@ from __future__ import annotations
12
12
  import datetime as dt
13
13
  from typing import TYPE_CHECKING, cast
14
14
 
15
- import exchange_calendars as exchcal
15
+ import exchange_calendars as exchcal # type: ignore[import-untyped]
16
16
  from dateutil.relativedelta import relativedelta
17
17
  from holidays import (
18
18
  country_holidays,
@@ -163,7 +163,7 @@ def holiday_calendar(
163
163
  custom_list = (
164
164
  [custom_holidays]
165
165
  if isinstance(custom_holidays, str)
166
- else list(custom_holidays) # type: ignore[arg-type]
166
+ else list(custom_holidays)
167
167
  )
168
168
  hols.extend([date_fix(fixerdate=ddate) for ddate in custom_list])
169
169
 
@@ -255,7 +255,7 @@ def date_offset_foll(
255
255
  while not is_busday(dates=new_date, busdaycal=calendar):
256
256
  new_date += day_delta
257
257
 
258
- return new_date # type: ignore[no-any-return]
258
+ return new_date
259
259
 
260
260
 
261
261
  def get_previous_business_day_before_today(
@@ -505,7 +505,7 @@ def _do_resample_to_business_period_ends(
505
505
  dates = DatetimeIndex(
506
506
  [copydata.index[0]]
507
507
  + [
508
- date_offset_foll( # type: ignore[misc]
508
+ date_offset_foll(
509
509
  raw_date=dt.date(d.year, d.month, 1)
510
510
  + relativedelta(months=1)
511
511
  - dt.timedelta(days=1),
@@ -7,7 +7,6 @@ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
7
  SPDX-License-Identifier: BSD-3-Clause
8
8
  """
9
9
 
10
- # mypy: disable-error-code="assignment,no-any-return"
11
10
  from __future__ import annotations
12
11
 
13
12
  from copy import deepcopy
@@ -18,6 +17,8 @@ from typing import TYPE_CHECKING, Any, cast
18
17
  if TYPE_CHECKING: # pragma: no cover
19
18
  import datetime as dt
20
19
 
20
+ from numpy import dtype, int64, ndarray
21
+
21
22
  from numpy import (
22
23
  array,
23
24
  cov,
@@ -38,7 +39,7 @@ from pandas import (
38
39
  merge,
39
40
  )
40
41
  from pydantic import field_validator
41
- from sklearn.linear_model import LinearRegression
42
+ from sklearn.linear_model import LinearRegression # type: ignore[import-untyped]
42
43
 
43
44
  from ._common_model import _CommonModel
44
45
  from .datefixer import _do_resample_to_business_period_ends
@@ -69,7 +70,7 @@ __all__ = ["OpenFrame"]
69
70
 
70
71
 
71
72
  # noinspection PyUnresolvedReferences,PyTypeChecker
72
- class OpenFrame(_CommonModel): # type: ignore[misc]
73
+ class OpenFrame(_CommonModel):
73
74
  """OpenFrame objects hold OpenTimeSeries in the list constituents.
74
75
 
75
76
  The intended use is to allow comparisons across these timeseries.
@@ -94,7 +95,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
94
95
 
95
96
  # noinspection PyMethodParameters
96
97
  @field_validator("constituents") # type: ignore[misc]
97
- def _check_labels_unique( # type: ignore[misc]
98
+ def _check_labels_unique(
98
99
  cls: OpenFrame, # noqa: N805
99
100
  tseries: list[OpenTimeSeries],
100
101
  ) -> list[OpenTimeSeries]:
@@ -129,7 +130,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
129
130
  """
130
131
  copied_constituents = [ts.from_deepcopy() for ts in constituents]
131
132
 
132
- super().__init__( # type: ignore[call-arg]
133
+ super().__init__(
133
134
  constituents=copied_constituents,
134
135
  weights=weights,
135
136
  )
@@ -853,14 +854,15 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
853
854
  self.tsdf.loc[:, base_column].name,
854
855
  )[0]
855
856
  elif isinstance(base_column, int):
856
- shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
857
- :,
858
- base_column,
859
- ]
860
- short_item = self.tsdf.iloc[
861
- :,
862
- base_column,
863
- ].name
857
+ shortdf = cast(
858
+ "DataFrame",
859
+ self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
860
+ :, base_column
861
+ ],
862
+ )
863
+ short_item = cast(
864
+ "tuple[str, ValueType]", self.tsdf.iloc[:, base_column].name
865
+ )
864
866
  short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
865
867
  0
866
868
  ]
@@ -870,7 +872,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
870
872
  if periods_in_a_year_fixed:
871
873
  time_factor = float(periods_in_a_year_fixed)
872
874
  else:
873
- time_factor = float(shortdf.count() / fraction)
875
+ time_factor = float(cast("int64", shortdf.count()) / fraction)
874
876
 
875
877
  terrors = []
876
878
  for item in self.tsdf:
@@ -945,14 +947,20 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
945
947
  self.tsdf.loc[:, base_column].name,
946
948
  )[0]
947
949
  elif isinstance(base_column, int):
948
- shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
949
- :,
950
- base_column,
951
- ]
952
- short_item = self.tsdf.iloc[
953
- :,
954
- base_column,
955
- ].name
950
+ shortdf = cast(
951
+ "DataFrame",
952
+ self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
953
+ :,
954
+ base_column,
955
+ ],
956
+ )
957
+ short_item = cast(
958
+ "tuple[str, ValueType]",
959
+ self.tsdf.iloc[
960
+ :,
961
+ base_column,
962
+ ].name,
963
+ )
956
964
  short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
957
965
  0
958
966
  ]
@@ -962,7 +970,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
962
970
  if periods_in_a_year_fixed:
963
971
  time_factor = float(periods_in_a_year_fixed)
964
972
  else:
965
- time_factor = float(shortdf.count() / fraction)
973
+ time_factor = float(shortdf.count() / fraction) # type: ignore[arg-type]
966
974
 
967
975
  ratios = []
968
976
  for item in self.tsdf:
@@ -1046,14 +1054,20 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1046
1054
  self.tsdf.loc[:, base_column].name,
1047
1055
  )[0]
1048
1056
  elif isinstance(base_column, int):
1049
- shortdf = self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
1050
- :,
1051
- base_column,
1052
- ]
1053
- short_item = self.tsdf.iloc[
1054
- :,
1055
- base_column,
1056
- ].name
1057
+ shortdf = cast(
1058
+ "DataFrame",
1059
+ self.tsdf.loc[cast("int", earlier) : cast("int", later)].iloc[
1060
+ :,
1061
+ base_column,
1062
+ ],
1063
+ )
1064
+ short_item = cast(
1065
+ "tuple[str, ValueType]",
1066
+ self.tsdf.iloc[
1067
+ :,
1068
+ base_column,
1069
+ ].name,
1070
+ )
1057
1071
  short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
1058
1072
  0
1059
1073
  ]
@@ -1063,7 +1077,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1063
1077
  if periods_in_a_year_fixed:
1064
1078
  time_factor = float(periods_in_a_year_fixed)
1065
1079
  else:
1066
- time_factor = float(shortdf.count() / fraction)
1080
+ time_factor = float(shortdf.count() / fraction) # type: ignore[arg-type]
1067
1081
 
1068
1082
  ratios = []
1069
1083
  for item in self.tsdf:
@@ -1218,7 +1232,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1218
1232
  if isinstance(asset, tuple):
1219
1233
  y_value = self.tsdf.loc[:, asset]
1220
1234
  elif isinstance(asset, int):
1221
- y_value = self.tsdf.iloc[:, asset]
1235
+ y_value = cast("DataFrame", self.tsdf.iloc[:, asset])
1222
1236
  else:
1223
1237
  raise TypeError(msg)
1224
1238
 
@@ -1226,7 +1240,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1226
1240
  if isinstance(market, tuple):
1227
1241
  x_value = self.tsdf.loc[:, market]
1228
1242
  elif isinstance(market, int):
1229
- x_value = self.tsdf.iloc[:, market]
1243
+ x_value = cast("DataFrame", self.tsdf.iloc[:, market])
1230
1244
  else:
1231
1245
  raise TypeError(msg)
1232
1246
  elif not any(vtypes):
@@ -1234,7 +1248,9 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1234
1248
  if isinstance(asset, tuple):
1235
1249
  y_value = self.tsdf.loc[:, asset].ffill().pct_change().iloc[1:]
1236
1250
  elif isinstance(asset, int):
1237
- y_value = self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
1251
+ y_value = cast(
1252
+ "DataFrame", self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
1253
+ )
1238
1254
  else:
1239
1255
  raise TypeError(msg)
1240
1256
  msg = "market should be a tuple[str, ValueType] or an integer."
@@ -1242,7 +1258,10 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1242
1258
  if isinstance(market, tuple):
1243
1259
  x_value = self.tsdf.loc[:, market].ffill().pct_change().iloc[1:]
1244
1260
  elif isinstance(market, int):
1245
- x_value = self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:]
1261
+ x_value = cast(
1262
+ "DataFrame",
1263
+ self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:],
1264
+ )
1246
1265
  else:
1247
1266
  raise TypeError(msg)
1248
1267
  else:
@@ -1289,7 +1308,10 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1289
1308
  self.tsdf.loc[:, y_column].name,
1290
1309
  )[0]
1291
1310
  elif isinstance(y_column, int):
1292
- y_value = self.tsdf.iloc[:, y_column].to_numpy()
1311
+ y_value = cast(
1312
+ "ndarray[tuple[int, int], dtype[Any]]",
1313
+ self.tsdf.iloc[:, y_column].to_numpy(),
1314
+ )
1293
1315
  y_label = cast("tuple[str, str]", self.tsdf.iloc[:, y_column].name)[0]
1294
1316
  else:
1295
1317
  raise TypeError(msg)
@@ -1357,7 +1379,9 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1357
1379
  asset_rtn = self.tsdf.loc[:, asset].ffill().pct_change().iloc[1:]
1358
1380
  asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
1359
1381
  elif isinstance(asset, int):
1360
- asset_rtn = self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
1382
+ asset_rtn = cast(
1383
+ "DataFrame", self.tsdf.iloc[:, asset].ffill().pct_change().iloc[1:]
1384
+ )
1361
1385
  asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
1362
1386
  else:
1363
1387
  raise TypeError(msg)
@@ -1367,7 +1391,10 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1367
1391
  market_rtn = self.tsdf.loc[:, market].ffill().pct_change().iloc[1:]
1368
1392
  market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
1369
1393
  elif isinstance(market, int):
1370
- market_rtn = self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:]
1394
+ market_rtn = cast(
1395
+ "DataFrame",
1396
+ self.tsdf.iloc[:, market].ffill().pct_change().iloc[1:],
1397
+ )
1371
1398
  market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
1372
1399
  else:
1373
1400
  raise TypeError(msg)
@@ -1377,7 +1404,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1377
1404
  asset_rtn = self.tsdf.loc[:, asset]
1378
1405
  asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
1379
1406
  elif isinstance(asset, int):
1380
- asset_rtn = self.tsdf.iloc[:, asset]
1407
+ asset_rtn = cast("DataFrame", self.tsdf.iloc[:, asset])
1381
1408
  asset_rtn_mean = float(asset_rtn.mean() * self.periods_in_a_year)
1382
1409
  else:
1383
1410
  raise TypeError(msg)
@@ -1387,7 +1414,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1387
1414
  market_rtn = self.tsdf.loc[:, market]
1388
1415
  market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
1389
1416
  elif isinstance(market, int):
1390
- market_rtn = self.tsdf.iloc[:, market]
1417
+ market_rtn = cast("DataFrame", self.tsdf.iloc[:, market])
1391
1418
  market_rtn_mean = float(market_rtn.mean() * self.periods_in_a_year)
1392
1419
  else:
1393
1420
  raise TypeError(msg)
@@ -1585,7 +1612,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1585
1612
  rollbeta.index = rollbeta.index.droplevel(level=1)
1586
1613
  rollbeta.columns = MultiIndex.from_arrays([[beta_label], ["Beta"]])
1587
1614
 
1588
- return rollbeta
1615
+ return cast("DataFrame", rollbeta)
1589
1616
 
1590
1617
  def rolling_corr(
1591
1618
  self: Self,
@@ -16,7 +16,7 @@
16
16
  ],
17
17
  "responsive": true,
18
18
  "toImageButtonOptions": {
19
- "filename": "captor_plot_image",
19
+ "filename": "openseries_plot",
20
20
  "format": "png"
21
21
  }
22
22
  },
@@ -7,7 +7,6 @@ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
7
  SPDX-License-Identifier: BSD-3-Clause
8
8
  """
9
9
 
10
- # mypy: disable-error-code="assignment"
11
10
  from __future__ import annotations
12
11
 
13
12
  from inspect import stack
@@ -36,7 +35,7 @@ from pandas import (
36
35
  from plotly.graph_objs import Figure # type: ignore[import-untyped]
37
36
  from plotly.io import to_html # type: ignore[import-untyped]
38
37
  from plotly.offline import plot # type: ignore[import-untyped]
39
- from scipy.optimize import minimize # type: ignore[import-untyped]
38
+ from scipy.optimize import minimize
40
39
 
41
40
  from .load_plotly import load_plotly_dict
42
41
  from .owntypes import (
@@ -143,7 +142,7 @@ def efficient_frontier(
143
142
  eframe: OpenFrame,
144
143
  num_ports: int = 5000,
145
144
  seed: int = 71,
146
- bounds: tuple[tuple[float]] | None = None,
145
+ bounds: tuple[tuple[float, float], ...] | None = None,
147
146
  frontier_points: int = 200,
148
147
  minimize_method: LiteralMinimizeMethods = "SLSQP",
149
148
  *,
@@ -159,7 +158,7 @@ def efficient_frontier(
159
158
  Number of possible portfolios to simulate
160
159
  seed: int, default: 71
161
160
  The seed for the random process
162
- bounds: tuple[tuple[float]], optional
161
+ bounds: tuple[tuple[float, float], ...], optional
163
162
  The range of minumum and maximum allowed allocations for each asset
164
163
  frontier_points: int, default: 200
165
164
  number of points along frontier to optimize
@@ -253,7 +252,7 @@ def efficient_frontier(
253
252
  bounds = tuple((0.0, 1.0) for _ in range(eframe.item_count))
254
253
  init_guess = array(eframe.weights)
255
254
 
256
- opt_results = minimize(
255
+ opt_results = minimize( # type: ignore[call-overload]
257
256
  fun=_neg_sharpe,
258
257
  x0=init_guess,
259
258
  method=minimize_method,
@@ -288,7 +287,7 @@ def efficient_frontier(
288
287
  ),
289
288
  )
290
289
 
291
- result = minimize(
290
+ result = minimize( # type: ignore[call-overload]
292
291
  fun=_minimize_volatility,
293
292
  x0=init_guess,
294
293
  method=minimize_method,
@@ -333,7 +332,7 @@ def constrain_optimized_portfolios(
333
332
  portfolioname: str = "Current Portfolio",
334
333
  simulations: int = 10000,
335
334
  curve_points: int = 200,
336
- bounds: tuple[tuple[float]] | None = None,
335
+ bounds: tuple[tuple[float, float], ...] | None = None,
337
336
  minimize_method: LiteralMinimizeMethods = "SLSQP",
338
337
  ) -> tuple[OpenFrame, OpenTimeSeries, OpenFrame, OpenTimeSeries]:
339
338
  """Constrain optimized portfolios to those that improve on the current one.
@@ -350,7 +349,7 @@ def constrain_optimized_portfolios(
350
349
  Number of possible portfolios to simulate
351
350
  curve_points: int, default: 200
352
351
  Number of optimal portfolios on the efficient frontier
353
- bounds: tuple[tuple[float]], optional
352
+ bounds: tuple[tuple[float, float], ...], optional
354
353
  The range of minumum and maximum allowed allocations for each asset
355
354
  minimize_method: LiteralMinimizeMethods, default: SLSQP
356
355
  The method passed into the scipy.minimize function
@@ -441,7 +440,7 @@ def prepare_plot_data(
441
440
  for wgt, nm in zip(optimized[3:], assets.columns_lvl_zero, strict=True)
442
441
  ]
443
442
  opt_text = "<br><br>Weights:<br>" + "<br>".join(opt_text_list)
444
- vol: Series[float] = assets.vol
443
+ vol = cast("Series[float]", assets.vol)
445
444
  plotframe = DataFrame(
446
445
  data=[
447
446
  assets.arithmetic_ret,
@@ -7,7 +7,6 @@ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
7
  SPDX-License-Identifier: BSD-3-Clause
8
8
  """
9
9
 
10
- # mypy: disable-error-code="assignment"
11
10
  from __future__ import annotations
12
11
 
13
12
  from inspect import stack
@@ -20,20 +19,21 @@ from warnings import catch_warnings, simplefilter
20
19
 
21
20
  if TYPE_CHECKING: # pragma: no cover
22
21
  from pandas import Series
23
- from plotly.graph_objs import Figure
22
+ from plotly.graph_objs import Figure # type: ignore[import-untyped]
24
23
 
25
24
  from .frame import OpenFrame
26
25
  from .owntypes import LiteralPlotlyJSlib, LiteralPlotlyOutput
27
26
 
28
27
 
29
- from pandas import DataFrame, Series, Timestamp, concat
30
- from plotly.io import to_html
31
- from plotly.offline import plot
32
- from plotly.subplots import make_subplots
28
+ from pandas import DataFrame, Index, Series, Timestamp, concat
29
+ from plotly.io import to_html # type: ignore[import-untyped]
30
+ from plotly.offline import plot # type: ignore[import-untyped]
31
+ from plotly.subplots import make_subplots # type: ignore[import-untyped]
33
32
 
34
33
  from .load_plotly import load_plotly_dict
35
34
  from .owntypes import (
36
35
  LiteralBizDayFreq,
36
+ LiteralFrameProps,
37
37
  ValueType,
38
38
  )
39
39
 
@@ -72,15 +72,15 @@ def calendar_period_returns(
72
72
  cldr = copied.tsdf.iloc[1:].copy()
73
73
  if relabel:
74
74
  if freq.upper() == "BYE":
75
- cldr.index = [d.year for d in cldr.index]
75
+ cldr.index = Index([d.year for d in cldr.index])
76
76
  elif freq.upper() == "BQE":
77
- cldr.index = [
78
- Timestamp(d).to_period("Q").strftime("Q%q %Y") for d in cldr.index
79
- ]
77
+ cldr.index = Index(
78
+ [Timestamp(d).to_period("Q").strftime("Q%q %Y") for d in cldr.index]
79
+ )
80
80
  else:
81
- cldr.index = [d.strftime("%b %y") for d in cldr.index]
81
+ cldr.index = Index([d.strftime("%b %y") for d in cldr.index])
82
82
 
83
- return cldr # type: ignore[no-any-return]
83
+ return cldr
84
84
 
85
85
 
86
86
  def report_html(
@@ -127,9 +127,10 @@ def report_html(
127
127
  Plotly Figure and a div section or a html filename with location
128
128
 
129
129
  """
130
- data.trunc_frame().value_nan_handle().to_cumret()
130
+ copied = data.from_deepcopy()
131
+ copied.trunc_frame().value_nan_handle().to_cumret()
131
132
 
132
- if data.yearfrac > 1.0:
133
+ if copied.yearfrac > 1.0:
133
134
  properties = [
134
135
  "geo_ret",
135
136
  "vol",
@@ -217,10 +218,10 @@ def report_html(
217
218
  ],
218
219
  )
219
220
 
220
- for item, lbl in enumerate(data.columns_lvl_zero):
221
+ for item, lbl in enumerate(copied.columns_lvl_zero):
221
222
  figure.add_scatter(
222
- x=data.tsdf.index,
223
- y=data.tsdf.iloc[:, item],
223
+ x=copied.tsdf.index,
224
+ y=copied.tsdf.iloc[:, item],
224
225
  hovertemplate="%{y:.2%}<br>%{x|%Y-%m-%d}",
225
226
  line={"width": 2.5, "dash": "solid"},
226
227
  mode="lines",
@@ -231,18 +232,19 @@ def report_html(
231
232
  )
232
233
 
233
234
  quarter_of_year = 0.25
234
- if data.yearfrac < quarter_of_year:
235
- tmp = data.from_deepcopy()
235
+ if copied.yearfrac < quarter_of_year:
236
+ tmp = copied.from_deepcopy()
236
237
  bdf = tmp.value_to_ret().tsdf.iloc[1:]
237
238
  else:
238
- bdf = calendar_period_returns(data, freq=bar_freq)
239
+ bdf = calendar_period_returns(data=copied, freq=bar_freq)
239
240
 
240
- for item in range(data.item_count):
241
+ for item in range(copied.item_count):
242
+ col_name = cast("tuple[str, ValueType]", bdf.iloc[:, item].name)
241
243
  figure.add_bar(
242
244
  x=bdf.index,
243
245
  y=bdf.iloc[:, item],
244
246
  hovertemplate="%{y:.2%}<br>%{x}",
245
- name=bdf.iloc[:, item].name[0], # type: ignore[index]
247
+ name=col_name[0],
246
248
  showlegend=False,
247
249
  row=2,
248
250
  col=1,
@@ -263,8 +265,10 @@ def report_html(
263
265
  ]
264
266
 
265
267
  # noinspection PyTypeChecker
266
- rpt_df = data.all_properties(properties=properties) # type: ignore[arg-type]
267
- alpha_frame = data.from_deepcopy()
268
+ rpt_df = copied.all_properties(
269
+ properties=cast("list[LiteralFrameProps]", properties)
270
+ )
271
+ alpha_frame = copied.from_deepcopy()
268
272
  alpha_frame.to_cumret()
269
273
  with catch_warnings():
270
274
  simplefilter("ignore")
@@ -277,14 +281,16 @@ def report_html(
277
281
  for aname in alpha_frame.columns_lvl_zero[:-1]
278
282
  ]
279
283
  alphas.append("")
280
- ar = DataFrame(data=alphas, index=data.tsdf.columns, columns=["Jensen's Alpha"]).T
284
+ ar = DataFrame(
285
+ data=alphas, index=copied.tsdf.columns, columns=["Jensen's Alpha"]
286
+ ).T
281
287
  rpt_df = concat([rpt_df, ar])
282
- ir = data.info_ratio_func()
288
+ ir = copied.info_ratio_func()
283
289
  ir.name = "Information Ratio"
284
290
  ir.iloc[-1] = None
285
- ir = ir.to_frame().T
286
- rpt_df = concat([rpt_df, ir])
287
- te_frame = data.from_deepcopy()
291
+ ir_df = ir.to_frame().T
292
+ rpt_df = concat([rpt_df, ir_df])
293
+ te_frame = copied.from_deepcopy()
288
294
  te_frame.resample("7D")
289
295
  with catch_warnings():
290
296
  simplefilter("ignore")
@@ -298,11 +304,11 @@ def report_html(
298
304
  else:
299
305
  te.iloc[-1] = None
300
306
  te.name = "Tracking Error (weekly)"
301
- te = te.to_frame().T
302
- rpt_df = concat([rpt_df, te])
307
+ te_df = te.to_frame().T
308
+ rpt_df = concat([rpt_df, te_df])
303
309
 
304
- if data.yearfrac > 1.0:
305
- crm = data.from_deepcopy()
310
+ if copied.yearfrac > 1.0:
311
+ crm = copied.from_deepcopy()
306
312
  crm.resample("ME")
307
313
  cru_save = Series(
308
314
  data=[""] * crm.item_count,
@@ -322,10 +328,10 @@ def report_html(
322
328
  else:
323
329
  cru.iloc[-1] = None
324
330
  cru.name = "Capture Ratio (monthly)"
325
- cru = cru.to_frame().T
326
- rpt_df = concat([rpt_df, cru])
331
+ cru_df = cru.to_frame().T
332
+ rpt_df = concat([rpt_df, cru_df])
327
333
  formats.append("{:.2f}")
328
- beta_frame = data.from_deepcopy()
334
+ beta_frame = copied.from_deepcopy()
329
335
  beta_frame.resample("7D").value_nan_handle("drop")
330
336
  beta_frame.to_cumret()
331
337
  betas: list[str | float] = [
@@ -335,51 +341,50 @@ def report_html(
335
341
  )
336
342
  for bname in beta_frame.columns_lvl_zero[:-1]
337
343
  ]
338
- # noinspection PyTypeChecker
339
344
  betas.append("")
340
345
  br = DataFrame(
341
346
  data=betas,
342
- index=data.tsdf.columns,
347
+ index=copied.tsdf.columns,
343
348
  columns=["Index Beta (weekly)"],
344
349
  ).T
345
350
  rpt_df = concat([rpt_df, br])
346
351
 
347
352
  for item, f in zip(rpt_df.index, formats, strict=False):
348
353
  rpt_df.loc[item] = rpt_df.loc[item].apply(
349
- lambda x, fmt=f: x if (isinstance(x, str) or x is None) else fmt.format(x), # type: ignore[return-value]
354
+ lambda x, fmt=f: x if (isinstance(x, str) or x is None) else fmt.format(x),
350
355
  )
351
356
 
352
- rpt_df.index = labels_init
357
+ rpt_df.index = Index(labels_init)
353
358
 
354
- this_year = data.last_idx.year
355
- this_month = data.last_idx.month
356
- ytd = cast("Series[float]", data.value_ret_calendar_period(year=this_year)).map(
359
+ this_year = copied.last_idx.year
360
+ this_month = copied.last_idx.month
361
+ ytd = cast("Series[float]", copied.value_ret_calendar_period(year=this_year)).map(
357
362
  "{:.2%}".format
358
363
  )
359
364
  ytd.name = "Year-to-Date"
360
365
  mtd = cast(
361
366
  "Series[float]",
362
- data.value_ret_calendar_period(year=this_year, month=this_month),
367
+ copied.value_ret_calendar_period(year=this_year, month=this_month),
363
368
  ).map(
364
369
  "{:.2%}".format,
365
370
  )
366
371
  mtd.name = "Month-to-Date"
367
- ytd = ytd.to_frame().T
368
- mtd = mtd.to_frame().T
369
- rpt_df = concat([rpt_df, ytd])
370
- rpt_df = concat([rpt_df, mtd])
372
+ ytd_df = ytd.to_frame().T
373
+ mtd_df = mtd.to_frame().T
374
+ rpt_df = concat([rpt_df, ytd_df])
375
+ rpt_df = concat([rpt_df, mtd_df])
371
376
  rpt_df = rpt_df.reindex(labels_final)
372
377
 
373
- rpt_df.index = [f"<b>{x}</b>" for x in rpt_df.index]
378
+ rpt_df.index = Index([f"<b>{x}</b>" for x in rpt_df.index])
374
379
  rpt_df = rpt_df.reset_index()
375
380
 
376
- colmns = ["", *data.columns_lvl_zero]
381
+ colmns = ["", *copied.columns_lvl_zero]
377
382
  columns = [f"<b>{x}</b>" for x in colmns]
378
383
  aligning = ["left"] + ["center"] * (len(columns) - 1)
379
384
 
380
385
  col_even_color = "lightgrey"
381
386
  col_odd_color = "white"
382
- color_lst = ["grey"] + [col_odd_color] * (data.item_count - 1) + [col_even_color]
387
+ color_lst = ["grey"] + [col_odd_color] * (copied.item_count - 1) + [col_even_color]
383
388
 
384
389
  tablevalues = rpt_df.transpose().to_numpy().tolist()
385
390
  cleanedtablevalues = list(tablevalues)[:-1]
@@ -424,7 +429,9 @@ def report_html(
424
429
  figure.add_layout_image(logo)
425
430
 
426
431
  figure.update_layout(fig.get("layout"))
427
- colorway: list[str] = cast("dict[str, list[str]]", fig["layout"]).get("colorway")
432
+ colorway: list[str] = cast("dict[str, list[str]]", fig["layout"]).get(
433
+ "colorway", []
434
+ )
428
435
 
429
436
  if vertical_legend:
430
437
  legend = {
@@ -445,7 +452,7 @@ def report_html(
445
452
 
446
453
  figure.update_layout(
447
454
  legend=legend,
448
- colorway=colorway[: data.item_count],
455
+ colorway=colorway[: copied.item_count],
449
456
  )
450
457
  figure.update_xaxes(gridcolor="#EEEEEE", automargin=True, tickangle=-45)
451
458
  figure.update_yaxes(tickformat=".2%", gridcolor="#EEEEEE", automargin=True)
@@ -9,13 +9,13 @@ SPDX-License-Identifier: BSD-3-Clause
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- from collections.abc import Iterable
13
12
  from copy import deepcopy
14
13
  from logging import getLogger
15
14
  from typing import TYPE_CHECKING, Any, TypeVar, cast
16
15
 
17
16
  if TYPE_CHECKING: # pragma: no cover
18
17
  import datetime as dt
18
+ from collections.abc import Callable
19
19
 
20
20
  from numpy import (
21
21
  append,
@@ -59,6 +59,9 @@ from .owntypes import (
59
59
  ValueType,
60
60
  )
61
61
 
62
+ FieldValidator = cast("Callable[..., Callable[..., Any]]", field_validator)
63
+ ModelValidator = cast("Callable[..., Callable[..., Any]]", model_validator)
64
+
62
65
  logger = getLogger(__name__)
63
66
 
64
67
  __all__ = ["OpenTimeSeries", "timeseries_chain"]
@@ -67,7 +70,7 @@ TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
67
70
 
68
71
 
69
72
  # noinspection PyUnresolvedReferences,PyNestedDecorators
70
- class OpenTimeSeries(_CommonModel): # type: ignore[misc]
73
+ class OpenTimeSeries(_CommonModel):
71
74
  """OpenTimeSeries objects are at the core of the openseries package.
72
75
 
73
76
  The intended use is to allow analyses of financial timeseries.
@@ -123,21 +126,21 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
123
126
  isin: str | None = None
124
127
  label: str | None = None
125
128
 
126
- @field_validator("domestic", mode="before") # type: ignore[misc]
129
+ @FieldValidator("domestic", mode="before")
127
130
  @classmethod
128
131
  def _validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType:
129
132
  """Pydantic validator to ensure domestic field is validated."""
130
133
  _ = Currency(ccy=value)
131
134
  return value
132
135
 
133
- @field_validator("countries", mode="before") # type: ignore[misc]
136
+ @FieldValidator("countries", mode="before")
134
137
  @classmethod
135
138
  def _validate_countries(cls, value: CountriesType) -> CountriesType:
136
139
  """Pydantic validator to ensure countries field is validated."""
137
140
  _ = Countries(countryinput=value)
138
141
  return value
139
142
 
140
- @field_validator("markets", mode="before") # type: ignore[misc]
143
+ @FieldValidator("markets", mode="before")
141
144
  @classmethod
142
145
  def _validate_markets(
143
146
  cls, value: list[str] | str | None
@@ -156,7 +159,7 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
156
159
  raise MarketsNotStringNorListStrError(item_msg)
157
160
  raise MarketsNotStringNorListStrError(msg)
158
161
 
159
- @model_validator(mode="after") # type: ignore[misc,unused-ignore]
162
+ @ModelValidator(mode="after")
160
163
  def _dates_and_values_validate(self: Self) -> Self:
161
164
  """Pydantic validator to ensure dates and values are validated."""
162
165
  values_list_length = len(self.values)
@@ -370,19 +373,17 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
370
373
  An OpenTimeSeries object
371
374
 
372
375
  """
373
- if not isinstance(d_range, Iterable) and all([days, end_dt]):
374
- d_range = DatetimeIndex(
375
- [d.date() for d in date_range(periods=days, end=end_dt, freq="D")],
376
- )
377
- elif not isinstance(d_range, Iterable) and not all([days, end_dt]):
378
- msg = "If d_range is not provided both days and end_dt must be."
379
- raise IncorrectArgumentComboError(msg)
380
-
381
- deltas = array(
382
- [i.days for i in DatetimeIndex(d_range)[1:] - DatetimeIndex(d_range)[:-1]], # type: ignore[arg-type]
383
- )
376
+ if d_range is None:
377
+ if days is not None and end_dt is not None:
378
+ d_range = DatetimeIndex(
379
+ [d.date() for d in date_range(periods=days, end=end_dt, freq="D")],
380
+ )
381
+ else:
382
+ msg = "If d_range is not provided both days and end_dt must be."
383
+ raise IncorrectArgumentComboError(msg)
384
+ deltas = array([i.days for i in d_range[1:] - d_range[:-1]])
384
385
  arr: list[float] = list(cumprod(insert(1 + deltas * rate / 365, 0, 1.0)))
385
- dates = [d.strftime("%Y-%m-%d") for d in DatetimeIndex(d_range)] # type: ignore[arg-type]
386
+ dates = [d.strftime("%Y-%m-%d") for d in d_range]
386
387
 
387
388
  return cls(
388
389
  timeseries_id="",
@@ -552,8 +553,7 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
552
553
  arr = array(self.values) / divider
553
554
 
554
555
  deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]])
555
- # noinspection PyTypeChecker
556
- arr = cumprod( # type: ignore[assignment]
556
+ arr = cumprod(
557
557
  a=insert(arr=1.0 + deltas * arr[:-1] / days_in_year, obj=0, values=1.0)
558
558
  )
559
559
 
@@ -115,7 +115,7 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
115
115
  Simulation data
116
116
 
117
117
  """
118
- return self.dframe.add(1.0).cumprod(axis="columns").T # type: ignore[no-any-return]
118
+ return self.dframe.add(1.0).cumprod(axis="columns").T
119
119
 
120
120
  @property
121
121
  def realized_mean_return(self: Self) -> float:
@@ -463,7 +463,7 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
463
463
  [ValueType.RTRN],
464
464
  ],
465
465
  )
466
- return sdf # type: ignore[no-any-return]
466
+ return sdf
467
467
 
468
468
  fdf = DataFrame()
469
469
  for item in range(self.number_of_sims):
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openseries"
3
- version = "1.9.4"
3
+ version = "1.9.5"
4
4
  description = "Tools for analyzing financial timeseries."
5
5
  authors = [
6
6
  { name = "Martin Karrin", email = "martin.karrin@captor.se" },
@@ -60,15 +60,15 @@ dependencies = [
60
60
  "Release Notes" = "https://github.com/CaptorAB/openseries/releases"
61
61
 
62
62
  [tool.poetry.group.dev.dependencies]
63
- black = ">=24.4.2,<27.0.0"
64
63
  mypy = "1.17.1"
65
64
  pandas-stubs = ">=2.1.2,<3.0.0"
66
65
  pre-commit = ">=3.7.1,<6.0.0"
67
66
  pytest = ">=8.2.2,<9.0.0"
68
67
  pytest-cov = ">=5.0.0,<7.0.0"
69
68
  pytest-xdist = ">=3.3.1,<5.0.0"
70
- ruff = "0.12.10"
69
+ ruff = "0.12.12"
71
70
  types-openpyxl = ">=3.1.2,<5.0.0"
71
+ scipy-stubs = ">=1.14.1.0,<2.0.0"
72
72
  types-python-dateutil = ">=2.8.2,<4.0.0"
73
73
  types-requests = ">=2.20.0,<3.0.0"
74
74
 
@@ -77,6 +77,7 @@ requires = ["poetry-core>=2.1.3"]
77
77
  build-backend = "poetry.core.masonry.api"
78
78
 
79
79
  [tool.mypy]
80
+ python_version = "3.13"
80
81
  mypy_path = ["src"]
81
82
  exclude = ["venv/*"]
82
83
  cache_dir = ".mypy_cache"
@@ -85,10 +86,10 @@ strict = true
85
86
  pretty = true
86
87
  cache_fine_grained = true
87
88
  incremental = true
88
- ignore_missing_imports = true
89
+ ignore_missing_imports = false
89
90
  warn_unreachable = true
90
91
  warn_redundant_casts = true
91
- warn_unused_ignores = false
92
+ warn_unused_ignores = true
92
93
  disallow_any_generics = true
93
94
  check_untyped_defs = true
94
95
  no_implicit_reexport = true
@@ -100,9 +101,6 @@ init_forbid_extra = true
100
101
  init_typed = true
101
102
  warn_required_dynamic_aliases = true
102
103
 
103
- [tool.black]
104
- line-length = 87
105
-
106
104
  [tool.ruff]
107
105
  target-version = "py310"
108
106
  line-length = 87
@@ -113,7 +111,7 @@ ignore = ["COM812", "D203", "D213"]
113
111
  fixable = ["ALL"]
114
112
  mccabe = { max-complexity = 18 }
115
113
  pydocstyle = { convention = "google" }
116
- pylint = { max-args = 19, max-branches = 24, max-statements = 128 }
114
+ pylint = { max-args = 19, max-branches = 24, max-statements = 130 }
117
115
 
118
116
  [tool.pytest.ini_options]
119
117
  testpaths = ["tests"]
File without changes
File without changes