openseries 1.7.4__tar.gz → 1.7.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.1
2
2
  Name: openseries
3
- Version: 1.7.4
3
+ Version: 1.7.5
4
4
  Summary: Tools for analyzing financial timeseries.
5
5
  Home-page: https://github.com/CaptorAB/openseries
6
6
  License: BSD-3-Clause
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Topic :: Office/Business :: Financial :: Investment
22
22
  Requires-Dist: holidays (>=0.30,<1.0)
23
- Requires-Dist: numpy (>=1.23.2,<=3.0.0)
23
+ Requires-Dist: numpy (>=1.23.2,<3.0.0)
24
24
  Requires-Dist: openpyxl (>=3.1.2,<4.0.0)
25
25
  Requires-Dist: pandas (>=2.1.2,<3.0.0)
26
26
  Requires-Dist: plotly (>=5.18.0,<6.0.0)
@@ -29,7 +29,7 @@ Requires-Dist: pydantic (>=2.5.2,<3.0.0)
29
29
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
30
30
  Requires-Dist: requests (>=2.20.0,<3.0.0)
31
31
  Requires-Dist: scipy (>=1.11.4,<2.0.0)
32
- Requires-Dist: statsmodels (>=0.14.0,<1.0.0)
32
+ Requires-Dist: statsmodels (>=0.14.0,!=0.14.2,<1.0.0)
33
33
  Project-URL: Repository, https://github.com/CaptorAB/openseries
34
34
  Description-Content-Type: text/markdown
35
35
 
