openseries 1.3.7__py3-none-any.whl → 1.3.9__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.
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import datetime as dt
6
+ from inspect import stack
6
7
  from json import dump
7
8
  from math import ceil
8
9
  from pathlib import Path
@@ -11,8 +12,9 @@ from string import ascii_letters
11
12
  from typing import Any, Optional, Union, cast
12
13
 
13
14
  from numpy import log, sqrt
14
- from openpyxl import Workbook
15
15
  from openpyxl.utils.dataframe import dataframe_to_rows
16
+ from openpyxl.workbook.workbook import Workbook
17
+ from openpyxl.worksheet.worksheet import Worksheet
16
18
  from pandas import DataFrame, DatetimeIndex, Series
17
19
  from plotly.graph_objs import Figure # type: ignore[import-untyped]
18
20
  from plotly.offline import plot # type: ignore[import-untyped]
@@ -23,6 +25,7 @@ from openseries.datefixer import get_calc_range
23
25
  from openseries.load_plotly import load_plotly_dict
24
26
  from openseries.risk import cvar_down_calc, drawdown_series, var_down_calc
25
27
  from openseries.types import (
28
+ DaysInYearType,
26
29
  LiteralBarPlotMode,
27
30
  LiteralLinePlotMode,
28
31
  LiteralNanMethod,
@@ -466,8 +469,10 @@ class CommonModel(BaseModel): # type: ignore[misc]
466
469
  """
467
470
  if directory:
468
471
  dirpath = Path(directory).resolve()
472
+ elif Path.home().joinpath("Documents").exists():
473
+ dirpath = Path.home().joinpath("Documents")
469
474
  else:
470
- dirpath = Path(__file__).resolve().parent
475
+ dirpath = Path(stack()[1].filename).parent
471
476
 
472
477
  cleaner_list = ["label", "tsdf"]
473
478
  data = dict(self.__dict__)
@@ -500,6 +505,8 @@ class CommonModel(BaseModel): # type: ignore[misc]
500
505
  filename: str,
501
506
  sheet_title: Optional[str] = None,
502
507
  directory: Optional[DirectoryPath] = None,
508
+ *,
509
+ overwrite: bool = True,
503
510
  ) -> str:
504
511
  """
505
512
  Save .tsdf DataFrame to an Excel spreadsheet file.
@@ -512,6 +519,8 @@ class CommonModel(BaseModel): # type: ignore[misc]
512
519
  Name of the sheet in the Excel file
513
520
  directory: DirectoryPath, optional
514
521
  The file directory where the Excel file is saved.
522
+ overwrite: bool, default: True
523
+ Flag whether to overwrite an existing file
515
524
 
516
525
  Returns
517
526
  -------
@@ -521,20 +530,28 @@ class CommonModel(BaseModel): # type: ignore[misc]
521
530
  if filename[-5:].lower() != ".xlsx":
522
531
  msg = "Filename must end with .xlsx"
523
532
  raise NameError(msg)
533
+
524
534
  if directory:
525
535
  dirpath = Path(directory).resolve()
536
+ elif Path.home().joinpath("Documents").exists():
537
+ dirpath = Path.home().joinpath("Documents")
526
538
  else:
527
- dirpath = Path(__file__).resolve().parent
539
+ dirpath = Path(stack()[1].filename).parent
540
+
528
541
  sheetfile = dirpath.joinpath(filename)
529
542
 
530
543
  wrkbook = Workbook()
531
544
  wrksheet = wrkbook.active
532
545
 
533
546
  if sheet_title:
534
- wrksheet.title = sheet_title # type: ignore[union-attr]
547
+ cast(Worksheet, wrksheet).title = sheet_title
535
548
 
536
549
  for row in dataframe_to_rows(df=self.tsdf, index=True, header=True):
537
- wrksheet.append(row) # type: ignore[union-attr]
550
+ cast(Worksheet, wrksheet).append(row)
551
+
552
+ if not overwrite and Path(sheetfile).exists():
553
+ msg = f"{sheetfile!s} already exists."
554
+ raise FileExistsError(msg)
538
555
 
539
556
  wrkbook.save(sheetfile)
540
557
 
@@ -547,9 +564,10 @@ class CommonModel(BaseModel): # type: ignore[misc]
547
564
  filename: Optional[str] = None,
548
565
  directory: Optional[DirectoryPath] = None,
549
566
  labels: Optional[list[str]] = None,
550
- auto_open: bool = True, # noqa: FBT001, FBT002
551
- add_logo: bool = True, # noqa: FBT001, FBT002
552
567
  output_type: LiteralPlotlyOutput = "file",
568
+ *,
569
+ auto_open: bool = True,
570
+ add_logo: bool = True,
553
571
  ) -> tuple[Figure, str]:
554
572
  """
555
573
  Create a Plotly Bar Figure.
@@ -569,12 +587,12 @@ class CommonModel(BaseModel): # type: ignore[misc]
569
587
  labels: list[str], optional
570
588
  A list of labels to manually override using the names of
571
589
  the input self.tsdf
590
+ output_type: LiteralPlotlyOutput, default: "file"
591
+ Determines output type
572
592
  auto_open: bool, default: True
573
593
  Determines whether to open a browser window with the plot
574
594
  add_logo: bool, default: True
575
595
  If True a Captor logo is added to the plot
576
- output_type: LiteralPlotlyOutput, default: "file"
577
- Determines output type
578
596
 
579
597
  Returns
580
598
  -------
@@ -641,10 +659,11 @@ class CommonModel(BaseModel): # type: ignore[misc]
641
659
  filename: Optional[str] = None,
642
660
  directory: Optional[DirectoryPath] = None,
643
661
  labels: Optional[list[str]] = None,
644
- auto_open: bool = True, # noqa: FBT001, FBT002
645
- add_logo: bool = True, # noqa: FBT001, FBT002
646
- show_last: bool = False, # noqa: FBT001, FBT002
647
662
  output_type: LiteralPlotlyOutput = "file",
663
+ *,
664
+ auto_open: bool = True,
665
+ add_logo: bool = True,
666
+ show_last: bool = False,
648
667
  ) -> tuple[Figure, str]:
649
668
  """
