openseries 1.2.2__py3-none-any.whl → 1.2.4__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.
@@ -1,14 +1,13 @@
1
- """
2
- Defining the CommonModel class
3
- """
1
+ """Defining the CommonModel class."""
4
2
  import datetime as dt
5
3
  from json import dump
4
+ from math import ceil
5
+ from os import path
6
6
  from pathlib import Path
7
7
  from random import choices
8
8
  from string import ascii_letters
9
- from os import path
10
- from typing import Any, cast, Optional, TypeVar, Union
11
- from math import ceil
9
+ from typing import Any, Optional, TypeVar, Union, cast
10
+
12
11
  from numpy import cumprod, log, sqrt
13
12
  from openpyxl import Workbook
14
13
  from openpyxl.utils.dataframe import dataframe_to_rows
@@ -17,24 +16,25 @@ from plotly.graph_objs import Figure
17
16
  from plotly.offline import plot
18
17
  from scipy.stats import kurtosis, norm, skew
19
18
 
19
+ from openseries.datefixer import get_calc_range
20
+ from openseries.load_plotly import load_plotly_dict
20
21
  from openseries.risk import cvar_down_calc, drawdown_series, var_down_calc
21
22
  from openseries.types import (
22
23
  LiteralBarPlotMode,
23
24
  LiteralLinePlotMode,
24
- LiteralPlotlyOutput,
25
25
  LiteralNanMethod,
26
+ LiteralPlotlyOutput,
27
+ LiteralQuantileInterp,
26
28
  ValueType,
27
29
  )
28
- from openseries.load_plotly import load_plotly_dict
29
- from openseries.datefixer import get_calc_range
30
- from openseries.types import LiteralQuantileInterp
31
-
32
30
 
33
31
  TypeCommonModel = TypeVar("TypeCommonModel", bound="CommonModel")
34
32
 
35
33
 
36
34
  class CommonModel:
37
- """CommonModel declared
35
+
36
+ """
37
+ CommonModel declared.
38
38
 
39
39
  Parameters
40
40
  ----------
@@ -47,73 +47,80 @@ class CommonModel:
47
47
  @property
48
48
  def length(self: TypeCommonModel) -> int:
49
49
  """
50
+ Number of observations.
51
+
50
52
  Returns
51
53
  -------
52
54
  int
53
55
  Number of observations
54
56
  """
55
-
56
57
  return len(self.tsdf.index)
57
58
 
58
59
  @property
59
60
  def first_idx(self: TypeCommonModel) -> dt.date:
60
61
  """
62
+ The first date in the timeseries.
63
+
61
64
  Returns
62
65
  -------
63
66
  datetime.date
64
67
  The first date in the timeseries
65
68
  """
66
-
67
69
  return cast(dt.date, self.tsdf.index[0])
68
70
 
69
71
  @property
70
72
  def last_idx(self: TypeCommonModel) -> dt.date:
71
73
  """
74
+ The last date in the timeseries.
75
+
72
76
  Returns
73
77
  -------
74
78
  datetime.date
75
79
  The last date in the timeseries
76
80
  """
77
-
78
81
  return cast(dt.date, self.tsdf.index[-1])
79
82
 
80
83
  @property
81
84
  def span_of_days(self: TypeCommonModel) -> int:
82
85
  """
86
+ Number of days from the first date to the last.
87
+
83
88
  Returns
84
89
  -------
85
90
  int
86
91
  Number of days from the first date to the last
87
92
  """
88
-
89
93
  return (self.last_idx - self.first_idx).days
90
94
 
91
95
  @property
92
96
  def yearfrac(self: TypeCommonModel) -> float:
93
97
  """
98
+ Length of series expressed in years assuming all years have 365.25 days.
99
+
94
100
  Returns
95
101
  -------
96
102
  float
97
103
  Length of the timeseries expressed in years assuming all years
98
104
  have 365.25 days
99
105
  """
100
-
101
106
  return self.span_of_days / 365.25
102
107
 
103
108
  @property
104
109
  def periods_in_a_year(self: TypeCommonModel) -> float:
105
110
  """
111
+ The average number of observations per year.
112
+
106
113
  Returns
107
114
  -------
108
115
  float
109
116
  The average number of observations per year
110
117
  """
111
-
112
118
  return self.length / self.yearfrac
113
119
 
114
120
  @property
115
121
  def max_drawdown_cal_year(self: TypeCommonModel) -> Union[float, Series]:
116
- """https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
122
+ """
123
+ https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.
117
124
 
118
125
  Returns
119
126
  -------
@@ -125,7 +132,7 @@ class CommonModel:
125
132
  self.tsdf.groupby(years)
126
133
  .apply(
127
134
  lambda prices: (prices / prices.expanding(min_periods=1).max()).min()
128
- - 1
135
+ - 1,
129
136
  )
130
137
  .min()
131
138
  )
@@ -137,7 +144,8 @@ class CommonModel:
137
144
 
138
145
  @property
139
146
  def geo_ret(self: TypeCommonModel) -> Union[float, Series]:
140
- """https://www.investopedia.com/terms/c/cagr.asp
147
+ """
148
+ https://www.investopedia.com/terms/c/cagr.asp.
141
149
 
142
150
  Returns
143
151
  -------
@@ -148,19 +156,21 @@ class CommonModel:
148
156
 
149
157
  @property
150
158
  def arithmetic_ret(self: TypeCommonModel) -> Union[float, Series]:
151
- """https://www.investopedia.com/terms/a/arithmeticmean.asp
159
+ """
160
+ https://www.investopedia.com/terms/a/arithmeticmean.asp.
152
161
 
153
162
  Returns
154
163
  -------
