openseries 1.9.2__py3-none-any.whl → 1.9.3__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/frame.py CHANGED
@@ -7,13 +7,13 @@ 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="index,assignment,arg-type,no-any-return"
10
+ # mypy: disable-error-code="assignment,no-any-return"
11
11
  from __future__ import annotations
12
12
 
13
13
  from copy import deepcopy
14
14
  from functools import reduce
15
15
  from logging import getLogger
16
- from typing import TYPE_CHECKING, cast
16
+ from typing import TYPE_CHECKING, Any, cast
17
17
 
18
18
  if TYPE_CHECKING: # pragma: no cover
19
19
  import datetime as dt
@@ -26,7 +26,6 @@ from numpy import (
26
26
  log,
27
27
  nan,
28
28
  sqrt,
29
- square,
30
29
  std,
31
30
  )
32
31
  from pandas import (
@@ -127,12 +126,14 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
127
126
  Object of the class OpenFrame
128
127
 
129
128
  """
129
+ copied_constituents = [ts.from_deepcopy() for ts in constituents]
130
+
130
131
  super().__init__( # type: ignore[call-arg]
131
- constituents=constituents,
132
+ constituents=copied_constituents,
132
133
  weights=weights,
133
134
  )
134
135
 
135
- self.constituents = constituents
136
+ self.constituents = copied_constituents
136
137
  self.weights = weights
137
138
  self._set_tsdf()
138
139
 
@@ -339,10 +340,13 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
339
340
  The returns of the values in the series
340
341
 
341
342
  """
342
- returns = self.tsdf.ffill().pct_change()
343
+ returns = self.tsdf.pct_change()
343
344
  returns.iloc[0] = 0
344
- new_labels = [ValueType.RTRN] * self.item_count
345
- arrays = [self.tsdf.columns.get_level_values(0), new_labels]
345
+ new_labels: list[ValueType] = [ValueType.RTRN] * self.item_count
346
+ arrays: list[Index[Any], list[ValueType]] = [ # type: ignore[type-arg]
347
+ self.tsdf.columns.get_level_values(0),
348
+ new_labels,
349
+ ]
346
350
  returns.columns = MultiIndex.from_arrays(arrays=arrays)
347
351
  self.tsdf = returns.copy()
348
352
  return self
@@ -364,8 +368,11 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
364
368
  """
365
369
  self.tsdf = self.tsdf.diff(periods=periods)
366
370
  self.tsdf.iloc[0] = 0
367
- new_labels = [ValueType.RTRN] * self.item_count
368
- arrays = [self.tsdf.columns.get_level_values(0), new_labels]
371
+ new_labels: list[ValueType] = [ValueType.RTRN] * self.item_count
372
+ arrays: list[Index[Any], list[ValueType]] = [ # type: ignore[type-arg]
373
+ self.tsdf.columns.get_level_values(0),
374
+ new_labels,
375
+ ]
369
376
  self.tsdf.columns = MultiIndex.from_arrays(arrays)
370
377
  return self
371
378
 
@@ -380,7 +387,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
380
387
  """
381
388
  vtypes = [x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)]
382
389
  if not any(vtypes):
383
- returns = self.tsdf.ffill().pct_change()
390
+ returns = self.tsdf.pct_change()
384
391
  returns.iloc[0] = 0
385
392
  elif all(vtypes):
386
393
  returns = self.tsdf.copy()
@@ -392,8 +399,11 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
392
399
  returns = returns.add(1.0)
393
400
  self.tsdf = returns.cumprod(axis=0) / returns.iloc[0]
394
401
 
395
- new_labels = [ValueType.PRICE] * self.item_count
396
- arrays = [self.tsdf.columns.get_level_values(0), new_labels]
402
+ new_labels: list[ValueType] = [ValueType.PRICE] * self.item_count
403
+ arrays: list[Index[Any], list[ValueType]] = [ # type: ignore[type-arg]
404
+ self.tsdf.columns.get_level_values(0),
405
+ new_labels,
406
+ ]
397
407
  self.tsdf.columns = MultiIndex.from_arrays(arrays)
398
408
  return self
399
409
 