650
669
  Create a Plotly Scatter Figure.
@@ -664,14 +683,14 @@ class CommonModel(BaseModel): # type: ignore[misc]
664
683
  labels: list[str], optional
665
684
  A list of labels to manually override using the names of
666
685
  the input self.tsdf
686
+ output_type: LiteralPlotlyOutput, default: "file"
687
+ Determines output type
667
688
  auto_open: bool, default: True
668
689
  Determines whether to open a browser window with the plot
669
690
  add_logo: bool, default: True
670
691
  If True a Captor logo is added to the plot
671
692
  show_last: bool, default: False
672
693
  If True the last self.tsdf point is highlighted as red dot with a label
673
- output_type: LiteralPlotlyOutput, default: "file"
674
- Determines output type
675
694
 
676
695
  Returns
677
696
  -------
@@ -751,7 +770,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
751
770
  months_from_last: Optional[int] = None,
752
771
  from_date: Optional[dt.date] = None,
753
772
  to_date: Optional[dt.date] = None,
754
- periods_in_a_year_fixed: Optional[int] = None,
773
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
755
774
  ) -> Union[float, Series[type[float]]]:
756
775
  """
757
776
  https://www.investopedia.com/terms/a/arithmeticmean.asp.
@@ -765,7 +784,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
765
784
  Specific from date
766
785
  to_date : datetime.date, optional
767
786
  Specific to date
768
- periods_in_a_year_fixed : int, optional
787
+ periods_in_a_year_fixed : DaysInYearType, optional
769
788
  Allows locking the periods-in-a-year to simplify test cases and
770
789
  comparisons
771
790
 
@@ -811,8 +830,8 @@ class CommonModel(BaseModel): # type: ignore[misc]
811
830
  months_from_last: Optional[int] = None,
812
831
  from_date: Optional[dt.date] = None,
813
832
  to_date: Optional[dt.date] = None,
814
- periods_in_a_year_fixed: Optional[int] = None,
815
- ) -> Union[float, Series]: # type: ignore[type-arg]
833
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
834
+ ) -> Union[float, Series[type[float]]]:
816
835
  """
817
836
  Annualized volatility.
818
837
 
@@ -828,7 +847,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
828
847
  Specific from date
829
848
  to_date : datetime.date, optional
830
849
  Specific to date
831
- periods_in_a_year_fixed : int, optional
850
+ periods_in_a_year_fixed : DaysInYearType, optional
832
851
  Allows locking the periods-in-a-year to simplify test cases and comparisons
833
852
 
834
853
  Returns
@@ -851,9 +870,9 @@ class CommonModel(BaseModel): # type: ignore[misc]
851
870
  )
852
871
  time_factor = how_many / fraction
853
872
 
854
- result = self.tsdf.loc[cast(int, earlier) : cast(int, later)]
855
- result = result.ffill()
856
- result = result.pct_change().std() * sqrt(time_factor)
873
+ data = self.tsdf.loc[cast(int, earlier) : cast(int, later)]
874
+ data = data.ffill()
875
+ result = data.pct_change().std().mul(sqrt(time_factor))
857
876
 
858
877
  if self.tsdf.shape[1] == 1:
859
878
  return float(result.iloc[0])
@@ -871,8 +890,9 @@ class CommonModel(BaseModel): # type: ignore[misc]
871
890
  from_date: Optional[dt.date] = None,
872
891
  to_date: Optional[dt.date] = None,
873
892
  interpolation: LiteralQuantileInterp = "lower",
874
- drift_adjust: bool = False, # noqa: FBT001, FBT002
875
- periods_in_a_year_fixed: Optional[int] = None,
893
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
894
+ *,
895
+ drift_adjust: bool = False,
876
896
  ) -> Union[float, Series[type[float]]]:
877
897
  """
878
898
  Implied annualized volatility.
@@ -893,11 +913,11 @@ class CommonModel(BaseModel): # type: ignore[misc]
893
913
  Specific to date
894
914
  interpolation: LiteralQuantileInterp, default: "lower"
895
915
  type of interpolation in Pandas.DataFrame.quantile() function.
896
- drift_adjust: bool, default: False
897
- An adjustment to remove the bias implied by the average return
898
- periods_in_a_year_fixed : int, optional
916
+ periods_in_a_year_fixed : DaysInYearType, optional
899
917
  Allows locking the periods-in-a-year to simplify test cases and
900
918
  comparisons
919
+ drift_adjust: bool, default: False
920
+ An adjustment to remove the bias implied by the average return
901
921
 
902
922
  Returns
903
923
  -------
@@ -926,8 +946,9 @@ class CommonModel(BaseModel): # type: ignore[misc]
926
946
  from_date: Optional[dt.date] = None,
927
947
  to_date: Optional[dt.date] = None,
928
948
  interpolation: LiteralQuantileInterp = "lower",
929
- drift_adjust: bool = False, # noqa: FBT001, FBT002
930
- periods_in_a_year_fixed: Optional[int] = None,
949
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
950
+ *,
951
+ drift_adjust: bool = False,
931
952
  ) -> Union[float, Series[type[float]]]:
932
953
  """
933
954
  Target weight from VaR.
@@ -954,11 +975,11 @@ class CommonModel(BaseModel): # type: ignore[misc]
954
975
  Specific to date
955
976
  interpolation: LiteralQuantileInterp, default: "lower"
956
977
  type of interpolation in Pandas.DataFrame.quantile() function.
957
- drift_adjust: bool, default: False
958
- An adjustment to remove the bias implied by the average return
959
- periods_in_a_year_fixed : int, optional
978
+ periods_in_a_year_fixed : DaysInYearType, optional
960
979
  Allows locking the periods-in-a-year to simplify test cases and
961
980
  comparisons
981
+ drift_adjust: bool, default: False
982
+ An adjustment to remove the bias implied by the average return
962
983
 