155
164
  Union[float, Pandas.Series]
156
165
  Annualized arithmetic mean of returns
157
166
  """
158
-
159
167
  return self.arithmetic_ret_func()
160
168
 
161
169
  @property
162
170
  def value_ret(self: TypeCommonModel) -> Union[float, Series]:
163
171
  """
172
+ Simple return.
173
+
164
174
  Returns
165
175
  -------
166
176
  Union[float, Pandas.Series]
@@ -170,9 +180,11 @@ class CommonModel:
170
180
 
171
181
  @property
172
182
  def vol(self: TypeCommonModel) -> Union[float, Series]:
173
- """Based on Pandas .std() which is the equivalent of stdev.s([...])
174
- in MS Excel \n
175
- https://www.investopedia.com/terms/v/volatility.asp
183
+ """
184
+ Annualized volatility.
185
+
186
+ Based on Pandas .std() which is the equivalent of stdev.s([...]) in MS Excel.
187
+ https://www.investopedia.com/terms/v/volatility.asp.
176
188
 
177
189
  Returns
178
190
  -------
@@ -183,10 +195,12 @@ class CommonModel:
183
195
 
184
196
  @property
185
197
  def downside_deviation(self: TypeCommonModel) -> Union[float, Series]:
186
- """The standard deviation of returns that are below a Minimum Accepted
187
- Return of zero.
188
- It is used to calculate the Sortino Ratio \n
189
- https://www.investopedia.com/terms/d/downside-deviation.asp
198
+ """
199
+ Downside Deviation.
200
+
201
+ Standard deviation of returns that are below a Minimum Accepted Return
202
+ of zero. It is used to calculate the Sortino Ratio.
203
+ https://www.investopedia.com/terms/d/downside-deviation.asp.
190
204
 
191
205
  Returns
192
206
  -------
@@ -199,6 +213,8 @@ class CommonModel:
199
213
  @property
200
214
  def ret_vol_ratio(self: TypeCommonModel) -> Union[float, Series]:
201
215
  """
216
+ Ratio of annualized arithmetic mean of returns and annualized volatility.
217
+
202
218
  Returns
203
219
  -------
204
220
  Union[float, Pandas.Series]
@@ -210,7 +226,8 @@ class CommonModel:
210
226
 
211
227
  @property
212
228
  def sortino_ratio(self: TypeCommonModel) -> Union[float, Series]:
213
- """https://www.investopedia.com/terms/s/sortinoratio.asp
229
+ """
230
+ https://www.investopedia.com/terms/s/sortinoratio.asp.
214
231
 
215
232
  Returns
216
233
  -------
@@ -222,23 +239,26 @@ class CommonModel:
222
239
  riskfree_rate: float = 0.0
223
240
  minimum_accepted_return: float = 0.0
224
241
  return self.sortino_ratio_func(
225
- riskfree_rate=riskfree_rate, min_accepted_return=minimum_accepted_return
242
+ riskfree_rate=riskfree_rate,
243
+ min_accepted_return=minimum_accepted_return,
226
244
  )
227
245
 
228
246
  @property
229
247
  def z_score(self: TypeCommonModel) -> Union[float, Series]:
230
- """https://www.investopedia.com/terms/z/zscore.asp
248
+ """
249
+ https://www.investopedia.com/terms/z/zscore.asp.
231
250
 
232
251
  Returns
233
252
  -------
234
- float
253
+ Union[float, Pandas.Series]
235
254
  Z-score as (last return - mean return) / standard deviation of returns.
236
255
  """
237
256
  return self.z_score_func()
238
257
 
239
258
  @property
240
259
  def max_drawdown(self: TypeCommonModel) -> Union[float, Series]:
241
- """https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
260
+ """
261
+ https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.
242
262
 
243
263
  Returns
244
264
  -------
@@ -250,6 +270,8 @@ class CommonModel:
250
270
  @property
251
271
  def worst(self: TypeCommonModel) -> Union[float, Series]:
252
272
  """
273
+ Most negative percentage change.
274
+
253
275
  Returns
254
276
  -------
255
277
  Union[float, Pandas.Series]
@@ -261,6 +283,8 @@ class CommonModel:
261
283
  @property
262
284
  def positive_share(self: TypeCommonModel) -> Union[float, Series]:
263
285
  """
286
+ The share of percentage changes that are greater than zero.
287
+
264
288
  Returns
265
289
  -------
266
290
  Union[float, Pandas.Series]
@@ -270,7 +294,8 @@ class CommonModel:
270
294
 
271
295
  @property
272
296
  def skew(self: TypeCommonModel) -> Union[float, Series]:
273
- """https://www.investopedia.com/terms/s/skewness.asp
297
+ """
298
+ https://www.investopedia.com/terms/s/skewness.asp.
274
299
 
275
300
  Returns
276
301
  -------
@@ -281,7 +306,8 @@ class CommonModel:
281
306
 
282
307
  @property
283
308
  def kurtosis(self: TypeCommonModel) -> Union[float, Series]:
284
- """https://www.investopedia.com/terms/k/kurtosis.asp
309
+ """
310
+ https://www.investopedia.com/terms/k/kurtosis.asp.
285
311
 
286
312
  Returns
287
313
  -------
@@ -292,7 +318,8 @@ class CommonModel:
292
318
 
293
319
  @property
294
320
  def cvar_down(self: TypeCommonModel) -> Union[float, Series]:
295
- """https://www.investopedia.com/terms/c/conditional_value_at_risk.asp
321
+ """
322
+ https://www.investopedia.com/terms/c/conditional_value_at_risk.asp.
296
323
 
297
324
  Returns
298
325
  -------