@@ -360,7 +360,7 @@ class _CommonModel(BaseModel):
360
360
  """
361
361
  wmdf = self.tsdf.copy()
362
362
  wmdf.index = DatetimeIndex(wmdf.index)
363
- result = wmdf.resample("BME").last().pct_change().min()
363
+ result = wmdf.resample("BME").last().ffill().pct_change().min()
364
364
 
365
365
  if self.tsdf.shape[1] == 1:
366
366
  return float(result.iloc[0])
@@ -1285,19 +1285,24 @@ class _CommonModel(BaseModel):
1285
1285
  if drift_adjust:
1286
1286
  imp_vol = (-sqrt(time_factor) / norm.ppf(level)) * (
1287
1287
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1288
+ .ffill()
1288
1289
  .pct_change()
1289
1290
  .quantile(1 - level, interpolation=interpolation)
1290
1291
  - self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1292
+ .ffill()
1291
1293
  .pct_change()
1292
1294
  .sum()
1293
1295
  / len(
1294
- self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(),
1296
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1297
+ .ffill()
1298
+ .pct_change(),
1295
1299
  )
1296
1300
  )
1297
1301
  else:
1298
1302
  imp_vol = (
1299
1303
  -sqrt(time_factor)
1300
1304
  * self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1305
+ .ffill()
1301
1306
  .pct_change()
1302
1307
  .quantile(1 - level, interpolation=interpolation)
1303
1308
  / norm.ppf(level)
@@ -1361,6 +1366,7 @@ class _CommonModel(BaseModel):
1361
1366
  cvar_df = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy(deep=True)
1362
1367
  result = [
1363
1368
  cvar_df.loc[:, x] # type: ignore[call-overload,index]
1369
+ .ffill()
1364
1370
  .pct_change()
1365
1371
  .sort_values()
1366
1372
  .iloc[
@@ -1368,6 +1374,7 @@ class _CommonModel(BaseModel):
1368
1374
  ceil(
1369
1375
  (1 - level)
1370
1376
  * cvar_df.loc[:, x] # type: ignore[index]
1377
+ .ffill()
1371
1378
  .pct_change()
1372
1379
  .count(),
1373
1380
  ),
@@ -1428,6 +1435,7 @@ class _CommonModel(BaseModel):
1428
1435
  )
1429
1436
  how_many = (
1430
1437
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1438
+ .ffill()
1431
1439
  .pct_change()
1432
1440
  .count(numeric_only=True)
1433
1441
  )
@@ -1443,6 +1451,7 @@ class _CommonModel(BaseModel):
1443
1451
 
1444
1452
  dddf = (
1445
1453
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1454
+ .ffill()
1446
1455
  .pct_change()
1447
1456
  .sub(min_accepted_return / time_factor)
1448
1457
  )
@@ -1546,6 +1555,7 @@ class _CommonModel(BaseModel):
1546
1555
  )
1547
1556
  result: NDArray[float64] = skew(
1548
1557
  a=self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1558
+ .ffill()
1549
1559
  .pct_change()
1550
1560
  .to_numpy(),
1551
1561
  bias=True,
@@ -1593,7 +1603,7 @@ class _CommonModel(BaseModel):
1593
1603
  to_dt=to_date,
1594
1604
  )
1595
1605
  result: NDArray[float64] = kurtosis(
1596
- self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change(),
1606
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)].ffill().pct_change(),
1597
1607
  fisher=True,
1598
1608
  bias=True,
1599
1609
  nan_policy="omit",
@@ -1689,13 +1699,21 @@ class _CommonModel(BaseModel):
1689
1699
  )
1690
1700
  pos = (
1691
1701
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1702
+ .ffill()
1692
1703
  .pct_change()[1:][
1693
- self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()[1:]
1704
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1705
+ .ffill()
1706
+ .pct_change()[1:]
1694
1707
  > zero
1695
1708
  ]
1696
1709
  .count()
1697
1710
  )
1698
- tot = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change().count()
1711
+ tot = (
1712
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1713
+ .ffill()
1714
+ .pct_change()
1715
+ .count()
1716
+ )
1699
1717
  share = pos / tot
1700
1718
  if self.tsdf.shape[1] == 1:
1701
1719
  return float(share.iloc[0])
@@ -1871,7 +1889,9 @@ class _CommonModel(BaseModel):
1871
1889
  from_dt=from_date,
1872
1890
  to_dt=to_date,
1873
1891
  )
1874
- retdf = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()
1892
+ retdf = (
1893
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)].ffill().pct_change()
1894
+ )
1875
1895
  pos = retdf[retdf > min_accepted_return].sub(min_accepted_return).sum()
1876
1896
  neg = retdf[retdf < min_accepted_return].sub(min_accepted_return).sum()
1877
1897
  ratio = pos / -neg
@@ -1959,7 +1979,7 @@ class _CommonModel(BaseModel):
1959
1979
  period = "-".join([str(year), str(month).zfill(2)])
1960
1980
  vrdf = self.tsdf.copy()
1961
1981
  vrdf.index = DatetimeIndex(vrdf.index)
1962
- resultdf = DataFrame(vrdf.pct_change())
1982
+ resultdf = DataFrame(vrdf.ffill().pct_change())
1963
1983
  result = resultdf.loc[period] + 1
1964
1984
  cal_period = result.cumprod(axis="index").iloc[-1] - 1
1965
1985
  if self.tsdf.shape[1] == 1:
@@ -2011,6 +2031,7 @@ class _CommonModel(BaseModel):
2011
2031
  )
2012
2032
  result = (
2013
2033
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
2034
+ .ffill()
2014
2035
  .pct_change()
2015
2036
  .quantile(1 - level, interpolation=interpolation)
2016
2037
  )
@@ -2059,6 +2080,7 @@ class _CommonModel(BaseModel):
2059
2080
  )
2060
2081
  result = (
2061
2082
  self.tsdf.loc[cast(int, earlier) : cast(int, later)]
2083
+ .ffill()
2062
2084
  .pct_change()
2063
2085
  .rolling(observations, min_periods=observations)
2064
2086
  .sum()
@@ -2105,7 +2127,9 @@ class _CommonModel(BaseModel):
2105
2127
  from_dt=from_date,
2106
2128
  to_dt=to_date,
2107
2129
  )
2108
- zscframe = self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change()
2130
+ zscframe = (
2131
+ self.tsdf.loc[cast(int, earlier) : cast(int, later)].ffill().pct_change()
2132
+ )
2109
2133
  result = (zscframe.iloc[-1] - zscframe.mean()) / zscframe.std()
2110
2134
 
2111
2135
  if self.tsdf.shape[1] == 1:
@@ -2174,6 +2198,7 @@ class _CommonModel(BaseModel):
2174
2198
  ret_label = cast(tuple[str], self.tsdf.iloc[:, column].name)[0]
2175
2199
  retseries = (
2176
2200
  self.tsdf.iloc[:, column]
2201
+ .ffill()
2177
2202
  .pct_change()
2178
2203
  .rolling(observations, min_periods=observations)
2179
2204
  .sum()
@@ -2251,7 +2276,7 @@ class _CommonModel(BaseModel):
2251
2276
  else:
2252
2277
  time_factor = self.periods_in_a_year
2253
2278
  vol_label = cast(tuple[str, ValueType], self.tsdf.iloc[:, column].name)[0]
2254
- dframe = self.tsdf.iloc[:, column].pct_change()
2279
+ dframe = self.tsdf.iloc[:, column].ffill().pct_change()
2255
2280
  volseries = dframe.rolling(
2256
2281
  observations,
2257
2282
  min_periods=observations,
@@ -602,9 +602,13 @@ class OpenFrame(_CommonModel):
602
602
  Correlation matrix
603
603
 
604
604
  """