963
984
  Returns
964
985
  -------
@@ -1050,7 +1071,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
1050
1071
  months_from_last: Optional[int] = None,
1051
1072
  from_date: Optional[dt.date] = None,
1052
1073
  to_date: Optional[dt.date] = None,
1053
- periods_in_a_year_fixed: Optional[int] = None,
1074
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
1054
1075
  ) -> Union[float, Series[type[float]]]:
1055
1076
  """
1056
1077
  Downside Deviation.
@@ -1070,7 +1091,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
1070
1091
  Specific from date
1071
1092
  to_date : datetime.date, optional
1072
1093
  Specific to date
1073
- periods_in_a_year_fixed : int, optional
1094
+ periods_in_a_year_fixed : DaysInYearType, optional
1074
1095
  Allows locking the periods-in-a-year to simplify test cases and
1075
1096
  comparisons
1076
1097
 
@@ -1410,7 +1431,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
1410
1431
  months_from_last: Optional[int] = None,
1411
1432
  from_date: Optional[dt.date] = None,
1412
1433
  to_date: Optional[dt.date] = None,
1413
- periods_in_a_year_fixed: Optional[int] = None,
1434
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
1414
1435
  ) -> Union[float, Series[type[float]]]:
1415
1436
  """
1416
1437
  Ratio between arithmetic mean of returns and annualized volatility.
@@ -1432,7 +1453,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
1432
1453
  Specific from date
1433
1454
  to_date : datetime.date, optional
1434
1455
  Specific to date
1435
- periods_in_a_year_fixed : int, optional
1456
+ periods_in_a_year_fixed : DaysInYearType, optional
1436
1457
  Allows locking the periods-in-a-year to simplify test cases and
1437
1458
  comparisons
1438
1459
 
@@ -1470,7 +1491,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
1470
1491
  months_from_last: Optional[int] = None,
1471
1492
  from_date: Optional[dt.date] = None,
1472
1493
  to_date: Optional[dt.date] = None,
1473
- periods_in_a_year_fixed: Optional[int] = None,
1494
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
1474
1495
  ) -> Union[float, Series[type[float]]]:
1475
1496
  """
1476
1497
  Sortino Ratio.
@@ -1494,7 +1515,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
1494
1515
  Specific from date
1495
1516
  to_date : datetime.date, optional
1496
1517
  Specific to date
1497
- periods_in_a_year_fixed : int, optional
1518
+ periods_in_a_year_fixed : DaysInYearType, optional
1498
1519
  Allows locking the periods-in-a-year to simplify test cases and
1499
1520
  comparisons
1500
1521
 
@@ -1875,7 +1896,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
1875
1896
  self: CommonModel,
1876
1897
  column: int = 0,
1877
1898
  observations: int = 21,
1878
- periods_in_a_year_fixed: Optional[int] = None,
1899
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
1879
1900
  ) -> DataFrame:
1880
1901
  """
1881
1902
  Calculate rolling annualised volatilities.
@@ -1886,7 +1907,7 @@ class CommonModel(BaseModel): # type: ignore[misc]
1886
1907
  Position as integer of column to calculate
1887
1908
  observations: int, default: 21
1888
1909
  Number of observations in the overlapping window.
1889
- periods_in_a_year_fixed : int, optional
1910
+ periods_in_a_year_fixed : DaysInYearType, optional
1890
1911
  Allows locking the periods-in-a-year to simplify test cases and
1891
1912
  comparisons
1892
1913
 
@@ -1926,8 +1947,9 @@ def _var_implied_vol_and_target_func(
1926
1947
  from_date: Optional[dt.date] = None,
1927
1948
  to_date: Optional[dt.date] = None,
1928
1949
  interpolation: LiteralQuantileInterp = "lower",
1929
- drift_adjust: bool = False, # noqa: FBT001, FBT002
1930
- periods_in_a_year_fixed: Optional[int] = None,
1950
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
1951
+ *,
1952
+ drift_adjust: bool = False,
1931
1953
  ) -> Union[float, Series[type[float]]]:
1932
1954
  """
1933
1955
  Volatility implied from VaR or Target Weight.
@@ -1958,11 +1980,11 @@ def _var_implied_vol_and_target_func(
1958
1980
  Specific to date
1959
1981
  interpolation: LiteralQuantileInterp, default: "lower"
1960
1982
  type of interpolation in Pandas.DataFrame.quantile() function.
1961
- drift_adjust: bool, default: False
1962
- An adjustment to remove the bias implied by the average return
1963
- periods_in_a_year_fixed : int, optional
1983
+ periods_in_a_year_fixed : DaysInYearType, optional
1964
1984
  Allows locking the periods-in-a-year to simplify test cases and
1965
1985
  comparisons
1986
+ drift_adjust: bool, default: False
1987
+ An adjustment to remove the bias implied by the average return
1966
1988
 
1967
1989
  Returns
1968
1990
  -------
openseries/datefixer.py CHANGED
@@ -124,10 +124,11 @@ def date_fix(
124
124
  def date_offset_foll(
125
125
  raw_date: DateType,
126
126
  months_offset: int = 12,
127
- adjust: bool = False, # noqa: FBT001, FBT002
128
- following: bool = True, # noqa: FBT001, FBT002
129
127
  countries: CountriesType = "SE",
130
128
  custom_holidays: Optional[HolidayType] = None,
129
+ *,
130
+ adjust: bool = False,
131
+ following: bool = True,
131
132
  ) -> dt.date:
132
133
  """
133
134
  Offset dates according to a given calendar.