@@ -304,14 +331,16 @@ class CommonModel:
304
331
 
305
332
  @property
306
333
  def var_down(self: TypeCommonModel) -> Union[float, Series]:
307
- """Downside 95% Value At Risk, "VaR". The equivalent of
308
- percentile.inc([...], 1-level) over returns in MS Excel \n
309
- https://www.investopedia.com/terms/v/var.asp
334
+ """
335
+ Downside 95% Value At Risk (VaR).
336
+
337
+ The equivalent of percentile.inc([...], 1-level) over returns in MS Excel.
338
+ https://www.investopedia.com/terms/v/var.asp.
310
339
 
311
340
  Returns
312
341
  -------
313
342
  Union[float, Pandas.Series]
314
- Downside 95% Value At Risk
343
+ Downside 95% Value At Risk (VaR)
315
344
  """
316
345
  level: float = 0.95
317
346
  interpolation: LiteralQuantileInterp = "lower"
@@ -320,6 +349,10 @@ class CommonModel:
320
349
  @property
321
350
  def vol_from_var(self: TypeCommonModel) -> Union[float, Series]:
322
351
  """
352
+ Implied annualized volatility from Downside 95% Value at Risk.
353
+
354
+ Assumes that returns are normally distributed.
355
+
323
356
  Returns
324
357
  -------
325
358
  Union[float, Pandas.Series]
@@ -331,15 +364,16 @@ class CommonModel:
331
364
  return self.vol_from_var_func(level=level, interpolation=interpolation)
332
365
 
333
366
  def value_to_log(self: TypeCommonModel) -> TypeCommonModel:
334
- """Converts a valueseries into logarithmic weighted series \n
335
- Equivalent to LN(value[t] / value[t=0]) in MS Excel
367
+ """
368
+ Series of values converted into logarithmic weighted series.
369
+
370
+ Equivalent to LN(value[t] / value[t=0]) in Excel.
336
371
 
337
372
  Returns
338
373
  -------
339
374
  self
340
375
  An object of the same class
341
376
  """
342
-
343
377
  self.tsdf = DataFrame(
344
378
  data=log(self.tsdf / self.tsdf.iloc[0]),
345
379
  index=self.tsdf.index,
@@ -348,9 +382,11 @@ class CommonModel:
348
382
  return self
349
383
 
350
384
  def value_nan_handle(
351
- self: TypeCommonModel, method: LiteralNanMethod = "fill"
385
+ self: TypeCommonModel,
386
+ method: LiteralNanMethod = "fill",
352
387
  ) -> TypeCommonModel:
353
- """Handling of missing values in a valueseries
388
+ """
389
+ Handle missing values in a valueseries.
354
390
 
355
391
  Parameters
356
392
  ----------
@@ -363,15 +399,17 @@ class CommonModel:
363
399
  An object of the same class
364
400
  """
365
401
  if method == "fill":
366
- self.tsdf.fillna(method="pad", inplace=True)
402
+ self.tsdf = self.tsdf.fillna(method="pad")
367
403
  else:
368
- self.tsdf.dropna(inplace=True)
404
+ self.tsdf = self.tsdf.dropna()
369
405
  return self
370
406
 
371
407
  def return_nan_handle(
372
- self: TypeCommonModel, method: LiteralNanMethod = "fill"
408
+ self: TypeCommonModel,
409
+ method: LiteralNanMethod = "fill",
373
410
  ) -> TypeCommonModel:
374
- """Handling of missing values in a returnseries
411
+ """
412
+ Handle missing values in a returnseries.
375
413
 
376
414
  Parameters
377
415
  ----------
@@ -384,28 +422,31 @@ class CommonModel:
384
422
  An object of the same class
385
423
  """
386
424
  if method == "fill":
387
- self.tsdf.fillna(value=0.0, inplace=True)
425
+ self.tsdf = self.tsdf.fillna(value=0.0)
388
426
  else:
389
- self.tsdf.dropna(inplace=True)
427
+ self.tsdf = self.tsdf.dropna()
390
428
  return self
391
429
 
392
430
  def to_drawdown_series(self: TypeCommonModel) -> TypeCommonModel:
393
- """Converts timeseries into a drawdown series
431
+ """
432
+ Convert timeseries into a drawdown series.
394
433
 
395
434
  Returns
396
435
  -------
397
436
  self
398
437
  An object of the same class
399
438
  """
400
-
401
439
  for serie in self.tsdf:
402
440
  self.tsdf.loc[:, serie] = drawdown_series(self.tsdf.loc[:, serie])
403
441
  return self
404
442
 
405
443
  def to_json(
406
- self: TypeCommonModel, filename: str, directory: Optional[str] = None
444
+ self: TypeCommonModel,
445
+ filename: str,
446
+ directory: Optional[str] = None,
407
447
  ) -> list[dict[str, Union[str, bool, ValueType, list[str], list[float]]]]:
408
- """Dumps timeseries data into a json file
448
+ """
449
+ Dump timeseries data into a json file.
409
450
 
410
451
  The label and tsdf parameters are deleted before the json file is saved
411
452
 
@@ -415,6 +456,7 @@ class CommonModel:
415
456
  Filename including filetype
416
457
  directory: str, optional
417
458
  File folder location
459
+
418
460
  Returns
419
461
  -------
420
462
  list[Dict[str, Union[str, bool, ValueType, list[str], list[float]]]]
@@ -451,7 +493,8 @@ class CommonModel:
451
493
  sheet_title: Optional[str] = None,
452
494
  directory: Optional[str] = None,
453
495
  ) -> str:
454
- """Saves the data in the .tsdf DataFrame to an Excel spreadsheet file
496
+ """
497
+ Save .tsdf DataFrame to an Excel spreadsheet file.
455
498
 