605
- corr_matrix = self.tsdf.pct_change().corr(
606
- method="pearson",
607
- min_periods=1,
605
+ corr_matrix = (
606
+ self.tsdf.ffill()
607
+ .pct_change()
608
+ .corr(
609
+ method="pearson",
610
+ min_periods=1,
611
+ )
608
612
  )
609
613
  corr_matrix.columns = corr_matrix.columns.droplevel(level=1)
610
614
  corr_matrix.index = corr_matrix.index.droplevel(level=1)
@@ -829,7 +833,7 @@ class OpenFrame(_CommonModel):
829
833
  ]
830
834
  relative = 1.0 + longdf - shortdf
831
835
  vol = float(
832
- relative.pct_change().std() * sqrt(time_factor),
836
+ relative.ffill().pct_change().std() * sqrt(time_factor),
833
837
  )
834
838
  terrors.append(vol)
835
839
 
@@ -919,10 +923,10 @@ class OpenFrame(_CommonModel):
919
923
  ]
920
924
  relative = 1.0 + longdf - shortdf
921
925
  ret = float(
922
- relative.pct_change().mean() * time_factor,
926
+ relative.ffill().pct_change().mean() * time_factor,
923
927
  )
924
928
  vol = float(
925
- relative.pct_change().std() * sqrt(time_factor),
929
+ relative.ffill().pct_change().std() * sqrt(time_factor),
926
930
  )
927
931
  ratios.append(ret / vol)
928
932
 
@@ -1021,16 +1025,18 @@ class OpenFrame(_CommonModel):
1021
1025
  msg = "ratio must be one of 'up', 'down' or 'both'."
1022
1026
  if ratio == "up":