@@ -138,15 +139,15 @@ def date_offset_foll(
138
139
  The date to offset from
139
140
  months_offset: int, default: 12
140
141
  Number of months as integer
141
- adjust: bool, default: False
142
- Determines if offset should adjust for business days
143
- following: bool, default: True
144
- Determines if days should be offset forward (following) or backward
145
142
  countries: CountriesType, default: "SE"
146
143
  (List of) country code(s) according to ISO 3166-1 alpha-2
147
144
  custom_holidays: HolidayType, optional
148
145
  Argument where missing holidays can be added as
149
146
  {"2021-02-12": "Jack's birthday"} or ["2021-02-12"]
147
+ adjust: bool, default: False
148
+ Determines if offset should adjust for business days
149
+ following: bool, default: True
150
+ Determines if days should be offset forward (following) or backward
150
151
 
151
152
  Returns
152
153
  -------
openseries/frame.py CHANGED
@@ -44,6 +44,7 @@ from openseries.risk import (
44
44
  from openseries.series import OpenTimeSeries
45
45
  from openseries.types import (
46
46
  CountriesType,
47
+ DaysInYearType,
47
48
  LiteralBizDayFreq,
48
49
  LiteralCaptureRatio,
49
50
  LiteralCovMethod,
@@ -70,7 +71,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
70
71
 
71
72
  Parameters
72
73
  ----------
73
- constituents: list[TypeOpenTimeSeries]
74
+ constituents: list[OpenTimeSeries]
74
75
  List of objects of Class OpenTimeSeries
75
76
  weights: list[float], optional
76
77
  List of weights in float format.
@@ -110,7 +111,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
110
111
 
111
112
  Parameters
112
113
  ----------
113
- constituents: list[TypeOpenTimeSeries]
114
+ constituents: list[OpenTimeSeries]
114
115
  List of objects of Class OpenTimeSeries
115
116
  weights: list[float], optional
116
117
  List of weights in float format.
@@ -254,6 +255,11 @@ class OpenFrame(CommonModel): # type: ignore[misc]
254
255
  """
255
256
  Align the index of .tsdf with local calendar business days.
256
257
 
258
+ Parameters
259
+ ----------
260
+ countries: CountriesType, default: "SE"
261
+ (List of) country code(s) according to ISO 3166-1 alpha-2
262
+
257
263
  Returns
258
264
  -------
259
265
  OpenFrame
@@ -307,13 +313,13 @@ class OpenFrame(CommonModel): # type: ignore[misc]
307
313
  return list(self.tsdf.columns.get_level_values(0))
308
314
 
309
315
  @property
310
- def columns_lvl_one(self: OpenFrame) -> list[str]:
316
+ def columns_lvl_one(self: OpenFrame) -> list[ValueType]:
311
317
  """
312
318
  Level 1 values of the MultiIndex columns in the .tsdf DataFrame.
313
319
 
314
320
  Returns
315
321
  -------
316
- list[str]
322
+ list[ValueType]
317
323
  Level 1 values of the MultiIndex columns in the .tsdf DataFrame
318
324
  """
319
325
  return list(self.tsdf.columns.get_level_values(1))
@@ -589,7 +595,6 @@ class OpenFrame(CommonModel): # type: ignore[misc]
589
595
  ----------
590
596
  freq: Union[LiteralBizDayFreq, str], default "BM"
591
597
  The date offset string that sets the resampled frequency
592
- Examples are "7D", "B", "M", "BM", "Q", "BQ", "A", "BA"
593
598
 
594
599
  Returns
595
600
  -------
@@ -624,7 +629,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
624
629
 
625
630
  Parameters
626
631
  ----------
627
- freq: LiteralBizDayFreq, default BM
632
+ freq: LiteralBizDayFreq, default "BM"
628
633
  The date offset string that sets the resampled frequency
629
634
  countries: CountriesType, default: "SE"
630
635
  (List of) country code(s) according to ISO 3166-1 alpha-2
@@ -694,7 +699,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
694
699
  months_from_last: Optional[int] = None,
695
700
  from_date: Optional[dt.date] = None,
696
701
  to_date: Optional[dt.date] = None,
697
- periods_in_a_year_fixed: Optional[int] = None,
702
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
698
703
  ) -> DataFrame:
699
704
  """
700
705
  Exponentially Weighted Moving Average Volatilities and Correlation.
@@ -721,7 +726,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
721
726
  Specific from date
722
727
  to_date : datetime.date, optional
723
728
  Specific to date
724
- periods_in_a_year_fixed : int, optional
729
+ periods_in_a_year_fixed : DaysInYearType, optional
725
730
  Allows locking the periods-in-a-year to simplify test cases and
726
731
  comparisons
727
732
 
@@ -753,30 +758,30 @@ class OpenFrame(CommonModel): # type: ignore[misc]
753
758
  data = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy()
754
759
 
755
760
  for rtn in cols:
756
- data[rtn, "Returns"] = (
761
+ data[rtn, ValueType.RTRN] = (
757
762
  data.loc[:, (rtn, ValueType.PRICE)] # type: ignore[index]
758
763
  .apply(log)
759
764
  .diff()
760
765
  )
761
766
 
762
767
  raw_one = [
763
- data.loc[:, (cols[0], "Returns")] # type: ignore[index]
768
+ data.loc[:, (cols[0], ValueType.RTRN)] # type: ignore[index]
764
769
  .iloc[1:day_chunk]
765
770
  .std(ddof=dlta_degr_freedms)
766
771
  * sqrt(time_factor),
767
772
  ]
768
773
  raw_two = [
769
- data.loc[:, (cols[1], "Returns")] # type: ignore[index]
774
+ data.loc[:, (cols[1], ValueType.RTRN)] # type: ignore[index]
770
775
  .iloc[1:day_chunk]
771
776
  .std(ddof=dlta_degr_freedms)
772
777
  * sqrt(time_factor),
773
778
  ]
774
779
  raw_cov = [
775
780
  cov(
776
- m=data.loc[:, (cols[0], "Returns")] # type: ignore[index]
781
+ m=data.loc[:, (cols[0], ValueType.RTRN)] # type: ignore[index]
777
782
  .iloc[1:day_chunk]
778
783
  .to_numpy(),
779
- y=data.loc[:, (cols[1], "Returns")] # type: ignore[index]
784
+ y=data.loc[:, (cols[1], ValueType.RTRN)] # type: ignore[index]
780
785
  .iloc[1:day_chunk]
781
786
  .to_numpy(),
782
787
  ddof=dlta_degr_freedms,
@@ -786,20 +791,20 @@ class OpenFrame(CommonModel): # type: ignore[misc]
786
791
 
787
792
  for _, row in data.iloc[1:].iterrows():
788
793
  tmp_raw_one = ewma_calc(
789
- reeturn=row.loc[cols[0], "Returns"],
794
+ reeturn=row.loc[cols[0], ValueType.RTRN],
790
795
  prev_ewma=raw_one[-1],
791
796
  time_factor=time_factor,
792
797
  lmbda=lmbda,
793
798
  )
794
799
  tmp_raw_two = ewma_calc(
795
- reeturn=row.loc[cols[1], "Returns"],
800
+ reeturn=row.loc[cols[1], ValueType.RTRN],
796
801
  prev_ewma=raw_two[-1],
797
802
  time_factor=time_factor,
798
803
  lmbda=lmbda,
799
804
  )
800
805
  tmp_raw_cov = (
801
- row.loc[cols[0], "Returns"]
802
- * row.loc[cols[1], "Returns"]
806
+ row.loc[cols[0], ValueType.RTRN]
807
+ * row.loc[cols[1], ValueType.RTRN]
803
808
  * time_factor
804
809
  * (1 - lmbda)
805
810
  + raw_cov[-1] * lmbda
@@ -922,28 +927,27 @@ class OpenFrame(CommonModel): # type: ignore[misc]
922
927
  copy=False,
923
928
  )
924
929
  if len(set(self.first_indices)) != 1:
925
- warning(
926
- msg=(
927
- "One or more constituents still "
928
- "not truncated to same start dates."
929
- ),
930
- extra={"tsdf.head": self.tsdf.head()},
930
+ msg = (
931
+ f"One or more constituents still "
932
+ f"not truncated to same start dates.\n"
933
+ f"{self.tsdf.head()}"
931
934
  )
935
+ warning(msg=msg)
932
936
  if len(set(self.last_indices)) != 1:
933
- warning(
934
- msg=(
935
- "One or more constituents still "
936
- "not truncated to same end dates."
937
- ),
938
- extra={"tsdf.tail": self.tsdf.tail()},
937
+ msg = (
938
+ f"One or more constituents still "
939
+ f"not truncated to same end dates.\n"
940
+ f"{self.tsdf.tail()}"
939
941
  )
942
+ warning(msg=msg)
940
943
  return self
941
944
 
942
945
  def relative(
943
946
  self: OpenFrame,
944
947
  long_column: int = 0,
945
948
  short_column: int = 1,
946
- base_zero: bool = True, # noqa: FBT001, FBT002
949
+ *,
950
+ base_zero: bool = True,
947
951
  ) -> None:
948
952
  """
949
953
  Calculate cumulative relative return between two series.
@@ -971,6 +975,9 @@ class OpenFrame(CommonModel): # type: ignore[misc]
971
975
  self.tsdf[rel_label, ValueType.RELRTRN] = (
972
976
  1.0 + self.tsdf.iloc[:, long_column] - self.tsdf.iloc[:, short_column]
973
977
  )
978
+ self.constituents += [
979
+ OpenTimeSeries.from_df(self.tsdf.iloc[:, -1]),
980
+ ]
974
981
 
975
982
  def tracking_error_func(
976
983
  self: OpenFrame,
@@ -978,7 +985,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
978
985
  months_from_last: Optional[int] = None,
979
986
  from_date: Optional[dt.date] = None,
980
987
  to_date: Optional[dt.date] = None,
981
- periods_in_a_year_fixed: Optional[int] = None,
988
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
982
989
  ) -> Series[type[float]]:
983
990
  """
984
991
  Tracking Error.
@@ -998,7 +1005,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
998
1005
  Specific from date
999
1006
  to_date : datetime.date, optional
1000
1007
  Specific to date
1001
- periods_in_a_year_fixed : int, optional
1008
+ periods_in_a_year_fixed : DaysInYearType, optional
1002
1009
  Allows locking the periods-in-a-year to simplify test cases and
1003
1010
  comparisons
1004
1011
 
@@ -1063,7 +1070,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
1063
1070
  months_from_last: Optional[int] = None,
1064
1071
  from_date: Optional[dt.date] = None,
1065
1072
  to_date: Optional[dt.date] = None,
1066
- periods_in_a_year_fixed: Optional[int] = None,
1073
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
1067
1074
  ) -> Series[type[float]]:
1068
1075
  """
1069
1076
  Information Ratio.
@@ -1084,7 +1091,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
1084
1091
  Specific from date
1085
1092
  to_date : datetime.date, optional
1086
1093
  Specific to date
1087
- periods_in_a_year_fixed : int, optional
1094
+ periods_in_a_year_fixed : DaysInYearType, optional
1088
1095
  Allows locking the periods-in-a-year to simplify test cases and
1089
1096
  comparisons
1090
1097
 
@@ -1157,7 +1164,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
1157
1164
  months_from_last: Optional[int] = None,
1158
1165
  from_date: Optional[dt.date] = None,
1159
1166
  to_date: Optional[dt.date] = None,
1160
- periods_in_a_year_fixed: Optional[int] = None,
1167
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
1161
1168
  ) -> Series[type[float]]:
1162
1169
  """
1163
1170
  Capture Ratio.
@@ -1184,7 +1191,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
1184
1191
  Specific from date
1185
1192
  to_date : datetime.date, optional
1186
1193
  Specific to date
1187
- periods_in_a_year_fixed : int, optional
1194
+ periods_in_a_year_fixed : DaysInYearType, optional
1188
1195
  Allows locking the periods-in-a-year to simplify test cases and
1189
1196
  comparisons
1190
1197
 
@@ -1429,9 +1436,10 @@ class OpenFrame(CommonModel): # type: ignore[misc]
1429
1436
  self: OpenFrame,
1430
1437
  y_column: Union[tuple[str, ValueType], int],
1431
1438
  x_column: Union[tuple[str, ValueType], int],
1432
- fitted_series: bool = True, # noqa: FBT001, FBT002
1433
1439
  method: LiteralOlsFitMethod = "pinv",
1434
1440
  cov_type: LiteralOlsFitCovType = "nonrobust",
1441
+ *,
1442
+ fitted_series: bool = True,
1435
1443
  ) -> RegressionResults:
1436
1444
  """
1437
1445
  Ordinary Least Squares fit.
@@ -1446,12 +1454,12 @@ class OpenFrame(CommonModel): # type: ignore[misc]
1446
1454
  The column level values of the dependent variable y
1447
1455
  x_column: Union[tuple[str, ValueType], int]
1448
1456
  The column level values of the exogenous variable x
1449
- fitted_series: bool, default: True
1450
- If True the fit is added as a new column in the .tsdf Pandas.DataFrame
1451
1457
  method: LiteralOlsFitMethod, default: pinv
1452
1458
  Method to solve least squares problem
1453
1459
  cov_type: LiteralOlsFitCovType, default: nonrobust
1454
1460
  Covariance estimator
1461
+ fitted_series: bool, default: True
1462
+ If True the fit is added as a new column in the .tsdf Pandas.DataFrame
1455
1463
 
1456
1464
  Returns
1457
1465
  -------
@@ -1598,7 +1606,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
1598
1606
  long_column: int = 0,
1599
1607
  short_column: int = 1,
1600
1608
  observations: int = 21,
1601
- periods_in_a_year_fixed: Optional[int] = None,
1609
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
1602
1610
  ) -> DataFrame:
1603
1611
  """
1604
1612
  Calculate rolling Information Ratio.
@@ -1615,7 +1623,7 @@ class OpenFrame(CommonModel): # type: ignore[misc]
1615
1623
  Column of timeseries that is the denominator in the ratio.
1616
1624
  observations: int, default: 21
1617
1625
  The length of the rolling window to use is set as number of observations.
1618
- periods_in_a_year_fixed : int, optional
1626
+ periods_in_a_year_fixed : DaysInYearType, optional
1619
1627
  Allows locking the periods-in-a-year to simplify test cases and comparisons
1620
1628
 
1621
1629
  Returns
openseries/load_plotly.py CHANGED
@@ -2,13 +2,42 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  from json import load
5
+ from logging import warning
5
6
  from pathlib import Path
6
7
 
8
+ import requests
9
+
7
10
  from openseries.types import CaptorLogoType, PlotlyLayoutType
8
11
 
9
12
 
13
+ def check_remote_file_existence(url: str) -> bool:
14
+ """
15
+ Check if remote file exists.
16
+
17
+ Parameters
18
+ ----------
19
+ url: str
20
+ Path to remote file
21
+
22
+ Returns
23
+ -------
24
+ bool
25
+ True if url is valid and False otherwise
26
+ """
27
+ ok_code = 200
28
+
29
+ try:
30
+ response = requests.head(url, timeout=30)
31
+ if response.status_code != ok_code:
32
+ return False
33
+ except requests.exceptions.ConnectionError:
34
+ return False
35
+ return True
36
+
37
+
10
38
  def load_plotly_dict(
11
- responsive: bool = True, # noqa: FBT001, FBT002
39
+ *,
40
+ responsive: bool = True,
12
41
  ) -> tuple[PlotlyLayoutType, CaptorLogoType]:
13
42
  """
14
43
  Load Plotly defaults.
@@ -32,6 +61,11 @@ def load_plotly_dict(
32
61
  with Path.open(logofile, encoding="utf-8") as logo_file:
33
62
  logo = load(logo_file)
34
63
 
64
+ if check_remote_file_existence(url=logo["source"]) is False:
65
+ msg = f"Failed to add logo image from URL {logo['source']}"
66
+ warning(msg)
67
+ logo = {}
68
+
35
69
  fig["config"].update({"responsive": responsive})
36
70
 
37
71
  return fig, logo
openseries/series.py CHANGED
@@ -43,6 +43,7 @@ from openseries.types import (
43
43
  CurrencyStringType,
44
44
  DatabaseIdStringType,
45
45
  DateListType,
46
+ DaysInYearType,
46
47
  LiteralBizDayFreq,
47
48
  LiteralPandasReindexMethod,
48
49
  LiteralPandasResampleConvention,
@@ -163,7 +164,8 @@ class OpenTimeSeries(CommonModel): # type: ignore[misc]
163
164
  instrument_id: DatabaseIdStringType = "",
164
165
  isin: Optional[str] = None,
165
166
  baseccy: CurrencyStringType = "SEK",
166
- local_ccy: bool = True, # noqa: FBT001, FBT002
167
+ *,
168
+ local_ccy: bool = True,
167
169
  ) -> OpenTimeSeries:
168
170
  """
169
171
  Create series from a Pandas DataFrame or Series.
@@ -220,7 +222,8 @@ class OpenTimeSeries(CommonModel): # type: ignore[misc]
220
222
  column_nmbr: int = 0,
221
223
  valuetype: ValueType = ValueType.PRICE,
222
224
  baseccy: CurrencyStringType = "SEK",
223
- local_ccy: bool = True, # noqa: FBT001, FBT002
225
+ *,
226
+ local_ccy: bool = True,
224
227
  ) -> OpenTimeSeries:
225
228
  """
226
229
  Create series from a Pandas DataFrame or Series.
@@ -256,17 +259,16 @@ class OpenTimeSeries(CommonModel): # type: ignore[misc]
256
259
  dframe.columns.get_level_values(0).to_numpy()[column_nmbr],
257
260
  ):
258
261
  label = "Series"
259
- warning(msg="Label missing. Adding:", extra={"label": label})
262
+ msg = f"Label missing. Adding: {label}"
263
+ warning(msg=msg)
260
264
  else:
261
265
  label = dframe.columns.get_level_values(0).to_numpy()[column_nmbr]
262
266
  if check_if_none(
263
267
  dframe.columns.get_level_values(1).to_numpy()[column_nmbr],
264
268
  ):
265
269
  valuetype = ValueType.PRICE
266
- warning(
267
- msg="valuetype missing. Adding: ",
268
- extra={"valuetype": valuetype.value},
269
- )
270
+ msg = f"valuetype missing. Adding: {valuetype.value}"
271
+ warning(msg=msg)
270
272
  else:
271
273
  valuetype = dframe.columns.get_level_values(1).to_numpy()[
272
274
  column_nmbr
@@ -303,7 +305,8 @@ class OpenTimeSeries(CommonModel): # type: ignore[misc]
303
305
  label: str = "Series",
304
306
  valuetype: ValueType = ValueType.PRICE,
305
307
  baseccy: CurrencyStringType = "SEK",
306
- local_ccy: bool = True, # noqa: FBT001, FBT002
308
+ *,
309
+ local_ccy: bool = True,
307
310
  ) -> OpenTimeSeries:
308
311
  """
309
312
  Create series from values accruing with a given fixed rate return.
@@ -684,7 +687,7 @@ class OpenTimeSeries(CommonModel): # type: ignore[misc]
684
687
  months_from_last: Optional[int] = None,
685
688
  from_date: Optional[dt.date] = None,
686
689
  to_date: Optional[dt.date] = None,
687
- periods_in_a_year_fixed: Optional[int] = None,
690
+ periods_in_a_year_fixed: Optional[DaysInYearType] = None,
688
691
  ) -> DataFrame:
689
692
  """
690
693
  Exponentially Weighted Moving Average Model for Volatility.
@@ -706,7 +709,7 @@ class OpenTimeSeries(CommonModel): # type: ignore[misc]
706
709
  Specific from date
707
710
  to_date : datetime.date, optional
708
711
  Specific to date
709
- periods_in_a_year_fixed : int, optional
712
+ periods_in_a_year_fixed : DaysInYearType, optional
710
713
  Allows locking the periods-in-a-year to simplify test cases and comparisons
711
714
 
712
715
  Returns
@@ -727,18 +730,18 @@ class OpenTimeSeries(CommonModel): # type: ignore[misc]
727
730
 
728
731
  data = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy()
729
732
 
730
- data[self.label, "Returns"] = (
733
+ data[self.label, ValueType.RTRN] = (
731
734
  data.loc[:, self.tsdf.columns.to_numpy()[0]].apply(log).diff()
732
735
  )
733
736
 
734
737
  rawdata = [
735
- data.loc[:, (self.label, "Returns")] # type: ignore[index]
738
+ data.loc[:, (self.label, ValueType.RTRN)] # type: ignore[index]
736
739
  .iloc[1:day_chunk]
737
740
  .std(ddof=dlta_degr_freedms)
738
741
  * sqrt(time_factor),
739
742
  ]
740
743
 
741
- for item in data.loc[:, (self.label, "Returns")].iloc[ # type: ignore[index]
744
+ for item in data.loc[:, (self.label, ValueType.RTRN)].iloc[ # type: ignore[index]
742
745
  1:
743
746
  ]:
744
747
  previous = rawdata[-1]
@@ -818,7 +821,8 @@ class OpenTimeSeries(CommonModel): # type: ignore[misc]
818
821
  self: OpenTimeSeries,
819
822
  lvl_zero: Optional[str] = None,
820
823
  lvl_one: Optional[ValueType] = None,
821
- delete_lvl_one: bool = False, # noqa: FBT001, FBT002
824
+ *,
825
+ delete_lvl_one: bool = False,
822
826
  ) -> OpenTimeSeries:
823
827
  """
824
828
  Set the column labels of the .tsdf Pandas Dataframe.
@@ -827,7 +831,7 @@ class OpenTimeSeries(CommonModel): # type: ignore[misc]
827
831
  ----------
828
832
  lvl_zero: str, optional
829
833
  New level zero label
830
- lvl_one: str, optional
834
+ lvl_one: ValueType, optional
831
835
  New level one label
832
836
  delete_lvl_one: bool, default: False
833
837
  If True the level one label is deleted
@@ -869,7 +873,7 @@ def timeseries_chain(
869
873
  Earlier series to chain with
870
874
  back: TypeOpenTimeSeries
871
875
  Later series to chain with
872
- old_fee: bool, default: False
876
+ old_fee: float, default: 0.0
873
877
  Fee to apply to earlier series
874
878
 
875
879
  Returns
openseries/simulation.py CHANGED
@@ -818,8 +818,7 @@ class ReturnSimulation:
818
818
  Mean standard deviation
819
819
  seed: int
820
820
  Seed for random process initiation
821
- trading_days_in_year: DaysInYearType,
822
- default: 252
821
+ trading_days_in_year: DaysInYearType, default: 252
823
822
  Number of trading days used to annualize
824
823
 
825
824
  Returns
@@ -889,8 +888,7 @@ class ReturnSimulation:
889
888
  This is the rate of mean reversion for volatility in the Heston model
890
889
  seed: int
891
890
  Seed for random process initiation
892
- trading_days_in_year: DaysInYearType,
893
- default: 252
891
+ trading_days_in_year: DaysInYearType, default: 252
894
892
  Number of trading days used to annualize
895
893
 
896
894
  Returns
@@ -962,8 +960,7 @@ class ReturnSimulation:
962
960
  This is the rate of mean reversion for volatility in the Heston model
963
961
  seed: int
964
962
  Seed for random process initiation
965
- trading_days_in_year: DaysInYearType,
966
- default: 252
963
+ trading_days_in_year: DaysInYearType, default: 252
967
964
  Number of trading days used to annualize
968
965
 
969
966
  Returns
@@ -1038,8 +1035,7 @@ class ReturnSimulation:
1038
1035
  This is the average jump size
1039
1036
  seed: int
1040
1037
  Seed for random process initiation
1041
- trading_days_in_year: DaysInYearType,
1042
- default: 252
1038
+ trading_days_in_year: DaysInYearType, default: 252
1043
1039
  Number of trading days used to annualize
1044
1040
 
1045
1041
  Returns
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openseries
3
- Version: 1.3.7
3
+ Version: 1.3.9
4
4
  Summary: Package for simple financial time series analysis.
5
5
  Home-page: https://github.com/CaptorAB/OpenSeries
6
6
  License: BSD-3-Clause
@@ -20,14 +20,14 @@ Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Topic :: Office/Business :: Financial :: Investment
22
22
  Requires-Dist: ffn (>=0.3.7,<0.4.0)
23
- Requires-Dist: holidays (>=0.34,<0.35)
24
- Requires-Dist: numpy (>=1.25.2,<2.0.0)
23
+ Requires-Dist: holidays (>=0.35,<0.36)
24
+ Requires-Dist: numpy (>=1.26.1,<2.0.0)
25
25
  Requires-Dist: openpyxl (>=3.1.2,<4.0.0)
26
- Requires-Dist: pandas (>=2.0.3,<3.0.0)
26
+ Requires-Dist: pandas (>=2.1.1,<3.0.0)
27
27
  Requires-Dist: plotly (>=5.17.0,<6.0.0)
28
28
  Requires-Dist: pydantic (>=2.4.2,<3.0.0)
29
29
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
30
- Requires-Dist: scipy (>=1.11.2,<2.0.0)
30
+ Requires-Dist: scipy (>=1.11.3,<2.0.0)
31
31
  Requires-Dist: statsmodels (>=0.14.0,<0.15.0)
32
32
  Project-URL: Repository, https://github.com/CaptorAB/OpenSeries
33
33
  Description-Content-Type: text/markdown
@@ -0,0 +1,15 @@
1
+ openseries/__init__.py,sha256=hA7I5IFk88EnX6eyBbI1KLT_FGcmPIKF49xa5g3T8Yg,41
2
+ openseries/common_model.py,sha256=de3sFqvpHjxwJ3RSk80nzg98msGVVhqnhzW74QkS6Yk,66399
3
+ openseries/datefixer.py,sha256=vfZtT0YnfoLX8z9fq-B8SJJL8yq35SS21MiZYa5jthw,15898
4
+ openseries/frame.py,sha256=M_1ZVKh1AAJ3kWQuhLujnTwpgeddy_BR7Wn4bY5OCd4,62054
5
+ openseries/load_plotly.py,sha256=UBBjzByrKWjLRBNfwHJXQEs80QXaS_7EzaFu8TnrJxA,1803
6
+ openseries/plotly_captor_logo.json,sha256=pGMuPVu4cEO3ZsCH1wU03hxqbIQkHFNoJUs1k1WK89Y,178
7
+ openseries/plotly_layouts.json,sha256=xhrMOqW8LXb4QMtPiNBGdkPX518OHThiIJ68jpQk524,1429
8
+ openseries/risk.py,sha256=u2BZwmm5Ydbu03HdT-XjwHWkV-vZ0tUY3-Ok58qobVc,6312
9
+ openseries/series.py,sha256=GRPZpbhzwDSJR4mHY9Su33-K_Qaez-XXNZV9Iqn9UCw,30889
10
+ openseries/simulation.py,sha256=kPPIC4j0ceCLpvlSS21RCHJrIlC3s1SWWggyti1iMbw,35741
11
+ openseries/types.py,sha256=oZVcAT2-5-_ZMEU_r7FQZUFPU11REhHyUNOFu4joWk8,7775
12
+ openseries-1.3.9.dist-info/LICENSE.md,sha256=NJjeq3wyB7EnnHLmsdK1EK6zT00T1eB3FgAmHAPT_vM,1521
13
+ openseries-1.3.9.dist-info/METADATA,sha256=Q-g0RdwIf8vOOdctp-UonZKZELBauthls_j-a2iZC5Y,47751
14
+ openseries-1.3.9.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
15
+ openseries-1.3.9.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- openseries/__init__.py,sha256=hA7I5IFk88EnX6eyBbI1KLT_FGcmPIKF49xa5g3T8Yg,41
2
- openseries/common_model.py,sha256=QA0Ulwd8oeABaM1bYEgg4fai3P0ESANAeTYHARleapo,65754
3
- openseries/datefixer.py,sha256=HkHXkajI0QhkdAcvecH2yLki8B6Ne4Bk1iir-mKK5pw,15939
4
- openseries/frame.py,sha256=W34WKd8yUM__tUqRCIxlaY_wCnclai1KKDW4yQDaBSI,61778
5
- openseries/load_plotly.py,sha256=B6cKzd7ZnG9WWj3-oJS2v5UiEQUldPAz_npoq7OyLXs,1113
6
- openseries/plotly_captor_logo.json,sha256=pGMuPVu4cEO3ZsCH1wU03hxqbIQkHFNoJUs1k1WK89Y,178
7
- openseries/plotly_layouts.json,sha256=xhrMOqW8LXb4QMtPiNBGdkPX518OHThiIJ68jpQk524,1429
8
- openseries/risk.py,sha256=u2BZwmm5Ydbu03HdT-XjwHWkV-vZ0tUY3-Ok58qobVc,6312
9
- openseries/series.py,sha256=wpWpFGIJrn67ueoF4kdcgCjN4C8RLjsiZUcTWKCAZec,30925
10
- openseries/simulation.py,sha256=LSKHma-uVnUdGJlRvDK0TuPunp2snpby1gF93TYNe34,35789
11
- openseries/types.py,sha256=oZVcAT2-5-_ZMEU_r7FQZUFPU11REhHyUNOFu4joWk8,7775
12
- openseries-1.3.7.dist-info/LICENSE.md,sha256=NJjeq3wyB7EnnHLmsdK1EK6zT00T1eB3FgAmHAPT_vM,1521
13
- openseries-1.3.7.dist-info/METADATA,sha256=gZcCs6GDMcuZ8Yd9MNkjAors2423IGxg8ZLy3M1pP2E,47751
14
- openseries-1.3.7.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
15
- openseries-1.3.7.dist-info/RECORD,,