456
499
  Parameters
457
500
  ----------
@@ -461,12 +504,12 @@ class CommonModel:
461
504
  Name of the sheet in the Excel file
462
505
  directory: str, optional
463
506
  The file directory where the Excel file is saved.
507
+
464
508
  Returns
465
509
  -------
466
510
  str
467
511
  The Excel file path
468
512
  """
469
-
470
513
  if filename[-5:].lower() != ".xlsx":
471
514
  raise NameError("Filename must end with .xlsx")
472
515
  if directory:
@@ -499,7 +542,8 @@ class CommonModel:
499
542
  add_logo: bool = True,
500
543
  output_type: LiteralPlotlyOutput = "file",
501
544
  ) -> tuple[Figure, str]:
502
- """Creates a Plotly Bar Figure
545
+ """
546
+ Create a Plotly Bar Figure.
503
547
 
504
548
  Parameters
505
549
  ----------
@@ -525,7 +569,7 @@ class CommonModel:
525
569
 
526
570
  Returns
527
571
  -------
528
- (plotly.go.Figure, str)
572
+ tuple[plotly.go.Figure, str]
529
573
  Plotly Figure and html filename with location
530
574
  """
531
575
  if labels:
@@ -584,11 +628,8 @@ class CommonModel:
584
628
  show_last: bool = False,
585
629
  output_type: LiteralPlotlyOutput = "file",
586
630
  ) -> tuple[Figure, str]:
587
- """Creates a Plotly Figure
588
-
589
- To scale the bubble size, use the attribute sizeref.
590
- We recommend using the following formula to calculate a sizeref value:
591
- sizeref = 2. * max(array of size values) / (desired maximum marker size ** 2)
631
+ """
632
+ Create a Plotly Scatter Figure.
592
633
 
593
634
  Parameters
594
635
  ----------
@@ -616,10 +657,9 @@ class CommonModel:
616
657
 
617
658
  Returns
618
659
  -------
619
- (plotly.go.Figure, str)
660
+ tuple[plotly.go.Figure, str]
620
661
  Plotly Figure and html filename with location
621
662
  """
622
-
623
663
  if labels:
624
664
  assert (
625
665
  len(labels) == self.tsdf.shape[1]
@@ -686,7 +726,8 @@ class CommonModel:
686
726
  to_date: Optional[dt.date] = None,
687
727
  periods_in_a_year_fixed: Optional[int] = None,
688
728
  ) -> Union[float, Series]:
689
- """https://www.investopedia.com/terms/a/arithmeticmean.asp
729
+ """
730
+ https://www.investopedia.com/terms/a/arithmeticmean.asp.
690
731
 
691
732
  Parameters
692
733
  ----------
@@ -706,7 +747,6 @@ class CommonModel:
706
747
  Union[float, Pandas.Series]
707
748
  Annualized arithmetic mean of returns
708
749
  """
709
-
710
750
  earlier, later = get_calc_range(
711
751
  data=self.tsdf,
712
752
  months_offset=months_from_last,
@@ -718,7 +758,8 @@ class CommonModel:
718
758
  else:
719
759
  fraction = (later - earlier).days / 365.25
720
760
  how_many = self.tsdf.loc[
721
- cast(int, earlier) : cast(int, later), self.tsdf.columns.values[0]
761
+ cast(int, earlier) : cast(int, later),
762
+ self.tsdf.columns.to_numpy()[0],
722
763
  ].count()
723
764
  time_factor = how_many / fraction
724
765
 
@@ -742,9 +783,11 @@ class CommonModel:
742
783
  to_date: Optional[dt.date] = None,
743
784
  periods_in_a_year_fixed: Optional[int] = None,
744
785
  ) -> Union[float, Series]:
745
- """Based on Pandas .std() which is the equivalent of stdev.s([...])
746
- in MS Excel \n
747
- https://www.investopedia.com/terms/v/volatility.asp
786
+ """
787
+ Annualized volatility.
788
+
789
+ Based on Pandas .std() which is the equivalent of stdev.s([...]) in MS Excel.
790
+ https://www.investopedia.com/terms/v/volatility.asp.
748
791
 
749
792
  Parameters
750
793
  ----------
@@ -763,7 +806,6 @@ class CommonModel:
763
806
  Union[float, Pandas.Series]
764
807
  Annualized volatility
765
808
  """
766
-
767
809
  earlier, later = get_calc_range(
768
810
  data=self.tsdf,
769
811
  months_offset=months_from_last,
@@ -803,9 +845,13 @@ class CommonModel:
803
845
  periods_in_a_year_fixed: Optional[int] = None,
804
846
  ) -> Union[float, Series]:
805
847
  """
848
+ Implied annualized volatility.
849
+
850
+ Implied annualized volatility from the Downside VaR using the assumption
851
+ that returns are normally distributed.
852
+
806
853
  Parameters
807
854
  ----------
808
-
809
855
  level: float, default: 0.95
810
856
  The sought VaR level
811
857
  months_from_last : int, optional
@@ -853,8 +899,11 @@ class CommonModel:
853
899
  drift_adjust: bool = False,
854
900
  periods_in_a_year_fixed: Optional[int] = None,
855
901
  ) -> Union[float, Series]:
856
- """A position weight multiplier from the ratio between a VaR implied
857
- volatility and a given target volatility. Multiplier = 1.0 -> target met
902
+ """
903
+ Target weight from VaR.
904
+
905
+ A position weight multiplier from the ratio between a VaR implied
906
+ volatility and a given target volatility. Multiplier = 1.0 -> target met.
858
907
 