@@ -478,6 +488,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
478
488
  dlta_degr_freedms: int = 0,
479
489
  first_column: int = 0,
480
490
  second_column: int = 1,
491
+ corr_scale: float = 2.0,
481
492
  months_from_last: int | None = None,
482
493
  from_date: dt.date | None = None,
483
494
  to_date: dt.date | None = None,
@@ -500,6 +511,8 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
500
511
  Column of first timeseries.
501
512
  second_column: int, default: 1
502
513
  Column of second timeseries.
514
+ corr_scale: float, default: 2.0
515
+ Correlation scale factor.
503
516
  months_from_last : int, optional
504
517
  number of months offset as positive integer. Overrides use of from_date
505
518
  and to_date
@@ -542,9 +555,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
542
555
  data = self.tsdf.loc[cast("int", earlier) : cast("int", later)].copy()
543
556
 
544
557
  for rtn in cols:
545
- data[rtn, ValueType.RTRN] = (
546
- data.loc[:, (rtn, ValueType.PRICE)].apply(log).diff()
547
- )
558
+ data[rtn, ValueType.RTRN] = log(data.loc[:, (rtn, ValueType.PRICE)]).diff()
548
559
 
549
560
  raw_one = [
550
561
  data.loc[:, (cols[0], ValueType.RTRN)]
@@ -565,34 +576,39 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
565
576
  ddof=dlta_degr_freedms,
566
577
  )[0][1],
567
578
  ]
568
- raw_corr = [raw_cov[0] / (2 * raw_one[0] * raw_two[0])]
569
579
 
570
- for _, row in data.iloc[1:].iterrows():
571
- tmp_raw_one = sqrt(
572
- square(row.loc[cols[0], ValueType.RTRN]) * time_factor * (1 - lmbda)
573
- + square(raw_one[-1]) * lmbda,
574
- )
575
- tmp_raw_two = sqrt(
576
- square(row.loc[cols[1], ValueType.RTRN]) * time_factor * (1 - lmbda)
577
- + square(raw_two[-1]) * lmbda,
578
- )
579
- tmp_raw_cov = (
580
- row.loc[cols[0], ValueType.RTRN]
581
- * row.loc[cols[1], ValueType.RTRN]
582
- * time_factor
583
- * (1 - lmbda)
584
- + raw_cov[-1] * lmbda
585
- )
586
- tmp_raw_corr = tmp_raw_cov / (2 * tmp_raw_one * tmp_raw_two)
587
- raw_one.append(tmp_raw_one)
588
- raw_two.append(tmp_raw_two)
589
- raw_cov.append(tmp_raw_cov)
590
- raw_corr.append(tmp_raw_corr)
580
+ r1 = data.loc[:, (cols[0], ValueType.RTRN)]
581
+ r2 = data.loc[:, (cols[1], ValueType.RTRN)]
582
+
583
+ alpha = 1.0 - lmbda
584
+
585
+ s1 = (r1.pow(2) * time_factor).copy()
586
+ s2 = (r2.pow(2) * time_factor).copy()
587
+ sc = (r1 * r2 * time_factor).copy()
588
+
589
+ s1.iloc[0] = float(raw_one[0] ** 2)
590
+ s2.iloc[0] = float(raw_two[0] ** 2)
591
+ sc.iloc[0] = float(raw_cov[0])
592
+
593
+ m1 = s1.ewm(alpha=alpha, adjust=False).mean()
594
+ m2 = s2.ewm(alpha=alpha, adjust=False).mean()
595
+ mc = sc.ewm(alpha=alpha, adjust=False).mean()
596
+
597
+ m1v = m1.to_numpy(copy=False)
598
+ m2v = m2.to_numpy(copy=False)
599
+ mcv = mc.to_numpy(copy=False)
600
+
601
+ vol1 = sqrt(m1v)
602
+ vol2 = sqrt(m2v)
603
+ denom = corr_scale * vol1 * vol2
604
+
605
+ corr = mcv / denom
606
+ corr[denom == 0.0] = nan
591
607
 