1023
1027
  uparray = (
1024
- longdf.pct_change()[
1025
- shortdf.pct_change().to_numpy() > loss_limit
1028
+ longdf.ffill()
1029
+ .pct_change()[
1030
+ shortdf.ffill().pct_change().to_numpy() > loss_limit
1026
1031
  ]
1027
1032
  .add(1)
1028
1033
  .to_numpy()
1029
1034
  )
1030
1035
  up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
1031
1036
  upidxarray = (
1032
- shortdf.pct_change()[
1033
- shortdf.pct_change().to_numpy() > loss_limit
1037
+ shortdf.ffill()
1038
+ .pct_change()[
1039
+ shortdf.ffill().pct_change().to_numpy() > loss_limit
1034
1040
  ]
1035
1041
  .add(1)
1036
1042
  .to_numpy()
@@ -1041,8 +1047,9 @@ class OpenFrame(_CommonModel):
1041
1047
  ratios.append(up_rtrn / up_idx_return)
1042
1048
  elif ratio == "down":
1043
1049
  downarray = (
1044
- longdf.pct_change()[
1045
- shortdf.pct_change().to_numpy() < loss_limit
1050
+ longdf.ffill()
1051
+ .pct_change()[
1052
+ shortdf.ffill().pct_change().to_numpy() < loss_limit
1046
1053
  ]
1047
1054
  .add(1)
1048
1055
  .to_numpy()
@@ -1051,8 +1058,9 @@ class OpenFrame(_CommonModel):
1051
1058
  downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
1052
1059
  )
1053
1060
  downidxarray = (
1054
- shortdf.pct_change()[
1055
- shortdf.pct_change().to_numpy() < loss_limit
1061
+ shortdf.ffill()
1062
+ .pct_change()[
1063
+ shortdf.ffill().pct_change().to_numpy() < loss_limit
1056
1064
  ]
1057
1065
  .add(1)
1058
1066
  .to_numpy()
@@ -1064,16 +1072,18 @@ class OpenFrame(_CommonModel):
1064
1072
  ratios.append(down_return / down_idx_return)
1065
1073
  elif ratio == "both":
1066
1074
  uparray = (
1067
- longdf.pct_change()[
1068
- shortdf.pct_change().to_numpy() > loss_limit
1075
+ longdf.ffill()
1076
+ .pct_change()[
1077
+ shortdf.ffill().pct_change().to_numpy() > loss_limit
1069
1078
  ]
1070
1079
  .add(1)
1071
1080
  .to_numpy()
1072
1081
  )
1073
1082
  up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
1074
1083
  upidxarray = (
1075
- shortdf.pct_change()[
1076
- shortdf.pct_change().to_numpy() > loss_limit
1084
+ shortdf.ffill()
1085
+ .pct_change()[
1086
+ shortdf.ffill().pct_change().to_numpy() > loss_limit
1077
1087
  ]
1078
1088
  .add(1)
1079
1089
  .to_numpy()
@@ -1082,8 +1092,9 @@ class OpenFrame(_CommonModel):
1082
1092
  upidxarray.prod() ** (1 / (len(upidxarray) / time_factor)) - 1
1083
1093
  )
1084
1094
  downarray = (
1085
- longdf.pct_change()[
1086
- shortdf.pct_change().to_numpy() < loss_limit
1095
+ longdf.ffill()
1096
+ .pct_change()[
1097
+ shortdf.ffill().pct_change().to_numpy() < loss_limit
1087
1098
  ]
1088
1099
  .add(1)
1089
1100
  .to_numpy()
@@ -1092,8 +1103,9 @@ class OpenFrame(_CommonModel):
1092
1103
  downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
1093
1104
  )
1094
1105
  downidxarray = (
1095
- shortdf.pct_change()[
1096
- shortdf.pct_change().to_numpy() < loss_limit
1106
+ shortdf.ffill()
1107
+ .pct_change()[
1108
+ shortdf.ffill().pct_change().to_numpy() < loss_limit
1097
1109
  ]
1098
1110
  .add(1)
1099
1111
  .to_numpy()
@@ -1499,11 +1511,14 @@ class OpenFrame(_CommonModel):
1499
1511
  )
1500
1512
 
1501
1513
  retseries = (
1502
- relative.pct_change().rolling(observations, min_periods=observations).sum()
1514
+ relative.ffill()
1515
+ .pct_change()
1516
+ .rolling(observations, min_periods=observations)
1517
+ .sum()
1503
1518
  )
1504
1519
  retdf = retseries.dropna().to_frame()
1505
1520
 
1506
- voldf = relative.pct_change().rolling(
1521
+ voldf = relative.ffill().pct_change().rolling(
1507
1522
  observations,
1508
1523
  min_periods=observations,
1509
1524
  ).std() * sqrt(time_factor)
@@ -1547,9 +1562,13 @@ class OpenFrame(_CommonModel):
1547
1562
  asset_label = cast(tuple[str, str], self.tsdf.iloc[:, asset_column].name)[0]
1548
1563
  beta_label = f"{asset_label} / {market_label}"
1549
1564
 
1550
- rolling = self.tsdf.pct_change().rolling(
1551
- observations,
1552
- min_periods=observations,
1565
+ rolling = (
1566
+ self.tsdf.ffill()
1567
+ .pct_change()
1568
+ .rolling(
1569
+ observations,
1570
+ min_periods=observations,
1571
+ )
1553
1572
  )
1554
1573
 
1555
1574
  rcov = rolling.cov(ddof=dlta_degr_freedms)
@@ -1604,10 +1623,11 @@ class OpenFrame(_CommonModel):
1604
1623
  )
1605
1624
  first_series = (
1606
1625
  self.tsdf.iloc[:, first_column]
1626
+ .ffill()
1607
1627
  .pct_change()[1:]
1608
1628
  .rolling(observations, min_periods=observations)
1609
1629
  )
1610
- second_series = self.tsdf.iloc[:, second_column].pct_change()[1:]
1630
+ second_series = self.tsdf.iloc[:, second_column].ffill().pct_change()[1:]
1611
1631
  corrdf = first_series.corr(other=second_series).dropna().to_frame()
1612
1632
  corrdf.columns = MultiIndex.from_arrays(
1613
1633
  [
@@ -308,7 +308,7 @@ def efficient_frontier( # noqa: C901
308
308
 
309
309
  if tweak:
310
310
  limit_tweak = 0.001
311
- line_df["stdev_diff"] = line_df.stdev.pct_change()
311
+ line_df["stdev_diff"] = line_df.stdev.ffill().pct_change()
312
312
  line_df = line_df.loc[line_df.stdev_diff.abs() > limit_tweak]
313
313
  line_df = line_df.drop(columns="stdev_diff")
314
314
 
@@ -687,7 +687,7 @@ class OpenTimeSeries(_CommonModel):
687
687
  returns_input = True
688
688
  else:
689
689
  values = [cast(float, self.tsdf.iloc[0, 0])]
690
- ra_df = self.tsdf.pct_change()
690
+ ra_df = self.tsdf.ffill().pct_change()
691
691
  returns_input = False
692
692
  ra_df = ra_df.dropna()
693
693
 
@@ -122,7 +122,9 @@ class ReturnSimulation(BaseModel):
122
122
  """
123
123
  return cast(
124
124
  float,
125
- (self.results.pct_change().mean() * self.trading_days_in_year).iloc[0],
125
+ (
126
+ self.results.ffill().pct_change().mean() * self.trading_days_in_year
127
+ ).iloc[0],
126
128
  )
127
129
 
128
130
  @property
@@ -137,9 +139,10 @@ class ReturnSimulation(BaseModel):
137
139
  """
138
140
  return cast(
139
141
  float,
140
- (self.results.pct_change().std() * sqrt(self.trading_days_in_year)).iloc[
141
- 0
142
- ],
142
+ (
143
+ self.results.ffill().pct_change().std()
144
+ * sqrt(self.trading_days_in_year)
145
+ ).iloc[0],
143
146
  )
144
147
 
145
148
  @classmethod
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "openseries"
3
- version = "1.7.4"
3
+ version = "1.7.5"
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"
@@ -34,7 +34,7 @@ keywords = [
34
34
  [tool.poetry.dependencies]
35
35
  python = ">=3.10,<3.13"
36
36
  holidays = ">=0.30,<1.0"
37
- numpy = ">=1.23.2,<=3.0.0"
37
+ numpy = ">=1.23.2,<3.0.0"
38
38
  openpyxl = ">=3.1.2,<4.0.0"
39
39
  pandas = ">=2.1.2,<3.0.0"
40
40
  plotly = ">=5.18.0,<6.0.0"
@@ -43,7 +43,7 @@ pydantic = ">=2.5.2,<3.0.0"
43
43
  python-dateutil = ">=2.8.2,<3.0.0"
44
44
  requests = ">=2.20.0,<3.0.0"
45
45
  scipy = ">=1.11.4,<2.0.0"
46
- statsmodels = ">=0.14.0,<1.0.0"
46
+ statsmodels = ">=0.14.0,!=0.14.2,<1.0.0"
47
47
 
48
48
  [tool.poetry.group.dev.dependencies]
49
49
  black = ">=24.4.2,<25.0.0"
File without changes
File without changes