859
908
  Parameters
860
909
  ----------
@@ -908,7 +957,10 @@ class CommonModel:
908
957
  from_date: Optional[dt.date] = None,
909
958
  to_date: Optional[dt.date] = None,
910
959
  ) -> Union[float, Series]:
911
- """https://www.investopedia.com/terms/c/conditional_value_at_risk.asp
960
+ """
961
+ Downside Conditional Value At Risk "CVaR".
962
+
963
+ https://www.investopedia.com/terms/c/conditional_value_at_risk.asp.
912
964
 
913
965
  Parameters
914
966
  ----------
@@ -927,7 +979,6 @@ class CommonModel:
927
979
  Union[float, Pandas.Series]
928
980
  Downside Conditional Value At Risk "CVaR"
929
981
  """
930
-
931
982
  earlier, later = get_calc_range(
932
983
  data=self.tsdf,
933
984
  months_offset=months_from_last,
@@ -960,10 +1011,12 @@ class CommonModel:
960
1011
  to_date: Optional[dt.date] = None,
961
1012
  periods_in_a_year_fixed: Optional[int] = None,
962
1013
  ) -> Union[float, Series]:
963
- """The standard deviation of returns that are below a Minimum Accepted
964
- Return of zero.
965
- It is used to calculate the Sortino Ratio \n
966
- https://www.investopedia.com/terms/d/downside-deviation.asp
1014
+ """
1015
+ Downside Deviation.
1016
+
1017
+ The standard deviation of returns that are below a Minimum Accepted
1018
+ Return of zero. It is used to calculate the Sortino Ratio.
1019
+ https://www.investopedia.com/terms/d/downside-deviation.asp.
967
1020
 
968
1021
  Parameters
969
1022
  ----------
@@ -985,7 +1038,6 @@ class CommonModel:
985
1038
  Union[float, Pandas.Series]
986
1039
  Downside deviation
987
1040
  """
988
-
989
1041
  earlier, later = get_calc_range(
990
1042
  data=self.tsdf,
991
1043
  months_offset=months_from_last,
@@ -1026,7 +1078,10 @@ class CommonModel:
1026
1078
  from_date: Optional[dt.date] = None,
1027
1079
  to_date: Optional[dt.date] = None,
1028
1080
  ) -> Union[float, Series]:
1029
- """https://www.investopedia.com/terms/c/cagr.asp
1081
+ """
1082
+ Compounded Annual Growth Rate (CAGR).
1083
+
1084
+ https://www.investopedia.com/terms/c/cagr.asp.
1030
1085
 
1031
1086
  Parameters
1032
1087
  ----------
@@ -1057,7 +1112,7 @@ class CommonModel:
1057
1112
  ):
1058
1113
  raise ValueError(
1059
1114
  "Geometric return cannot be calculated due to an initial "
1060
- "value being zero or a negative value."
1115
+ "value being zero or a negative value.",
1061
1116
  )
1062
1117
 
1063
1118
  result = (self.tsdf.iloc[-1] / self.tsdf.iloc[0]) ** (1 / fraction) - 1
@@ -1071,13 +1126,65 @@ class CommonModel:
1071
1126
  dtype="float64",
1072
1127
  )
1073
1128
 
1129
+ def skew_func(
1130
+ self: TypeCommonModel,
1131
+ months_from_last: Optional[int] = None,
1132
+ from_date: Optional[dt.date] = None,
1133
+ to_date: Optional[dt.date] = None,
1134
+ ) -> Union[float, Series]:
1135
+ """
1136
+ Skew of the return distribution.
1137
+
1138
+ https://www.investopedia.com/terms/s/skewness.asp.
1139
+
1140
+ Parameters
1141
+ ----------
1142
+ months_from_last : int, optional
1143
+ number of months offset as positive integer. Overrides use of from_date
1144
+ and to_date
1145
+ from_date : datetime.date, optional
1146
+ Specific from date
1147
+ to_date : datetime.date, optional
1148
+ Specific to date
1149
+
1150
+ Returns
1151
+ -------
1152
+ Union[float, Pandas.Series]
1153
+ Skew of the return distribution
1154
+ """
1155
+ earlier, later = get_calc_range(
1156
+ data=self.tsdf,
1157
+ months_offset=months_from_last,
1158
+ from_dt=from_date,
1159
+ to_dt=to_date,
1160
+ )
1161
+ result = skew(
1162
+ a=self.tsdf.loc[cast(int, earlier) : cast(int, later)]
1163
+ .pct_change()
1164
+ .to_numpy(),
1165
+ bias=True,
1166
+ nan_policy="omit",
1167
+ )
1168
+
1169
+ if self.tsdf.shape[1] == 1:
1170
+ return float(result[0])
1171
+ return Series(
1172
+ data=result,
1173
+ index=self.tsdf.columns,
1174
+ name="Skew",
1175
+ dtype="float64",
1176
+ )
1177
+
1074
1178
  def kurtosis_func(
1075
1179
  self: TypeCommonModel,
1076
1180
  months_from_last: Optional[int] = None,
1077
1181
  from_date: Optional[dt.date] = None,
1078
1182
  to_date: Optional[dt.date] = None,
1079
1183
  ) -> Union[float, Series]:
1080
- """https://www.investopedia.com/terms/k/kurtosis.asp
1184
+ """
1185
+ Kurtosis of the return distribution.
1186
+
1187
+ https://www.investopedia.com/terms/k/kurtosis.asp.
1081
1188
 
1082
1189
  Parameters
1083
1190
  ----------
@@ -1094,7 +1201,6 @@ class CommonModel:
1094
1201
  Union[float, Pandas.Series]