592
608
  return DataFrame(
593
609
  index=[*cols, corr_label],
594
610
  columns=data.index,
595
- data=[raw_one, raw_two, raw_corr],
611
+ data=[vol1, vol2, corr],
596
612
  ).T
597
613
 
598
614
  @property
@@ -605,13 +621,9 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
605
621
  Correlation matrix
606
622
 
607
623
  """
608
- corr_matrix = (
609
- self.tsdf.ffill()
610
- .pct_change()
611
- .corr(
612
- method="pearson",
613
- min_periods=1,
614
- )
624
+ corr_matrix = self.tsdf.pct_change().corr(
625
+ method="pearson",
626
+ min_periods=1,
615
627
  )
616
628
  corr_matrix.columns = corr_matrix.columns.droplevel(level=1)
617
629
  corr_matrix.index = corr_matrix.index.droplevel(level=1)
@@ -838,7 +850,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
838
850
  ]
839
851
  relative = 1.0 + longdf - shortdf
840
852
  vol = float(
841
- relative.ffill().pct_change().std() * sqrt(time_factor),
853
+ relative.pct_change().std() * sqrt(time_factor),
842
854
  )
843
855
  terrors.append(vol)
844
856
 
@@ -930,10 +942,10 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
930
942
  ]
931
943
  relative = 1.0 + longdf - shortdf
932
944
  ret = float(
933
- relative.ffill().pct_change().mean() * time_factor,
945
+ relative.pct_change().mean() * time_factor,
934
946
  )
935
947
  vol = float(
936
- relative.ffill().pct_change().std() * sqrt(time_factor),
948
+ relative.pct_change().std() * sqrt(time_factor),
937
949
  )
938
950
  ratios.append(ret / vol)
939
951
 
@@ -1034,18 +1046,16 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1034
1046
  msg = "ratio must be one of 'up', 'down' or 'both'."
1035
1047
  if ratio == "up":
1036
1048
  uparray = (
1037
- longdf.ffill()
1038
- .pct_change()[
1039
- shortdf.ffill().pct_change().to_numpy() > loss_limit
1049
+ longdf.pct_change()[
1050
+ shortdf.pct_change().to_numpy() > loss_limit
1040
1051
  ]
1041
1052
  .add(1)
1042
1053
  .to_numpy()
1043
1054
  )
1044
1055
  up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
1045
1056
  upidxarray = (
1046
- shortdf.ffill()
1047
- .pct_change()[
1048
- shortdf.ffill().pct_change().to_numpy() > loss_limit
1057
+ shortdf.pct_change()[
1058
+ shortdf.pct_change().to_numpy() > loss_limit
1049
1059
  ]
1050
1060
  .add(1)
1051
1061
  .to_numpy()
@@ -1056,9 +1066,8 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1056
1066
  ratios.append(up_rtrn / up_idx_return)
1057
1067
  elif ratio == "down":
1058
1068
  downarray = (
1059
- longdf.ffill()
1060
- .pct_change()[
1061
- shortdf.ffill().pct_change().to_numpy() < loss_limit
1069
+ longdf.pct_change()[
1070
+ shortdf.pct_change().to_numpy() < loss_limit
1062
1071
  ]
1063
1072
  .add(1)
1064
1073
  .to_numpy()
@@ -1067,9 +1076,8 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1067
1076
  downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
1068
1077
  )
1069
1078
  downidxarray = (
1070
- shortdf.ffill()
1071
- .pct_change()[
1072
- shortdf.ffill().pct_change().to_numpy() < loss_limit
1079
+ shortdf.pct_change()[
1080
+ shortdf.pct_change().to_numpy() < loss_limit
1073
1081
  ]
1074
1082
  .add(1)
1075
1083
  .to_numpy()
@@ -1081,18 +1089,16 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1081
1089
  ratios.append(down_return / down_idx_return)
1082
1090
  elif ratio == "both":
1083
1091
  uparray = (
1084
- longdf.ffill()
1085
- .pct_change()[
1086
- shortdf.ffill().pct_change().to_numpy() > loss_limit
1092
+ longdf.pct_change()[
1093
+ shortdf.pct_change().to_numpy() > loss_limit
1087
1094
  ]
1088
1095
  .add(1)
1089
1096
  .to_numpy()
1090
1097
  )
1091
1098
  up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
1092
1099
  upidxarray = (
1093
- shortdf.ffill()
1094
- .pct_change()[
1095
- shortdf.ffill().pct_change().to_numpy() > loss_limit
1100
+ shortdf.pct_change()[
1101
+ shortdf.pct_change().to_numpy() > loss_limit
1096
1102
  ]
1097
1103
  .add(1)
1098
1104
  .to_numpy()
@@ -1101,9 +1107,8 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1101
1107
  upidxarray.prod() ** (1 / (len(upidxarray) / time_factor)) - 1
1102
1108
  )
1103
1109
  downarray = (
1104
- longdf.ffill()
1105
- .pct_change()[
1106
- shortdf.ffill().pct_change().to_numpy() < loss_limit
1110
+ longdf.pct_change()[
1111
+ shortdf.pct_change().to_numpy() < loss_limit
1107
1112
  ]
1108
1113
  .add(1)
1109
1114
  .to_numpy()
@@ -1112,9 +1117,8 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1112
1117
  downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
1113
1118
  )
1114
1119
  downidxarray = (
1115
- shortdf.ffill()
1116
- .pct_change()[
1117
- shortdf.ffill().pct_change().to_numpy() < loss_limit
1120
+ shortdf.pct_change()[
1121
+ shortdf.pct_change().to_numpy() < loss_limit
1118
1122
  ]
1119
1123
  .add(1)
1120
1124
  .to_numpy()
@@ -1410,7 +1414,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1410
1414
  msg = "Mix of series types will give inconsistent results"
1411
1415
  raise MixedValuetypesError(msg)
1412
1416
 
1413
- covariance = cov(asset_log, market_log, ddof=dlta_degr_freedms)
1417
+ covariance = cov(m=asset_log, y=market_log, ddof=dlta_degr_freedms)
1414
1418
  beta = covariance[0, 1] / covariance[1, 1]
1415
1419
 
1416
1420
  return float(asset_cagr - riskfree_rate - beta * (market_cagr - riskfree_rate))
@@ -1444,7 +1448,7 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1444
1448
 
1445
1449
  vtypes = [x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)]
1446
1450
  if not any(vtypes):
1447
- returns = self.tsdf.ffill().pct_change()
1451
+ returns = self.tsdf.pct_change()
1448
1452
  returns.iloc[0] = 0
1449
1453
  elif all(vtypes):
1450
1454
  returns = self.tsdf.copy()
@@ -1519,14 +1523,11 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1519
1523
  )
1520
1524
 
1521
1525
  retseries = (
1522
- relative.ffill()
1523
- .pct_change()
1524
- .rolling(observations, min_periods=observations)
1525
- .sum()
1526
+ relative.pct_change().rolling(observations, min_periods=observations).sum()
1526
1527
  )
1527
1528
  retdf = retseries.dropna().to_frame()
1528
1529
 
1529
- voldf = relative.ffill().pct_change().rolling(
1530
+ voldf = relative.pct_change().rolling(
1530
1531
  observations,
1531
1532
  min_periods=observations,
1532
1533
  ).std() * sqrt(time_factor)
@@ -1572,13 +1573,9 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1572
1573
  asset_label = cast("tuple[str, str]", self.tsdf.iloc[:, asset_column].name)[0]
1573
1574
  beta_label = f"{asset_label} / {market_label}"
1574
1575
 
1575
- rolling = (
1576
- self.tsdf.ffill()
1577
- .pct_change()
1578
- .rolling(
1579
- observations,
1580
- min_periods=observations,
1581
- )
1576
+ rolling = self.tsdf.pct_change().rolling(
1577
+ observations,
1578
+ min_periods=observations,
1582
1579
  )
1583
1580
 
1584
1581
  rcov = rolling.cov(ddof=dlta_degr_freedms)
@@ -1633,11 +1630,10 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1633
1630
  )
1634
1631
  first_series = (
1635
1632
  self.tsdf.iloc[:, first_column]
1636
- .ffill()
1637
1633
  .pct_change()[1:]
1638
1634
  .rolling(observations, min_periods=observations)
1639
1635
  )
1640
- second_series = self.tsdf.iloc[:, second_column].ffill().pct_change()[1:]
1636
+ second_series = self.tsdf.iloc[:, second_column].pct_change()[1:]
1641
1637
  corrdf = first_series.corr(other=second_series).dropna().to_frame()
1642
1638
  corrdf.columns = MultiIndex.from_arrays(
1643
1639
  [
@@ -1647,3 +1643,62 @@ class OpenFrame(_CommonModel): # type: ignore[misc]
1647
1643
  )
1648
1644
 
1649
1645
  return DataFrame(corrdf)
1646
+
1647
+ def multi_factor_linear_regression(
1648
+ self: Self,
1649
+ dependent_column: tuple[str, ValueType],
1650
+ ) -> tuple[DataFrame, OpenTimeSeries]:
1651
+ """Perform a multi-factor linear regression.
1652
+
1653
+ This function treats one specified column in the DataFrame as the dependent
1654
+ variable (y) and uses all remaining columns as independent variables (X).
1655
+ It utilizes a scikit-learn LinearRegression model and returns a DataFrame
1656
+ with summary output and an OpenTimeSeries of predicted values.
1657
+
1658
+ Parameters
1659
+ ----------
1660
+ dependent_column: tuple[str, ValueType]
1661
+ A tuple key to select the column in the OpenFrame.tsdf.columns
1662
+ to use as the dependent variable
1663
+
1664
+ Returns:
1665
+ -------
1666
+ tuple[pandas.DataFrame, OpenTimeSeries]
1667
+ - A DataFrame with the R-squared, the intercept
1668
+ and the regression coefficients
1669
+ - An OpenTimeSeries of predicted values
1670
+
1671
+ Raises:
1672
+ KeyError: If the column tuple is not found in the OpenFrame.tsdf.columns
1673
+ ValueError: If not all series are returnseries (ValueType.RTRN)
1674
+ """
1675
+ key_msg = (
1676
+ f"Tuple ({dependent_column[0]}, "
1677
+ f"{dependent_column[1].value}) not found in data."
1678
+ )
1679
+ if dependent_column not in self.tsdf.columns:
1680
+ raise KeyError(key_msg)
1681
+
1682
+ vtype_msg = "All series should be of ValueType.RTRN."
1683
+ if not all(x == ValueType.RTRN for x in self.tsdf.columns.get_level_values(1)):
1684
+ raise MixedValuetypesError(vtype_msg)
1685
+
1686
+ dependent = self.tsdf[dependent_column]
1687
+ factors = self.tsdf.drop(columns=[dependent_column])
1688
+ indx = ["R-square", "Intercept", *factors.columns.droplevel(level=1)]
1689
+
1690
+ model = LinearRegression()
1691
+ model.fit(factors, dependent)
1692
+
1693
+ predictions = OpenTimeSeries.from_arrays(
1694
+ name=f"Predicted {dependent_column[0]}",
1695
+ dates=[date.strftime("%Y-%m-%d") for date in self.tsdf.index],
1696
+ values=list(model.predict(factors)),
1697
+ valuetype=ValueType.RTRN,
1698
+ )
1699
+
1700
+ output = [model.score(factors, dependent), model.intercept_, *model.coef_]
1701
+
1702
+ result = DataFrame(data=output, index=indx, columns=[dependent_column[0]])
1703
+
1704
+ return result, predictions.to_cumret()
openseries/load_plotly.py CHANGED
@@ -76,7 +76,7 @@ def load_plotly_dict(
76
76
  with logofile.open(mode="r", encoding="utf-8") as logo_file:
77
77
  logo = load(logo_file)
78
78
 
79
- if _check_remote_file_existence(url=logo["source"]) is False:
79
+ if not _check_remote_file_existence(url=logo["source"]):
80
80
  msg = f"Failed to add logo image from URL {logo['source']}"
81
81
  logger.warning(msg)
82
82
  logo = {}
openseries/owntypes.py CHANGED
@@ -167,6 +167,7 @@ LiteralSeriesProps = Literal[
167
167
  "downside_deviation",
168
168
  "ret_vol_ratio",
169
169
  "sortino_ratio",
170
+ "kappa3_ratio",
170
171
  "z_score",
171
172
  "skew",
172
173
  "kurtosis",
@@ -194,6 +195,7 @@ LiteralFrameProps = Literal[
194
195
  "downside_deviation",
195
196
  "ret_vol_ratio",
196
197
  "sortino_ratio",
198
+ "kappa3_ratio",
197
199
  "z_score",
198
200
  "skew",
199
201
  "kurtosis",
@@ -224,6 +226,7 @@ class PropertiesList(list[str]):
224
226
  "downside_deviation",
225
227
  "ret_vol_ratio",
226
228
  "sortino_ratio",
229
+ "kappa3_ratio",
227
230
  "omega_ratio",
228
231
  "z_score",
229
232
  "skew",
@@ -7,7 +7,7 @@ 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="index,assignment"
10
+ # mypy: disable-error-code="assignment"
11
11
  from __future__ import annotations
12
12
 
13
13
  from inspect import stack
@@ -320,7 +320,7 @@ def efficient_frontier(
320
320
 
321
321
  if tweak:
322
322
  limit_tweak = 0.001
323
- line_df["stdev_diff"] = line_df.stdev.ffill().pct_change()
323
+ line_df["stdev_diff"] = line_df.stdev.pct_change()
324
324
  line_df = line_df.loc[line_df.stdev_diff.abs() > limit_tweak]
325
325
  line_df = line_df.drop(columns="stdev_diff")
326
326
 
@@ -378,9 +378,11 @@ def constrain_optimized_portfolios(
378
378
  condition_least_ret = front_frame.ret > serie.arithmetic_ret
379
379
  # noinspection PyArgumentList
380
380
  least_ret_frame = front_frame[condition_least_ret].sort_values(by="stdev")
381
- least_ret_port = least_ret_frame.iloc[0]
381
+ least_ret_port: Series[float] = least_ret_frame.iloc[0]
382
382
  least_ret_port_name = f"Minimize vol & target return of {portfolioname}"
383
- least_ret_weights = [least_ret_port[c] for c in lr_frame.columns_lvl_zero]
383
+ least_ret_weights: list[float] = [
384
+ least_ret_port.loc[c] for c in lr_frame.columns_lvl_zero
385
+ ]
384
386
  lr_frame.weights = least_ret_weights
385
387
  resleast = OpenTimeSeries.from_df(lr_frame.make_portfolio(least_ret_port_name))
386
388
 
@@ -390,9 +392,11 @@ def constrain_optimized_portfolios(
390
392
  by="ret",
391
393
  ascending=False,
392
394
  )
393
- most_vol_port = most_vol_frame.iloc[0]
395
+ most_vol_port: Series[float] = most_vol_frame.iloc[0]
394
396
  most_vol_port_name = f"Maximize return & target risk of {portfolioname}"
395
- most_vol_weights = [most_vol_port[c] for c in mv_frame.columns_lvl_zero]
397
+ most_vol_weights: list[float] = [
398
+ most_vol_port.loc[c] for c in mv_frame.columns_lvl_zero
399
+ ]
396
400
  mv_frame.weights = most_vol_weights
397
401
  resmost = OpenTimeSeries.from_df(mv_frame.make_portfolio(most_vol_port_name))
398
402
 
@@ -562,7 +566,7 @@ def sharpeplot(
562
566
  )
563
567
 
564
568
  if point_frame is not None:
565
- colorway = cast(
569
+ colorway = cast( # type: ignore[index]
566
570
  "dict[str, str | int | float | bool | list[str]]",
567
571
  fig["layout"],
568
572
  ).get("colorway")[: len(point_frame.columns)]
openseries/report.py CHANGED
@@ -7,7 +7,7 @@ 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,index,arg-type"
10
+ # mypy: disable-error-code="assignment"
11
11
  from __future__ import annotations
12
12
 
13
13
  from inspect import stack
@@ -244,7 +244,7 @@ def report_html(
244
244
  x=bdf.index,
245
245
  y=bdf.iloc[:, item],
246
246
  hovertemplate="%{y:.2%}<br>%{x}",
247
- name=bdf.iloc[:, item].name[0],
247
+ name=bdf.iloc[:, item].name[0], # type: ignore[index]
248
248
  showlegend=False,
249
249
  row=2,
250
250
  col=1,
@@ -265,7 +265,7 @@ def report_html(
265
265
  ]
266
266
 
267
267
  # noinspection PyTypeChecker
268
- rpt_df = data.all_properties(properties=properties)
268
+ rpt_df = data.all_properties(properties=properties) # type: ignore[arg-type]
269
269
  alpha_frame = data.from_deepcopy()
270
270
  alpha_frame.to_cumret()
271
271
  with catch_warnings():
openseries/series.py CHANGED
@@ -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
  from collections.abc import Iterable
@@ -469,7 +468,7 @@ class OpenTimeSeries(_CommonModel):
469
468
  The returns of the values in the series
470
469
 
471
470
  """