1095
1202
  Kurtosis of the return distribution
1096
1203
  """
1097
-
1098
1204
  earlier, later = get_calc_range(
1099
1205
  data=self.tsdf,
1100
1206
  months_offset=months_from_last,
@@ -1124,7 +1230,10 @@ class CommonModel:
1124
1230
  to_date: Optional[dt.date] = None,
1125
1231
  min_periods: int = 1,
1126
1232
  ) -> Union[float, Series]:
1127
- """https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
1233
+ """
1234
+ Maximum drawdown without any limit on date range.
1235
+
1236
+ https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.
1128
1237
 
1129
1238
  Parameters
1130
1239
  ----------
@@ -1166,14 +1275,16 @@ class CommonModel:
1166
1275
 
1167
1276
  @property
1168
1277
  def max_drawdown_date(self: TypeCommonModel) -> Union[dt.date, Series]:
1169
- """https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
1278
+ """
1279
+ Date when the maximum drawdown occurred.
1280
+
1281
+ https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.
1170
1282
 
1171
1283
  Returns
1172
1284
  -------
1173
1285
  Union[datetime.date, pandas.Series]
1174
1286
  Date when the maximum drawdown occurred
1175
1287
  """
1176
-
1177
1288
  mdddf = self.tsdf.copy()
1178
1289
  mdddf.index = DatetimeIndex(mdddf.index)
1179
1290
  result = (mdddf / mdddf.expanding(min_periods=1).max()).idxmin().dt.date
@@ -1184,8 +1295,8 @@ class CommonModel:
1184
1295
  data=result,
1185
1296
  index=self.tsdf.columns,
1186
1297
  name="Max drawdown date",
1187
- dtype="float64",
1188
- )
1298
+ dtype="datetime64[ns]",
1299
+ ).dt.date
1189
1300
 
1190
1301
  def positive_share_func(
1191
1302
  self: TypeCommonModel,
@@ -1193,7 +1304,8 @@ class CommonModel:
1193
1304
  from_date: Optional[dt.date] = None,
1194
1305
  to_date: Optional[dt.date] = None,
1195
1306
  ) -> Union[float, Series]:
1196
- """The share of percentage changes that are greater than zero
1307
+ """
1308
+ Calculate share of percentage changes that are greater than zero.
1197
1309
 
1198
1310
  Parameters
1199
1311
  ----------
@@ -1208,7 +1320,7 @@ class CommonModel:
1208
1320
  Returns
1209
1321
  -------
1210
1322
  Union[float, Pandas.Series]
1211
- The share of percentage changes that are greater than zero
1323
+ Calculate share of percentage changes that are greater than zero
1212
1324
  """
1213
1325
  earlier, later = get_calc_range(
1214
1326
  data=self.tsdf,
@@ -1244,11 +1356,14 @@ class CommonModel:
1244
1356
  to_date: Optional[dt.date] = None,
1245
1357
  periods_in_a_year_fixed: Optional[int] = None,
1246
1358
  ) -> Union[float, Series]:
1247
- """The ratio of annualized arithmetic mean of returns and annualized
1359
+ """
1360
+ Ratio between arithmetic mean of returns and annualized volatility.
1361
+
1362
+ The ratio of annualized arithmetic mean of returns and annualized
1248
1363
  volatility or, if riskfree return provided, Sharpe ratio calculated
1249
1364
  as ( geometric return - risk-free return ) / volatility. The latter ratio
1250
- implies that the riskfree asset has zero volatility. \n
1251
- https://www.investopedia.com/terms/s/sharperatio.asp
1365
+ implies that the riskfree asset has zero volatility.
1366
+ https://www.investopedia.com/terms/s/sharperatio.asp.
1252
1367
 
1253
1368
  Parameters
1254
1369
  ----------
@@ -1269,8 +1384,7 @@ class CommonModel:
1269
1384
  -------
1270
1385
  Union[float, Pandas.Series]
1271
1386
  Ratio of the annualized arithmetic mean of returns and annualized
1272
- volatility or,
1273
- if risk-free return provided, Sharpe ratio
1387
+ volatility or, if risk-free return provided, Sharpe ratio
1274
1388
  """
1275
1389
  ratio: Series = (
1276
1390
  self.arithmetic_ret_func(
@@ -1293,50 +1407,6 @@ class CommonModel:
1293
1407
  ratio = ratio.astype("float64")
1294
1408
  return ratio
1295
1409
 
1296
- def skew_func(
1297
- self: TypeCommonModel,
1298
- months_from_last: Optional[int] = None,
1299
- from_date: Optional[dt.date] = None,
1300
- to_date: Optional[dt.date] = None,
1301
- ) -> Union[float, Series]:
1302
- """https://www.investopedia.com/terms/s/skewness.asp
1303
-
1304
- Parameters
1305
- ----------
1306
- months_from_last : int, optional
1307
- number of months offset as positive integer. Overrides use of from_date
1308
- and to_date
1309
- from_date : datetime.date, optional
1310
- Specific from date
1311
- to_date : datetime.date, optional
1312
- Specific to date
1313
-
1314
- Returns
1315
- -------
1316
- Union[float, Pandas.Series]
1317
- Skew of the return distribution
1318
- """
1319
- earlier, later = get_calc_range(
1320
- data=self.tsdf,
1321
- months_offset=months_from_last,
1322
- from_dt=from_date,
1323
- to_dt=to_date,
1324
- )
1325
- result = skew(
1326
- a=self.tsdf.loc[cast(int, earlier) : cast(int, later)].pct_change().values,
1327
- bias=True,
1328
- nan_policy="omit",
1329
- )
1330
-
1331
- if self.tsdf.shape[1] == 1:
1332
- return float(result[0])
1333
- return Series(
1334
- data=result,
1335
- index=self.tsdf.columns,
1336
- name="Skew",
1337
- dtype="float64",
1338
- )
1339
-
1340
1410
  def sortino_ratio_func(
1341
1411
  self: TypeCommonModel,
1342
1412
  riskfree_rate: float = 0.0,
@@ -1346,11 +1416,14 @@ class CommonModel:
1346
1416
  to_date: Optional[dt.date] = None,
1347
1417
  periods_in_a_year_fixed: Optional[int] = None,
1348
1418
  ) -> Union[float, Series]:
1349
- """The Sortino ratio calculated as ( return - risk free return )
1419
+ """
1420
+ Sortino Ratio.
1421
+
1422
+ The Sortino ratio calculated as ( return - risk free return )
1350
1423
  / downside deviation. The ratio implies that the riskfree asset has zero
1351
1424
  volatility, and a minimum acceptable return of zero. The ratio is
1352
- calculated using the annualized arithmetic mean of returns. \n
1353
- https://www.investopedia.com/terms/s/sortinoratio.asp
1425
+ calculated using the annualized arithmetic mean of returns.
1426
+ https://www.investopedia.com/terms/s/sortinoratio.asp.
1354
1427
 
1355
1428
  Parameters
1356
1429
  ----------
@@ -1404,6 +1477,8 @@ class CommonModel:
1404
1477
  to_date: Optional[dt.date] = None,
1405
1478
  ) -> Union[float, Series]:
1406
1479
  """
1480
+ Calculate simple return.
1481
+
1407
1482
  Parameters
1408
1483
  ----------
1409
1484
  months_from_last : int, optional
@@ -1417,9 +1492,8 @@ class CommonModel:
1417
1492
  Returns
1418
1493
  -------
1419
1494
  Union[float, Pandas.Series]
1420
- Simple return
1495
+ Calculate simple return
1421
1496
  """
1422
-
1423
1497
  earlier, later = get_calc_range(
1424
1498
  data=self.tsdf,
1425
1499
  months_offset=months_from_last,
@@ -1429,7 +1503,7 @@ class CommonModel:
1429
1503
  if 0.0 in self.tsdf.iloc[0].tolist():
1430
1504
  raise ValueError(
1431
1505
  f"Simple return cannot be calculated due to an "
1432
- f"initial value being zero. ({self.tsdf.head(3)})"
1506
+ f"initial value being zero. ({self.tsdf.head(3)})",
1433
1507
  )
1434
1508
 
1435
1509
  result = self.tsdf.loc[later] / self.tsdf.loc[earlier] - 1
@@ -1444,9 +1518,13 @@ class CommonModel:
1444
1518
  )
1445
1519
 
1446
1520
  def value_ret_calendar_period(
1447
- self: TypeCommonModel, year: int, month: Optional[int] = None
1521
+ self: TypeCommonModel,
1522
+ year: int,
1523
+ month: Optional[int] = None,
1448
1524
  ) -> Union[float, Series]:
1449
1525
  """
1526
+ Calculate simple return for a specific calendar period.
1527
+
1450
1528
  Parameters
1451
1529
  ----------
1452
1530
  year : int
@@ -1457,9 +1535,8 @@ class CommonModel:
1457
1535
  Returns
1458
1536
  -------
1459
1537
  Union[float, Pandas.Series]
1460
- Simple return for a specific calendar period
1538
+ Calculate simple return for a specific calendar period
1461
1539
  """
1462
-
1463
1540
  if month is None:
1464
1541
  period = str(year)
1465
1542
  else:
@@ -1483,9 +1560,11 @@ class CommonModel:
1483
1560
  to_date: Optional[dt.date] = None,
1484
1561
  interpolation: LiteralQuantileInterp = "lower",
1485
1562
  ) -> Union[float, Series]:
1486
- """https://www.investopedia.com/terms/v/var.asp
1487
- Downside Value At Risk, "VaR". The equivalent of
1488
- percentile.inc([...], 1-level) over returns in MS Excel.
1563
+ """
1564
+ Downside Value At Risk, "VaR".
1565
+
1566
+ The equivalent of percentile.inc([...], 1-level) over returns in MS Excel.
1567
+ https://www.investopedia.com/terms/v/var.asp.
1489
1568
 
1490
1569
  Parameters
1491
1570
  ----------
@@ -1534,8 +1613,8 @@ class CommonModel:
1534
1613
  from_date: Optional[dt.date] = None,
1535
1614
  to_date: Optional[dt.date] = None,
1536
1615
  ) -> Union[float, Series]:
1537
- """Most negative percentage change over a rolling number of observations
1538
- within a chosen date range
1616
+ """
1617
+ Most negative percentage change over a rolling number of observations.
1539
1618
 
1540
1619
  Parameters
1541
1620
  ----------
@@ -1584,7 +1663,10 @@ class CommonModel:
1584
1663
  from_date: Optional[dt.date] = None,
1585
1664
  to_date: Optional[dt.date] = None,
1586
1665
  ) -> Union[float, Series]:
1587
- """https://www.investopedia.com/terms/z/zscore.asp
1666
+ """
1667
+ Z-score as (last return - mean return) / standard deviation of returns.
1668
+
1669
+ https://www.investopedia.com/terms/z/zscore.asp.
1588
1670
 
1589
1671
  Parameters
1590
1672
  ----------
@@ -1601,7 +1683,6 @@ class CommonModel:
1601
1683
  Union[float, Pandas.Series]
1602
1684
  Z-score as (last return - mean return) / standard deviation of returns