472
- returns = self.tsdf.ffill().pct_change()
471
+ returns = self.tsdf.pct_change()
473
472
  returns.iloc[0] = 0
474
473
  self.valuetype = ValueType.RTRN
475
474
  arrays = [[self.label], [self.valuetype]]
@@ -673,9 +672,9 @@ class OpenTimeSeries(_CommonModel):
673
672
 
674
673
  data = self.tsdf.loc[cast("int", earlier) : cast("int", later)].copy()
675
674
 
676
- data[self.label, ValueType.RTRN] = (
677
- data.loc[:, self.tsdf.columns.to_numpy()[0]].apply(log).diff()
678
- )
675
+ data[self.label, ValueType.RTRN] = log(
676
+ data.loc[:, self.tsdf.columns.to_numpy()[0]]
677
+ ).diff()
679
678
 
680
679
  rawdata = [
681
680
  data.loc[:, cast("int", (self.label, ValueType.RTRN))]
@@ -726,11 +725,12 @@ class OpenTimeSeries(_CommonModel):
726
725
  returns_input = True
727
726
  else:
728
727
  values = [cast("float", self.tsdf.iloc[0, 0])]
729
- ra_df = self.tsdf.ffill().pct_change()
728
+ ra_df = self.tsdf.pct_change()
730
729
  returns_input = False
731
730
  ra_df = ra_df.dropna()
732
731
 
733
732
  prev = self.first_idx
733
+ # noinspection PyTypeChecker
734
734
  dates: list[dt.date] = [prev]
735
735
 
736
736
  for idx, row in ra_df.iterrows():
openseries/simulation.py CHANGED
@@ -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
  from typing import TYPE_CHECKING, cast
@@ -116,7 +115,7 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
116
115
  Simulation data
117
116
 
118
117
  """
119
- return self.dframe.add(1.0).cumprod(axis="columns").T
118
+ return self.dframe.add(1.0).cumprod(axis="columns").T # type: ignore[no-any-return]
120
119
 
121
120
  @property
122
121
  def realized_mean_return(self: Self) -> float:
@@ -130,9 +129,7 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
130
129
  """
131
130
  return cast(
132
131
  "float",
133
- (
134
- self.results.ffill().pct_change().mean() * self.trading_days_in_year
135
- ).iloc[0],
132
+ (self.results.pct_change().mean() * self.trading_days_in_year).iloc[0],
136
133
  )
137
134
 
138
135
  @property
@@ -147,10 +144,9 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
147
144
  """
148
145
  return cast(
149
146
  "float",
150
- (
151
- self.results.ffill().pct_change().std()
152
- * sqrt(self.trading_days_in_year)
153
- ).iloc[0],
147
+ (self.results.pct_change().std() * sqrt(self.trading_days_in_year)).iloc[
148
+ 0
149
+ ],
154
150
  )
155
151
 
156
152
  @classmethod
@@ -464,7 +460,7 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
464
460
  [ValueType.RTRN],
465
461
  ],
466
462
  )
467
- return sdf
463
+ return sdf # type: ignore[no-any-return]
468
464
 
469
465
  fdf = DataFrame()
470
466
  for item in range(self.number_of_sims):