1603
1685
  """
1604
-
1605
1686
  earlier, later = get_calc_range(
1606
1687
  data=self.tsdf,
1607
1688
  months_offset=months_from_last,
@@ -1627,6 +1708,8 @@ class CommonModel:
1627
1708
  observations: int = 252,
1628
1709
  ) -> DataFrame:
1629
1710
  """
1711
+ Calculate rolling annualized downside CVaR.
1712
+
1630
1713
  Parameters
1631
1714
  ----------
1632
1715
  column: int, default: 0
@@ -1639,7 +1722,7 @@ class CommonModel:
1639
1722
  Returns
1640
1723
  -------
1641
1724
  Pandas.DataFrame
1642
- Rolling annualized downside CVaR
1725
+ Calculate rolling annualized downside CVaR
1643
1726
  """
1644
1727
  cvar_label = self.tsdf.iloc[:, column].name[0]
1645
1728
  cvardf = (
@@ -1653,9 +1736,13 @@ class CommonModel:
1653
1736
  return cvardf
1654
1737
 
1655
1738
  def rolling_return(
1656
- self: TypeCommonModel, column: int = 0, observations: int = 21
1739
+ self: TypeCommonModel,
1740
+ column: int = 0,
1741
+ observations: int = 21,
1657
1742
  ) -> DataFrame:
1658
1743
  """
1744
+ Calculate rolling returns.
1745
+
1659
1746
  Parameters
1660
1747
  ----------
1661
1748
  column: int, default: 0
@@ -1666,7 +1753,7 @@ class CommonModel:
1666
1753
  Returns
1667
1754
  -------
1668
1755
  Pandas.DataFrame
1669
- Rolling returns
1756
+ Calculate rolling returns
1670
1757
  """
1671
1758
  ret_label = self.tsdf.iloc[:, column].name[0]
1672
1759
  retdf = (
@@ -1688,6 +1775,8 @@ class CommonModel:
1688
1775
  interpolation: LiteralQuantileInterp = "lower",
1689
1776
  ) -> DataFrame:
1690
1777
  """
1778
+ Calculate rolling annualized downside Value At Risk "VaR".
1779
+
1691
1780
  Parameters
1692
1781
  ----------
1693
1782
  column: int, default: 0
@@ -1702,14 +1791,14 @@ class CommonModel:
1702
1791
  Returns
1703
1792
  -------
1704
1793
  Pandas.DataFrame
1705
- Rolling annualized downside Value At Risk "VaR"
1794
+ Calculate rolling annualized downside Value At Risk "VaR"
1706
1795
  """
1707
1796
  var_label = self.tsdf.iloc[:, column].name[0]
1708
1797
  vardf = (
1709
1798
  self.tsdf.iloc[:, column]
1710
1799
  .rolling(observations, min_periods=observations)
1711
1800
  .apply(
1712
- lambda x: var_down_calc(x, level=level, interpolation=interpolation)
1801
+ lambda x: var_down_calc(x, level=level, interpolation=interpolation),
1713
1802
  )
1714
1803
  )
1715
1804
  vardf = vardf.dropna().to_frame()
@@ -1724,6 +1813,8 @@ class CommonModel:
1724
1813
  periods_in_a_year_fixed: Optional[int] = None,
1725
1814
  ) -> DataFrame:
1726
1815
  """
1816
+ Calculate rolling annualised volatilities.
1817
+
1727
1818
  Parameters
1728
1819
  ----------
1729
1820
  column: int, default: 0
@@ -1737,7 +1828,7 @@ class CommonModel:
1737
1828
  Returns
1738
1829
  -------
1739
1830
  Pandas.DataFrame
1740
- Rolling annualised volatilities
1831
+ Calculate rolling annualised volatilities
1741
1832
  """
1742
1833
  if periods_in_a_year_fixed:
1743
1834
  time_factor = float(periods_in_a_year_fixed)
@@ -1746,7 +1837,7 @@ class CommonModel:
1746
1837
  vol_label = self.tsdf.iloc[:, column].name[0]
1747
1838
  dframe = self.tsdf.iloc[:, column].pct_change()
1748
1839
  voldf = dframe.rolling(observations, min_periods=observations).std() * sqrt(
1749
- time_factor
1840
+ time_factor,
1750
1841
  )
1751
1842
  voldf = voldf.dropna().to_frame()
1752
1843
  voldf.columns = [[vol_label], ["Rolling volatility"]]
@@ -1767,10 +1858,13 @@ def _var_implied_vol_and_target_func(
1767
1858
  drift_adjust: bool = False,
1768
1859
  periods_in_a_year_fixed: Optional[int] = None,
1769
1860
  ) -> Union[float, Series]:
1770
- """The function returns a position weight multiplier from the ratio between
1861
+ """
1862
+ Volatility implied from VaR or Target Weight.
1863
+
1864
+ The function returns a position weight multiplier from the ratio between
1771
1865
  a VaR implied volatility and a given target volatility if the argument
1772
1866
  target_vol is provided. Otherwise the function returns the VaR implied
1773
- volatility. Multiplier = 1.0 -> target met
1867
+ volatility. Multiplier = 1.0 -> target met.
1774
1868
 
1775
1869
  Parameters
1776
1870
  ----------
@@ -1836,7 +1930,7 @@ def _var_implied_vol_and_target_func(
1836
1930
 
1837
1931
  if target_vol:
1838
1932
  result = imp_vol.apply(
1839
- lambda x: max(min_leverage_local, min(target_vol / x, max_leverage_local))
1933
+ lambda x: max(min_leverage_local, min(target_vol / x, max_leverage_local)),
1840
1934
  )
1841
1935
  label = "Weight from target vol"
1842
1936
  else: