openseries 1.8.0__py3-none-any.whl → 1.8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openseries/__init__.py +2 -1
- openseries/_common_model.py +180 -155
- openseries/_risk.py +4 -4
- openseries/datefixer.py +20 -14
- openseries/frame.py +124 -109
- openseries/load_plotly.py +6 -4
- openseries/owntypes.py +66 -5
- openseries/plotly_layouts.json +6 -6
- openseries/portfoliotools.py +33 -29
- openseries/series.py +69 -79
- openseries/simulation.py +15 -14
- openseries-1.8.2.dist-info/LICENSE.md +27 -0
- {openseries-1.8.0.dist-info → openseries-1.8.2.dist-info}/METADATA +44 -16
- openseries-1.8.2.dist-info/RECORD +16 -0
- {openseries-1.8.0.dist-info → openseries-1.8.2.dist-info}/WHEEL +1 -1
- openseries-1.8.0.dist-info/LICENSE.md +0 -27
- openseries-1.8.0.dist-info/RECORD +0 -16
openseries/_common_model.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Defining the _CommonModel class."""
|
2
2
|
|
3
|
+
# mypy: disable-error-code="no-any-return"
|
3
4
|
from __future__ import annotations
|
4
5
|
|
5
6
|
import datetime as dt
|
@@ -13,11 +14,32 @@ from typing import TYPE_CHECKING, Any, SupportsFloat, cast
|
|
13
14
|
|
14
15
|
from numpy import float64, inf, isnan, log, maximum, sqrt
|
15
16
|
|
16
|
-
|
17
|
-
|
17
|
+
from .owntypes import (
|
18
|
+
DateAlignmentError,
|
19
|
+
InitialValueZeroError,
|
20
|
+
NumberOfItemsAndLabelsNotSameError,
|
21
|
+
Self,
|
22
|
+
)
|
23
|
+
|
24
|
+
if TYPE_CHECKING: # pragma: no cover
|
25
|
+
from numpy.typing import NDArray
|
26
|
+
from openpyxl.worksheet.worksheet import Worksheet
|
27
|
+
|
28
|
+
from .owntypes import (
|
29
|
+
CountriesType,
|
30
|
+
DaysInYearType,
|
31
|
+
LiteralBarPlotMode,
|
32
|
+
LiteralJsonOutput,
|
33
|
+
LiteralLinePlotMode,
|
34
|
+
LiteralNanMethod,
|
35
|
+
LiteralPandasReindexMethod,
|
36
|
+
LiteralPlotlyJSlib,
|
37
|
+
LiteralPlotlyOutput,
|
38
|
+
LiteralQuantileInterp,
|
39
|
+
ValueType,
|
40
|
+
)
|
18
41
|
from openpyxl.utils.dataframe import dataframe_to_rows
|
19
42
|
from openpyxl.workbook.workbook import Workbook
|
20
|
-
from openpyxl.worksheet.worksheet import Worksheet
|
21
43
|
from pandas import (
|
22
44
|
DataFrame,
|
23
45
|
DatetimeIndex,
|
@@ -28,16 +50,15 @@ from pandas import (
|
|
28
50
|
to_datetime,
|
29
51
|
)
|
30
52
|
from pandas.tseries.offsets import CustomBusinessDay
|
31
|
-
from plotly.graph_objs import Figure # type: ignore[import-untyped
|
32
|
-
from plotly.io import to_html # type: ignore[import-untyped
|
33
|
-
from plotly.offline import plot # type: ignore[import-untyped
|
53
|
+
from plotly.graph_objs import Figure # type: ignore[import-untyped]
|
54
|
+
from plotly.io import to_html # type: ignore[import-untyped]
|
55
|
+
from plotly.offline import plot # type: ignore[import-untyped]
|
34
56
|
from pydantic import BaseModel, ConfigDict, DirectoryPath
|
35
|
-
from scipy.stats import ( # type: ignore[import-untyped
|
57
|
+
from scipy.stats import ( # type: ignore[import-untyped]
|
36
58
|
kurtosis,
|
37
59
|
norm,
|
38
60
|
skew,
|
39
61
|
)
|
40
|
-
from typing_extensions import Self
|
41
62
|
|
42
63
|
from ._risk import (
|
43
64
|
_cvar_down_calc,
|
@@ -49,23 +70,10 @@ from .datefixer import (
|
|
49
70
|
holiday_calendar,
|
50
71
|
)
|
51
72
|
from .load_plotly import load_plotly_dict
|
52
|
-
from .owntypes import (
|
53
|
-
CountriesType,
|
54
|
-
DaysInYearType,
|
55
|
-
LiteralBarPlotMode,
|
56
|
-
LiteralJsonOutput,
|
57
|
-
LiteralLinePlotMode,
|
58
|
-
LiteralNanMethod,
|
59
|
-
LiteralPandasReindexMethod,
|
60
|
-
LiteralPlotlyJSlib,
|
61
|
-
LiteralPlotlyOutput,
|
62
|
-
LiteralQuantileInterp,
|
63
|
-
ValueType,
|
64
|
-
)
|
65
73
|
|
66
74
|
|
67
75
|
# noinspection PyTypeChecker
|
68
|
-
class _CommonModel(BaseModel):
|
76
|
+
class _CommonModel(BaseModel): # type: ignore[misc]
|
69
77
|
"""Declare _CommonModel."""
|
70
78
|
|
71
79
|
tsdf: DataFrame = DataFrame(dtype="float64")
|
@@ -80,7 +88,7 @@ class _CommonModel(BaseModel):
|
|
80
88
|
def length(self: Self) -> int:
|
81
89
|
"""Number of observations.
|
82
90
|
|
83
|
-
Returns
|
91
|
+
Returns:
|
84
92
|
-------
|
85
93
|
int
|
86
94
|
Number of observations
|
@@ -92,31 +100,31 @@ class _CommonModel(BaseModel):
|
|
92
100
|
def first_idx(self: Self) -> dt.date:
|
93
101
|
"""The first date in the timeseries.
|
94
102
|
|
95
|
-
Returns
|
103
|
+
Returns:
|
96
104
|
-------
|
97
105
|
datetime.date
|
98
106
|
The first date in the timeseries
|
99
107
|
|
100
108
|
"""
|
101
|
-
return cast(dt.date, self.tsdf.index[0])
|
109
|
+
return cast("dt.date", self.tsdf.index[0])
|
102
110
|
|
103
111
|
@property
|
104
112
|
def last_idx(self: Self) -> dt.date:
|
105
113
|
"""The last date in the timeseries.
|
106
114
|
|
107
|
-
Returns
|
115
|
+
Returns:
|
108
116
|
-------
|
109
117
|
datetime.date
|
110
118
|
The last date in the timeseries
|
111
119
|
|
112
120
|
"""
|
113
|
-
return cast(dt.date, self.tsdf.index[-1])
|
121
|
+
return cast("dt.date", self.tsdf.index[-1])
|
114
122
|
|
115
123
|
@property
|
116
124
|
def span_of_days(self: Self) -> int:
|
117
125
|
"""Number of days from the first date to the last.
|
118
126
|
|
119
|
-
Returns
|
127
|
+
Returns:
|
120
128
|
-------
|
121
129
|
int
|
122
130
|
Number of days from the first date to the last
|
@@ -128,7 +136,7 @@ class _CommonModel(BaseModel):
|
|
128
136
|
def yearfrac(self: Self) -> float:
|
129
137
|
"""Length of series expressed in years assuming all years have 365.25 days.
|
130
138
|
|
131
|
-
Returns
|
139
|
+
Returns:
|
132
140
|
-------
|
133
141
|
float
|
134
142
|
Length of the timeseries expressed in years assuming all years
|
@@ -141,7 +149,7 @@ class _CommonModel(BaseModel):
|
|
141
149
|
def periods_in_a_year(self: Self) -> float:
|
142
150
|
"""The average number of observations per year.
|
143
151
|
|
144
|
-
Returns
|
152
|
+
Returns:
|
145
153
|
-------
|
146
154
|
float
|
147
155
|
The average number of observations per year
|
@@ -153,7 +161,7 @@ class _CommonModel(BaseModel):
|
|
153
161
|
def max_drawdown_cal_year(self: Self) -> float | Series[float]:
|
154
162
|
"""https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.
|
155
163
|
|
156
|
-
Returns
|
164
|
+
Returns:
|
157
165
|
-------
|
158
166
|
float | Pandas.Series[float]
|
159
167
|
Maximum drawdown in a single calendar year.
|
@@ -181,7 +189,7 @@ class _CommonModel(BaseModel):
|
|
181
189
|
def geo_ret(self: Self) -> float | Series[float]:
|
182
190
|
"""https://www.investopedia.com/terms/c/cagr.asp.
|
183
191
|
|
184
|
-
Returns
|
192
|
+
Returns:
|
185
193
|
-------
|
186
194
|
float | Pandas.Series[float]
|
187
195
|
Compounded Annual Growth Rate (CAGR)
|
@@ -193,7 +201,7 @@ class _CommonModel(BaseModel):
|
|
193
201
|
def arithmetic_ret(self: Self) -> float | Series[float]:
|
194
202
|
"""https://www.investopedia.com/terms/a/arithmeticmean.asp.
|
195
203
|
|
196
|
-
Returns
|
204
|
+
Returns:
|
197
205
|
-------
|
198
206
|
float | Pandas.Series[float]
|
199
207
|
Annualized arithmetic mean of returns
|
@@ -205,7 +213,7 @@ class _CommonModel(BaseModel):
|
|
205
213
|
def value_ret(self: Self) -> float | Series[float]:
|
206
214
|
"""Simple return.
|
207
215
|
|
208
|
-
Returns
|
216
|
+
Returns:
|
209
217
|
-------
|
210
218
|
float | Pandas.Series[float]
|
211
219
|
Simple return
|
@@ -220,7 +228,7 @@ class _CommonModel(BaseModel):
|
|
220
228
|
Based on Pandas .std() which is the equivalent of stdev.s([...]) in MS Excel.
|
221
229
|
https://www.investopedia.com/terms/v/volatility.asp.
|
222
230
|
|
223
|
-
Returns
|
231
|
+
Returns:
|
224
232
|
-------
|
225
233
|
float | Pandas.Series[float]
|
226
234
|
Annualized volatility
|
@@ -236,7 +244,7 @@ class _CommonModel(BaseModel):
|
|
236
244
|
of zero. It is used to calculate the Sortino Ratio.
|
237
245
|
https://www.investopedia.com/terms/d/downside-deviation.asp.
|
238
246
|
|
239
|
-
Returns
|
247
|
+
Returns:
|
240
248
|
-------
|
241
249
|
float | Pandas.Series[float]
|
242
250
|
Downside deviation
|
@@ -249,7 +257,7 @@ class _CommonModel(BaseModel):
|
|
249
257
|
def ret_vol_ratio(self: Self) -> float | Series[float]:
|
250
258
|
"""Ratio of annualized arithmetic mean of returns and annualized volatility.
|
251
259
|
|
252
|
-
Returns
|
260
|
+
Returns:
|
253
261
|
-------
|
254
262
|
float | Pandas.Series[float]
|
255
263
|
Ratio of the annualized arithmetic mean of returns and annualized
|
@@ -263,7 +271,7 @@ class _CommonModel(BaseModel):
|
|
263
271
|
def sortino_ratio(self: Self) -> float | Series[float]:
|
264
272
|
"""https://www.investopedia.com/terms/s/sortinoratio.asp.
|
265
273
|
|
266
|
-
Returns
|
274
|
+
Returns:
|
267
275
|
-------
|
268
276
|
float | Pandas.Series[float]
|
269
277
|
Sortino ratio calculated as the annualized arithmetic mean of returns
|
@@ -282,7 +290,7 @@ class _CommonModel(BaseModel):
|
|
282
290
|
def omega_ratio(self: Self) -> float | Series[float]:
|
283
291
|
"""https://en.wikipedia.org/wiki/Omega_ratio.
|
284
292
|
|
285
|
-
Returns
|
293
|
+
Returns:
|
286
294
|
-------
|
287
295
|
float | Pandas.Series[float]
|
288
296
|
Omega ratio calculation
|
@@ -295,7 +303,7 @@ class _CommonModel(BaseModel):
|
|
295
303
|
def z_score(self: Self) -> float | Series[float]:
|
296
304
|
"""https://www.investopedia.com/terms/z/zscore.asp.
|
297
305
|
|
298
|
-
Returns
|
306
|
+
Returns:
|
299
307
|
-------
|
300
308
|
float | Pandas.Series[float]
|
301
309
|
Z-score as (last return - mean return) / standard deviation of returns.
|
@@ -307,7 +315,7 @@ class _CommonModel(BaseModel):
|
|
307
315
|
def max_drawdown(self: Self) -> float | Series[float]:
|
308
316
|
"""https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.
|
309
317
|
|
310
|
-
Returns
|
318
|
+
Returns:
|
311
319
|
-------
|
312
320
|
float | Pandas.Series[float]
|
313
321
|
Maximum drawdown without any limit on date range
|
@@ -321,7 +329,7 @@ class _CommonModel(BaseModel):
|
|
321
329
|
|
322
330
|
https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp.
|
323
331
|
|
324
|
-
Returns
|
332
|
+
Returns:
|
325
333
|
-------
|
326
334
|
datetime.date | pandas.Series[dt.date]
|
327
335
|
Date when the maximum drawdown occurred
|
@@ -344,7 +352,7 @@ class _CommonModel(BaseModel):
|
|
344
352
|
def worst(self: Self) -> float | Series[float]:
|
345
353
|
"""Most negative percentage change.
|
346
354
|
|
347
|
-
Returns
|
355
|
+
Returns:
|
348
356
|
-------
|
349
357
|
float | Pandas.Series[float]
|
350
358
|
Most negative percentage change
|
@@ -357,7 +365,7 @@ class _CommonModel(BaseModel):
|
|
357
365
|
def worst_month(self: Self) -> float | Series[float]:
|
358
366
|
"""Most negative month.
|
359
367
|
|
360
|
-
Returns
|
368
|
+
Returns:
|
361
369
|
-------
|
362
370
|
Pandas.Series[float]
|
363
371
|
Most negative month
|
@@ -388,7 +396,7 @@ class _CommonModel(BaseModel):
|
|
388
396
|
def positive_share(self: Self) -> float | Series[float]:
|
389
397
|
"""The share of percentage changes that are greater than zero.
|
390
398
|
|
391
|
-
Returns
|
399
|
+
Returns:
|
392
400
|
-------
|
393
401
|
float | Pandas.Series[float]
|
394
402
|
The share of percentage changes that are greater than zero
|
@@ -400,7 +408,7 @@ class _CommonModel(BaseModel):
|
|
400
408
|
def skew(self: Self) -> float | Series[float]:
|
401
409
|
"""https://www.investopedia.com/terms/s/skewness.asp.
|
402
410
|
|
403
|
-
Returns
|
411
|
+
Returns:
|
404
412
|
-------
|
405
413
|
float | Pandas.Series[float]
|
406
414
|
Skew of the return distribution
|
@@ -412,7 +420,7 @@ class _CommonModel(BaseModel):
|
|
412
420
|
def kurtosis(self: Self) -> float | Series[float]:
|
413
421
|
"""https://www.investopedia.com/terms/k/kurtosis.asp.
|
414
422
|
|
415
|
-
Returns
|
423
|
+
Returns:
|
416
424
|
-------
|
417
425
|
float | Pandas.Series[float]
|
418
426
|
Kurtosis of the return distribution
|
@@ -424,7 +432,7 @@ class _CommonModel(BaseModel):
|
|
424
432
|
def cvar_down(self: Self) -> float | Series[float]:
|
425
433
|
"""https://www.investopedia.com/terms/c/conditional_value_at_risk.asp.
|
426
434
|
|
427
|
-
Returns
|
435
|
+
Returns:
|
428
436
|
-------
|
429
437
|
float | Pandas.Series[float]
|
430
438
|
Downside 95% Conditional Value At Risk "CVaR"
|
@@ -440,7 +448,7 @@ class _CommonModel(BaseModel):
|
|
440
448
|
The equivalent of percentile.inc([...], 1-level) over returns in MS Excel.
|
441
449
|
https://www.investopedia.com/terms/v/var.asp.
|
442
450
|
|
443
|
-
Returns
|
451
|
+
Returns:
|
444
452
|
-------
|
445
453
|
float | Pandas.Series[float]
|
446
454
|
Downside 95% Value At Risk (VaR)
|
@@ -456,7 +464,7 @@ class _CommonModel(BaseModel):
|
|
456
464
|
|
457
465
|
Assumes that returns are normally distributed.
|
458
466
|
|
459
|
-
Returns
|
467
|
+
Returns:
|
460
468
|
-------
|
461
469
|
float | Pandas.Series[float]
|
462
470
|
Implied annualized volatility from the Downside 95% VaR using the
|
@@ -485,7 +493,7 @@ class _CommonModel(BaseModel):
|
|
485
493
|
to_dt: datetime.date, optional
|
486
494
|
Specific from date
|
487
495
|
|
488
|
-
Returns
|
496
|
+
Returns:
|
489
497
|
-------
|
490
498
|
tuple[datetime.date, datetime.date]
|
491
499
|
Start and end date of the chosen date range
|
@@ -504,18 +512,18 @@ class _CommonModel(BaseModel):
|
|
504
512
|
"Argument months_offset implies start"
|
505
513
|
"date before first date in series."
|
506
514
|
)
|
507
|
-
raise
|
515
|
+
raise DateAlignmentError(msg)
|
508
516
|
later = self.last_idx
|
509
517
|
else:
|
510
518
|
if from_dt is not None:
|
511
519
|
if from_dt < self.first_idx:
|
512
520
|
msg = "Given from_dt date < series start"
|
513
|
-
raise
|
521
|
+
raise DateAlignmentError(msg)
|
514
522
|
earlier = from_dt
|
515
523
|
if to_dt is not None:
|
516
524
|
if to_dt > self.last_idx:
|
517
525
|
msg = "Given to_dt date > series end"
|
518
|
-
raise
|
526
|
+
raise DateAlignmentError(msg)
|
519
527
|
later = to_dt
|
520
528
|
while earlier not in self.tsdf.index:
|
521
529
|
earlier -= dt.timedelta(days=1)
|
@@ -535,14 +543,14 @@ class _CommonModel(BaseModel):
|
|
535
543
|
countries: CountriesType, default: "SE"
|
536
544
|
(List of) country code(s) according to ISO 3166-1 alpha-2
|
537
545
|
|
538
|
-
Returns
|
546
|
+
Returns:
|
539
547
|
-------
|
540
548
|
OpenFrame
|
541
549
|
An OpenFrame object
|
542
550
|
|
543
551
|
"""
|
544
|
-
startyear = to_datetime(self.tsdf.index[0]).year
|
545
|
-
endyear = to_datetime(self.tsdf.index[-1]).year
|
552
|
+
startyear = cast("int", to_datetime(self.tsdf.index[0]).year)
|
553
|
+
endyear = cast("int", to_datetime(self.tsdf.index[-1]).year)
|
546
554
|
calendar = holiday_calendar(
|
547
555
|
startyear=startyear,
|
548
556
|
endyear=endyear,
|
@@ -552,8 +560,8 @@ class _CommonModel(BaseModel):
|
|
552
560
|
d_range = [
|
553
561
|
d.date()
|
554
562
|
for d in date_range(
|
555
|
-
start=cast(dt.date, self.tsdf.first_valid_index()),
|
556
|
-
end=cast(dt.date, self.tsdf.last_valid_index()),
|
563
|
+
start=cast("dt.date", self.tsdf.first_valid_index()),
|
564
|
+
end=cast("dt.date", self.tsdf.last_valid_index()),
|
557
565
|
freq=CustomBusinessDay(calendar=calendar),
|
558
566
|
)
|
559
567
|
]
|
@@ -566,7 +574,7 @@ class _CommonModel(BaseModel):
|
|
566
574
|
|
567
575
|
Equivalent to LN(value[t] / value[t=0]) in Excel.
|
568
576
|
|
569
|
-
Returns
|
577
|
+
Returns:
|
570
578
|
-------
|
571
579
|
self
|
572
580
|
An object of the same class
|
@@ -587,7 +595,7 @@ class _CommonModel(BaseModel):
|
|
587
595
|
method: LiteralNanMethod, default: "fill"
|
588
596
|
Method used to handle NaN. Either fill with last known or drop
|
589
597
|
|
590
|
-
Returns
|
598
|
+
Returns:
|
591
599
|
-------
|
592
600
|
self
|
593
601
|
An object of the same class
|
@@ -607,7 +615,7 @@ class _CommonModel(BaseModel):
|
|
607
615
|
method: LiteralNanMethod, default: "fill"
|
608
616
|
Method used to handle NaN. Either fill with zero or drop
|
609
617
|
|
610
|
-
Returns
|
618
|
+
Returns:
|
611
619
|
-------
|
612
620
|
self
|
613
621
|
An object of the same class
|
@@ -622,7 +630,7 @@ class _CommonModel(BaseModel):
|
|
622
630
|
def to_drawdown_series(self: Self) -> Self:
|
623
631
|
"""Convert timeseries into a drawdown series.
|
624
632
|
|
625
|
-
Returns
|
633
|
+
Returns:
|
626
634
|
-------
|
627
635
|
self
|
628
636
|
An object of the same class
|
@@ -652,7 +660,7 @@ class _CommonModel(BaseModel):
|
|
652
660
|
directory: DirectoryPath, optional
|
653
661
|
File folder location
|
654
662
|
|
655
|
-
Returns
|
663
|
+
Returns:
|
656
664
|
-------
|
657
665
|
list[dict[str, str | bool | ValueType | list[str] | list[float]]]
|
658
666
|
A list of dictionaries with the data of the series
|
@@ -670,17 +678,17 @@ class _CommonModel(BaseModel):
|
|
670
678
|
output = []
|
671
679
|
if "label" in data:
|
672
680
|
if what_output == "tsdf":
|
673
|
-
values = self.tsdf.iloc[:, 0].tolist()
|
681
|
+
values = Series(self.tsdf.iloc[:, 0]).tolist()
|
674
682
|
else:
|
675
|
-
values = list(cast(list[float], data.get("values")))
|
683
|
+
values = list(cast("list[float]", data.get("values")))
|
676
684
|
for item in cleaner_list:
|
677
685
|
data.pop(item)
|
678
|
-
valuetype = cast(ValueType, data.get("valuetype")).value
|
686
|
+
valuetype = cast("ValueType", data.get("valuetype")).value
|
679
687
|
data.update({"valuetype": valuetype})
|
680
688
|
data.update({"values": values})
|
681
689
|
output.append(dict(data))
|
682
690
|
else:
|
683
|
-
for serie in cast(list[Any], data.get("constituents")):
|
691
|
+
for serie in cast("list[Any]", data.get("constituents")):
|
684
692
|
if what_output == "tsdf":
|
685
693
|
values = serie.tsdf.iloc[:, 0].tolist()
|
686
694
|
else:
|
@@ -688,7 +696,7 @@ class _CommonModel(BaseModel):
|
|
688
696
|
itemdata = dict(serie.__dict__)
|
689
697
|
for item in cleaner_list:
|
690
698
|
itemdata.pop(item)
|
691
|
-
valuetype = cast(ValueType, itemdata["valuetype"]).value
|
699
|
+
valuetype = cast("ValueType", itemdata["valuetype"]).value
|
692
700
|
itemdata.update({"valuetype": valuetype})
|
693
701
|
itemdata.update({"values": values})
|
694
702
|
output.append(dict(itemdata))
|
@@ -719,7 +727,7 @@ class _CommonModel(BaseModel):
|
|
719
727
|
overwrite: bool, default: True
|
720
728
|
Flag whether to overwrite an existing file
|
721
729
|
|
722
|
-
Returns
|
730
|
+
Returns:
|
723
731
|
-------
|
724
732
|
str
|
725
733
|
The Excel file path
|
@@ -742,10 +750,10 @@ class _CommonModel(BaseModel):
|
|
742
750
|
wrksheet = wrkbook.active
|
743
751
|
|
744
752
|
if sheet_title:
|
745
|
-
cast(Worksheet, wrksheet).title = sheet_title
|
753
|
+
cast("Worksheet", wrksheet).title = sheet_title
|
746
754
|
|
747
755
|
for row in dataframe_to_rows(df=self.tsdf, index=True, header=True):
|
748
|
-
cast(Worksheet, wrksheet).append(row)
|
756
|
+
cast("Worksheet", wrksheet).append(row)
|
749
757
|
|
750
758
|
if not overwrite and Path(sheetfile).exists():
|
751
759
|
msg = f"{sheetfile!s} already exists."
|
@@ -794,7 +802,7 @@ class _CommonModel(BaseModel):
|
|
794
802
|
add_logo: bool, default: True
|
795
803
|
If True a Captor logo is added to the plot
|
796
804
|
|
797
|
-
Returns
|
805
|
+
Returns:
|
798
806
|
-------
|
799
807
|
tuple[plotly.go.Figure, str]
|
800
808
|
Plotly Figure and a div section or a html filename with location
|
@@ -803,7 +811,7 @@ class _CommonModel(BaseModel):
|
|
803
811
|
if labels:
|
804
812
|
if len(labels) != self.tsdf.shape[1]:
|
805
813
|
msg = "Must provide same number of labels as items in frame."
|
806
|
-
raise
|
814
|
+
raise NumberOfItemsAndLabelsNotSameError(msg)
|
807
815
|
else:
|
808
816
|
labels = list(self.tsdf.columns.get_level_values(0))
|
809
817
|
|
@@ -849,7 +857,7 @@ class _CommonModel(BaseModel):
|
|
849
857
|
auto_open=auto_open,
|
850
858
|
auto_play=False,
|
851
859
|
link_text="",
|
852
|
-
include_plotlyjs=cast(bool, include_plotlyjs),
|
860
|
+
include_plotlyjs=cast("bool", include_plotlyjs),
|
853
861
|
config=fig["config"],
|
854
862
|
output_type=output_type,
|
855
863
|
)
|
@@ -860,14 +868,14 @@ class _CommonModel(BaseModel):
|
|
860
868
|
fig=figure,
|
861
869
|
config=fig["config"],
|
862
870
|
auto_play=False,
|
863
|
-
include_plotlyjs=cast(bool, include_plotlyjs),
|
871
|
+
include_plotlyjs=cast("bool", include_plotlyjs),
|
864
872
|
full_html=False,
|
865
873
|
div_id=div_id,
|
866
874
|
)
|
867
875
|
|
868
876
|
return figure, string_output
|
869
877
|
|
870
|
-
def plot_series(
|
878
|
+
def plot_series(
|
871
879
|
self: Self,
|
872
880
|
mode: LiteralLinePlotMode = "lines",
|
873
881
|
tick_fmt: str | None = None,
|
@@ -909,7 +917,7 @@ class _CommonModel(BaseModel):
|
|
909
917
|
show_last: bool, default: False
|
910
918
|
If True the last self.tsdf point is highlighted as red dot with a label
|
911
919
|
|
912
|
-
Returns
|
920
|
+
Returns:
|
913
921
|
-------
|
914
922
|
tuple[plotly.go.Figure, str]
|
915
923
|
Plotly Figure and a div section or a html filename with location
|
@@ -918,7 +926,7 @@ class _CommonModel(BaseModel):
|
|
918
926
|
if labels:
|
919
927
|
if len(labels) != self.tsdf.shape[1]:
|
920
928
|
msg = "Must provide same number of labels as items in frame."
|
921
|
-
raise
|
929
|
+
raise NumberOfItemsAndLabelsNotSameError(msg)
|
922
930
|
else:
|
923
931
|
labels = list(self.tsdf.columns.get_level_values(0))
|
924
932
|
|
@@ -958,7 +966,7 @@ class _CommonModel(BaseModel):
|
|
958
966
|
|
959
967
|
for item in range(self.tsdf.shape[1]):
|
960
968
|
figure.add_scatter(
|
961
|
-
x=[self.tsdf.iloc[:, item].index[-1]],
|
969
|
+
x=[Series(self.tsdf.iloc[:, item]).index[-1]],
|
962
970
|
y=[self.tsdf.iloc[-1, item]],
|
963
971
|
mode="markers + text",
|
964
972
|
marker={"color": "red", "size": 12},
|
@@ -979,7 +987,7 @@ class _CommonModel(BaseModel):
|
|
979
987
|
auto_open=auto_open,
|
980
988
|
auto_play=False,
|
981
989
|
link_text="",
|
982
|
-
include_plotlyjs=cast(bool, include_plotlyjs),
|
990
|
+
include_plotlyjs=cast("bool", include_plotlyjs),
|
983
991
|
config=fig["config"],
|
984
992
|
output_type=output_type,
|
985
993
|
)
|
@@ -990,7 +998,7 @@ class _CommonModel(BaseModel):
|
|
990
998
|
fig=figure,
|
991
999
|
config=fig["config"],
|
992
1000
|
auto_play=False,
|
993
|
-
include_plotlyjs=cast(bool, include_plotlyjs),
|
1001
|
+
include_plotlyjs=cast("bool", include_plotlyjs),
|
994
1002
|
full_html=False,
|
995
1003
|
div_id=div_id,
|
996
1004
|
)
|
@@ -1019,7 +1027,7 @@ class _CommonModel(BaseModel):
|
|
1019
1027
|
Allows locking the periods-in-a-year to simplify test cases and
|
1020
1028
|
comparisons
|
1021
1029
|
|
1022
|
-
Returns
|
1030
|
+
Returns:
|
1023
1031
|
-------
|
1024
1032
|
float | Pandas.Series[float]
|
1025
1033
|
Annualized arithmetic mean of returns
|
@@ -1035,13 +1043,13 @@ class _CommonModel(BaseModel):
|
|
1035
1043
|
else:
|
1036
1044
|
fraction = (later - earlier).days / 365.25
|
1037
1045
|
how_many = self.tsdf.loc[
|
1038
|
-
cast(int, earlier) : cast(int, later),
|
1046
|
+
cast("int", earlier) : cast("int", later),
|
1039
1047
|
self.tsdf.columns.to_numpy()[0],
|
1040
1048
|
].count()
|
1041
|
-
time_factor = how_many / fraction
|
1049
|
+
time_factor = cast("int", how_many) / fraction
|
1042
1050
|
|
1043
1051
|
result = (
|
1044
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1052
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1045
1053
|
.ffill()
|
1046
1054
|
.pct_change()
|
1047
1055
|
.mean()
|
@@ -1081,7 +1089,7 @@ class _CommonModel(BaseModel):
|
|
1081
1089
|
periods_in_a_year_fixed : DaysInYearType, optional
|
1082
1090
|
Allows locking the periods-in-a-year to simplify test cases and comparisons
|
1083
1091
|
|
1084
|
-
Returns
|
1092
|
+
Returns:
|
1085
1093
|
-------
|
1086
1094
|
float | Pandas.Series[float]
|
1087
1095
|
Annualized volatility
|
@@ -1097,15 +1105,17 @@ class _CommonModel(BaseModel):
|
|
1097
1105
|
else:
|
1098
1106
|
fraction = (later - earlier).days / 365.25
|
1099
1107
|
how_many = (
|
1100
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1108
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1109
|
+
.count()
|
1110
|
+
.iloc[0]
|
1101
1111
|
)
|
1102
1112
|
time_factor = how_many / fraction
|
1103
1113
|
|
1104
|
-
data = self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1114
|
+
data = self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1105
1115
|
result = data.ffill().pct_change().std().mul(sqrt(time_factor))
|
1106
1116
|
|
1107
1117
|
if self.tsdf.shape[1] == 1:
|
1108
|
-
return float(cast(SupportsFloat, result.iloc[0]))
|
1118
|
+
return float(cast("SupportsFloat", result.iloc[0]))
|
1109
1119
|
return Series(
|
1110
1120
|
data=result,
|
1111
1121
|
index=self.tsdf.columns,
|
@@ -1148,7 +1158,7 @@ class _CommonModel(BaseModel):
|
|
1148
1158
|
drift_adjust: bool, default: False
|
1149
1159
|
An adjustment to remove the bias implied by the average return
|
1150
1160
|
|
1151
|
-
Returns
|
1161
|
+
Returns:
|
1152
1162
|
-------
|
1153
1163
|
float | Pandas.Series[float]
|
1154
1164
|
Implied annualized volatility from the Downside VaR using the
|
@@ -1209,7 +1219,7 @@ class _CommonModel(BaseModel):
|
|
1209
1219
|
drift_adjust: bool, default: False
|
1210
1220
|
An adjustment to remove the bias implied by the average return
|
1211
1221
|
|
1212
|
-
Returns
|
1222
|
+
Returns:
|
1213
1223
|
-------
|
1214
1224
|
float | Pandas.Series[float]
|
1215
1225
|
A position weight multiplier from the ratio between a VaR implied
|
@@ -1275,7 +1285,7 @@ class _CommonModel(BaseModel):
|
|
1275
1285
|
drift_adjust: bool, default: False
|
1276
1286
|
An adjustment to remove the bias implied by the average return
|
1277
1287
|
|
1278
|
-
Returns
|
1288
|
+
Returns:
|
1279
1289
|
-------
|
1280
1290
|
float | Pandas.Series[float]
|
1281
1291
|
Target volatility if target_vol is provided otherwise the VaR
|
@@ -1292,21 +1302,23 @@ class _CommonModel(BaseModel):
|
|
1292
1302
|
else:
|
1293
1303
|
fraction = (later - earlier).days / 365.25
|
1294
1304
|
how_many = (
|
1295
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1305
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1306
|
+
.count()
|
1307
|
+
.iloc[0]
|
1296
1308
|
)
|
1297
1309
|
time_factor = how_many / fraction
|
1298
1310
|
if drift_adjust:
|
1299
1311
|
imp_vol = (-sqrt(time_factor) / norm.ppf(level)) * (
|
1300
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1312
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1301
1313
|
.ffill()
|
1302
1314
|
.pct_change()
|
1303
1315
|
.quantile(1 - level, interpolation=interpolation)
|
1304
|
-
- self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1316
|
+
- self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1305
1317
|
.ffill()
|
1306
1318
|
.pct_change()
|
1307
1319
|
.sum()
|
1308
1320
|
/ len(
|
1309
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1321
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1310
1322
|
.ffill()
|
1311
1323
|
.pct_change(),
|
1312
1324
|
)
|
@@ -1314,7 +1326,7 @@ class _CommonModel(BaseModel):
|
|
1314
1326
|
else:
|
1315
1327
|
imp_vol = (
|
1316
1328
|
-sqrt(time_factor)
|
1317
|
-
* self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1329
|
+
* self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1318
1330
|
.ffill()
|
1319
1331
|
.pct_change()
|
1320
1332
|
.quantile(1 - level, interpolation=interpolation)
|
@@ -1334,7 +1346,7 @@ class _CommonModel(BaseModel):
|
|
1334
1346
|
label = f"Imp vol from VaR {level:.0%}"
|
1335
1347
|
|
1336
1348
|
if self.tsdf.shape[1] == 1:
|
1337
|
-
return float(cast(SupportsFloat, result.iloc[0]))
|
1349
|
+
return float(cast("SupportsFloat", result.iloc[0]))
|
1338
1350
|
return Series(
|
1339
1351
|
data=result,
|
1340
1352
|
index=self.tsdf.columns,
|
@@ -1365,7 +1377,7 @@ class _CommonModel(BaseModel):
|
|
1365
1377
|
to_date : datetime.date, optional
|
1366
1378
|
Specific to date
|
1367
1379
|
|
1368
|
-
Returns
|
1380
|
+
Returns:
|
1369
1381
|
-------
|
1370
1382
|
float | Pandas.Series[float]
|
1371
1383
|
Downside Conditional Value At Risk "CVaR"
|
@@ -1376,22 +1388,22 @@ class _CommonModel(BaseModel):
|
|
1376
1388
|
from_dt=from_date,
|
1377
1389
|
to_dt=to_date,
|
1378
1390
|
)
|
1379
|
-
cvar_df = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy(
|
1391
|
+
cvar_df = self.tsdf.loc[cast("int", earlier) : cast("int", later)].copy(
|
1392
|
+
deep=True
|
1393
|
+
)
|
1380
1394
|
result = [
|
1381
1395
|
cvar_df.loc[:, x] # type: ignore[call-overload,index]
|
1382
1396
|
.ffill()
|
1383
1397
|
.pct_change()
|
1384
1398
|
.sort_values()
|
1385
1399
|
.iloc[
|
1386
|
-
:
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
),
|
1394
|
-
)
|
1400
|
+
: ceil(
|
1401
|
+
(1 - level)
|
1402
|
+
* cvar_df.loc[:, x] # type: ignore[index]
|
1403
|
+
.ffill()
|
1404
|
+
.pct_change()
|
1405
|
+
.count(),
|
1406
|
+
),
|
1395
1407
|
]
|
1396
1408
|
.mean()
|
1397
1409
|
for x in self.tsdf
|
@@ -1434,7 +1446,7 @@ class _CommonModel(BaseModel):
|
|
1434
1446
|
Allows locking the periods-in-a-year to simplify test cases and
|
1435
1447
|
comparisons
|
1436
1448
|
|
1437
|
-
Returns
|
1449
|
+
Returns:
|
1438
1450
|
-------
|
1439
1451
|
float | Pandas.Series[float]
|
1440
1452
|
Downside deviation
|
@@ -1447,7 +1459,7 @@ class _CommonModel(BaseModel):
|
|
1447
1459
|
to_dt=to_date,
|
1448
1460
|
)
|
1449
1461
|
how_many = (
|
1450
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1462
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1451
1463
|
.ffill()
|
1452
1464
|
.pct_change()
|
1453
1465
|
.count(numeric_only=True)
|
@@ -1463,7 +1475,7 @@ class _CommonModel(BaseModel):
|
|
1463
1475
|
time_factor = how_many.div(fraction)
|
1464
1476
|
|
1465
1477
|
dddf = (
|
1466
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1478
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1467
1479
|
.ffill()
|
1468
1480
|
.pct_change()
|
1469
1481
|
.sub(min_accepted_return / time_factor)
|
@@ -1502,7 +1514,7 @@ class _CommonModel(BaseModel):
|
|
1502
1514
|
to_date : datetime.date, optional
|
1503
1515
|
Specific to date
|
1504
1516
|
|
1505
|
-
Returns
|
1517
|
+
Returns:
|
1506
1518
|
-------
|
1507
1519
|
float | Pandas.Series[float]
|
1508
1520
|
Compounded Annual Growth Rate (CAGR)
|
@@ -1516,13 +1528,18 @@ class _CommonModel(BaseModel):
|
|
1516
1528
|
)
|
1517
1529
|
fraction = (later - earlier).days / 365.25
|
1518
1530
|
|
1519
|
-
any_below_zero = any(
|
1531
|
+
any_below_zero = any(
|
1532
|
+
self.tsdf.loc[[earlier, later]] # type: ignore[index]
|
1533
|
+
.lt(0.0)
|
1534
|
+
.any()
|
1535
|
+
.to_numpy()
|
1536
|
+
)
|
1520
1537
|
if zero in self.tsdf.loc[earlier].to_numpy() or any_below_zero:
|
1521
1538
|
msg = (
|
1522
1539
|
"Geometric return cannot be calculated due to "
|
1523
1540
|
"an initial value being zero or a negative value."
|
1524
1541
|
)
|
1525
|
-
raise
|
1542
|
+
raise InitialValueZeroError(msg)
|
1526
1543
|
|
1527
1544
|
result = (self.tsdf.loc[later] / self.tsdf.loc[earlier]) ** (1 / fraction) - 1
|
1528
1545
|
|
@@ -1555,7 +1572,7 @@ class _CommonModel(BaseModel):
|
|
1555
1572
|
to_date : datetime.date, optional
|
1556
1573
|
Specific to date
|
1557
1574
|
|
1558
|
-
Returns
|
1575
|
+
Returns:
|
1559
1576
|
-------
|
1560
1577
|
float | Pandas.Series[float]
|
1561
1578
|
Skew of the return distribution
|
@@ -1567,7 +1584,7 @@ class _CommonModel(BaseModel):
|
|
1567
1584
|
to_dt=to_date,
|
1568
1585
|
)
|
1569
1586
|
result: NDArray[float64] = skew(
|
1570
|
-
a=self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1587
|
+
a=self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1571
1588
|
.ffill()
|
1572
1589
|
.pct_change()
|
1573
1590
|
.to_numpy(),
|
@@ -1604,7 +1621,7 @@ class _CommonModel(BaseModel):
|
|
1604
1621
|
to_date : datetime.date, optional
|
1605
1622
|
Specific to date
|
1606
1623
|
|
1607
|
-
Returns
|
1624
|
+
Returns:
|
1608
1625
|
-------
|
1609
1626
|
float | Pandas.Series[float]
|
1610
1627
|
Kurtosis of the return distribution
|
@@ -1616,7 +1633,11 @@ class _CommonModel(BaseModel):
|
|
1616
1633
|
to_dt=to_date,
|
1617
1634
|
)
|
1618
1635
|
result: NDArray[float64] = kurtosis(
|
1619
|
-
|
1636
|
+
a=(
|
1637
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1638
|
+
.ffill()
|
1639
|
+
.pct_change()
|
1640
|
+
),
|
1620
1641
|
fisher=True,
|
1621
1642
|
bias=True,
|
1622
1643
|
nan_policy="omit",
|
@@ -1654,7 +1675,7 @@ class _CommonModel(BaseModel):
|
|
1654
1675
|
min_periods: int, default: 1
|
1655
1676
|
Smallest number of observations to use to find the maximum drawdown
|
1656
1677
|
|
1657
|
-
Returns
|
1678
|
+
Returns:
|
1658
1679
|
-------
|
1659
1680
|
float | Pandas.Series[float]
|
1660
1681
|
Maximum drawdown without any limit on date range
|
@@ -1666,8 +1687,8 @@ class _CommonModel(BaseModel):
|
|
1666
1687
|
to_dt=to_date,
|
1667
1688
|
)
|
1668
1689
|
result = (
|
1669
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1670
|
-
/ self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1690
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1691
|
+
/ self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1671
1692
|
.expanding(min_periods=min_periods)
|
1672
1693
|
.max()
|
1673
1694
|
).min() - 1
|
@@ -1698,7 +1719,7 @@ class _CommonModel(BaseModel):
|
|
1698
1719
|
to_date : datetime.date, optional
|
1699
1720
|
Specific to date
|
1700
1721
|
|
1701
|
-
Returns
|
1722
|
+
Returns:
|
1702
1723
|
-------
|
1703
1724
|
float | Pandas.Series[float]
|
1704
1725
|
Calculate share of percentage changes that are greater than zero
|
@@ -1711,10 +1732,10 @@ class _CommonModel(BaseModel):
|
|
1711
1732
|
to_dt=to_date,
|
1712
1733
|
)
|
1713
1734
|
pos = (
|
1714
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1735
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1715
1736
|
.ffill()
|
1716
1737
|
.pct_change()[1:][
|
1717
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1738
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1718
1739
|
.ffill()
|
1719
1740
|
.pct_change()[1:]
|
1720
1741
|
> zero
|
@@ -1722,7 +1743,7 @@ class _CommonModel(BaseModel):
|
|
1722
1743
|
.count()
|
1723
1744
|
)
|
1724
1745
|
tot = (
|
1725
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1746
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1726
1747
|
.ffill()
|
1727
1748
|
.pct_change()
|
1728
1749
|
.count()
|
@@ -1768,7 +1789,7 @@ class _CommonModel(BaseModel):
|
|
1768
1789
|
Allows locking the periods-in-a-year to simplify test cases and
|
1769
1790
|
comparisons
|
1770
1791
|
|
1771
|
-
Returns
|
1792
|
+
Returns:
|
1772
1793
|
-------
|
1773
1794
|
float | Pandas.Series[float]
|
1774
1795
|
Ratio of the annualized arithmetic mean of returns and annualized
|
@@ -1791,7 +1812,7 @@ class _CommonModel(BaseModel):
|
|
1791
1812
|
)
|
1792
1813
|
|
1793
1814
|
if self.tsdf.shape[1] == 1:
|
1794
|
-
return float(cast(float64, ratio.iloc[0]))
|
1815
|
+
return float(cast("float64", ratio.iloc[0]))
|
1795
1816
|
return Series(
|
1796
1817
|
data=ratio,
|
1797
1818
|
index=self.tsdf.columns,
|
@@ -1833,7 +1854,7 @@ class _CommonModel(BaseModel):
|
|
1833
1854
|
Allows locking the periods-in-a-year to simplify test cases and
|
1834
1855
|
comparisons
|
1835
1856
|
|
1836
|
-
Returns
|
1857
|
+
Returns:
|
1837
1858
|
-------
|
1838
1859
|
float | Pandas.Series[float]
|
1839
1860
|
Sortino ratio calculated as ( return - riskfree return ) /
|
@@ -1857,7 +1878,7 @@ class _CommonModel(BaseModel):
|
|
1857
1878
|
)
|
1858
1879
|
|
1859
1880
|
if self.tsdf.shape[1] == 1:
|
1860
|
-
return float(cast(float64, ratio.iloc[0]))
|
1881
|
+
return float(cast("float64", ratio.iloc[0]))
|
1861
1882
|
return Series(
|
1862
1883
|
data=ratio,
|
1863
1884
|
index=self.tsdf.columns,
|
@@ -1891,7 +1912,7 @@ class _CommonModel(BaseModel):
|
|
1891
1912
|
to_date : datetime.date, optional
|
1892
1913
|
Specific to date
|
1893
1914
|
|
1894
|
-
Returns
|
1915
|
+
Returns:
|
1895
1916
|
-------
|
1896
1917
|
float | Pandas.Series[float]
|
1897
1918
|
Omega ratio calculation
|
@@ -1903,14 +1924,16 @@ class _CommonModel(BaseModel):
|
|
1903
1924
|
to_dt=to_date,
|
1904
1925
|
)
|
1905
1926
|
retdf = (
|
1906
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
1927
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
1928
|
+
.ffill()
|
1929
|
+
.pct_change()
|
1907
1930
|
)
|
1908
1931
|
pos = retdf[retdf > min_accepted_return].sub(min_accepted_return).sum()
|
1909
1932
|
neg = retdf[retdf < min_accepted_return].sub(min_accepted_return).sum()
|
1910
1933
|
ratio = pos / -neg
|
1911
1934
|
|
1912
1935
|
if self.tsdf.shape[1] == 1:
|
1913
|
-
return float(cast(float64, ratio.iloc[0]))
|
1936
|
+
return float(cast("float64", ratio.iloc[0]))
|
1914
1937
|
return Series(
|
1915
1938
|
data=ratio,
|
1916
1939
|
index=self.tsdf.columns,
|
@@ -1936,7 +1959,7 @@ class _CommonModel(BaseModel):
|
|
1936
1959
|
to_date : datetime.date, optional
|
1937
1960
|
Specific to date
|
1938
1961
|
|
1939
|
-
Returns
|
1962
|
+
Returns:
|
1940
1963
|
-------
|
1941
1964
|
float | Pandas.Series[float]
|
1942
1965
|
Calculate simple return
|
@@ -1953,7 +1976,7 @@ class _CommonModel(BaseModel):
|
|
1953
1976
|
"Simple return cannot be calculated due to "
|
1954
1977
|
f"an initial value being zero. ({self.tsdf.head(3)})"
|
1955
1978
|
)
|
1956
|
-
raise
|
1979
|
+
raise InitialValueZeroError(msg)
|
1957
1980
|
|
1958
1981
|
result = self.tsdf.loc[later] / self.tsdf.loc[earlier] - 1
|
1959
1982
|
|
@@ -1980,7 +2003,7 @@ class _CommonModel(BaseModel):
|
|
1980
2003
|
month : int, optional
|
1981
2004
|
Calendar month of the period to calculate.
|
1982
2005
|
|
1983
|
-
Returns
|
2006
|
+
Returns:
|
1984
2007
|
-------
|
1985
2008
|
float | Pandas.Series[float]
|
1986
2009
|
Calculate simple return for a specific calendar period
|
@@ -2031,7 +2054,7 @@ class _CommonModel(BaseModel):
|
|
2031
2054
|
interpolation: LiteralQuantileInterp, default: "lower"
|
2032
2055
|
Type of interpolation in Pandas.DataFrame.quantile() function.
|
2033
2056
|
|
2034
|
-
Returns
|
2057
|
+
Returns:
|
2035
2058
|
-------
|
2036
2059
|
float | Pandas.Series[float]
|
2037
2060
|
Downside Value At Risk
|
@@ -2043,7 +2066,7 @@ class _CommonModel(BaseModel):
|
|
2043
2066
|
to_dt=to_date,
|
2044
2067
|
)
|
2045
2068
|
result = (
|
2046
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
2069
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
2047
2070
|
.ffill()
|
2048
2071
|
.pct_change()
|
2049
2072
|
.quantile(1 - level, interpolation=interpolation)
|
@@ -2079,7 +2102,7 @@ class _CommonModel(BaseModel):
|
|
2079
2102
|
to_date : datetime.date, optional
|
2080
2103
|
Specific to date
|
2081
2104
|
|
2082
|
-
Returns
|
2105
|
+
Returns:
|
2083
2106
|
-------
|
2084
2107
|
float | Pandas.Series[float]
|
2085
2108
|
Most negative percentage change over a rolling number of observations
|
@@ -2092,7 +2115,7 @@ class _CommonModel(BaseModel):
|
|
2092
2115
|
to_dt=to_date,
|
2093
2116
|
)
|
2094
2117
|
result = (
|
2095
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
2118
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
2096
2119
|
.ffill()
|
2097
2120
|
.pct_change()
|
2098
2121
|
.rolling(observations, min_periods=observations)
|
@@ -2129,7 +2152,7 @@ class _CommonModel(BaseModel):
|
|
2129
2152
|
to_date : datetime.date, optional
|
2130
2153
|
Specific to date
|
2131
2154
|
|
2132
|
-
Returns
|
2155
|
+
Returns:
|
2133
2156
|
-------
|
2134
2157
|
float | Pandas.Series[float]
|
2135
2158
|
Z-score as (last return - mean return) / standard deviation of returns
|
@@ -2141,7 +2164,9 @@ class _CommonModel(BaseModel):
|
|
2141
2164
|
to_dt=to_date,
|
2142
2165
|
)
|
2143
2166
|
zscframe = (
|
2144
|
-
self.tsdf.loc[cast(int, earlier) : cast(int, later)]
|
2167
|
+
self.tsdf.loc[cast("int", earlier) : cast("int", later)]
|
2168
|
+
.ffill()
|
2169
|
+
.pct_change()
|
2145
2170
|
)
|
2146
2171
|
result = (zscframe.iloc[-1] - zscframe.mean()) / zscframe.std()
|
2147
2172
|
|
@@ -2171,15 +2196,15 @@ class _CommonModel(BaseModel):
|
|
2171
2196
|
observations: int, default: 252
|
2172
2197
|
Number of observations in the overlapping window.
|
2173
2198
|
|
2174
|
-
Returns
|
2199
|
+
Returns:
|
2175
2200
|
-------
|
2176
2201
|
Pandas.DataFrame
|
2177
2202
|
Calculate rolling annualized downside CVaR
|
2178
2203
|
|
2179
2204
|
"""
|
2180
|
-
cvar_label = cast(tuple[str], self.tsdf.iloc[:, column].name)[0]
|
2205
|
+
cvar_label = cast("tuple[str]", self.tsdf.iloc[:, column].name)[0]
|
2181
2206
|
cvarseries = (
|
2182
|
-
self.tsdf.iloc[:, column]
|
2207
|
+
Series(self.tsdf.iloc[:, column])
|
2183
2208
|
.rolling(observations, min_periods=observations)
|
2184
2209
|
.apply(lambda x: _cvar_down_calc(x, level=level))
|
2185
2210
|
)
|
@@ -2202,15 +2227,15 @@ class _CommonModel(BaseModel):
|
|
2202
2227
|
observations: int, default: 21
|
2203
2228
|
Number of observations in the overlapping window.
|
2204
2229
|
|
2205
|
-
Returns
|
2230
|
+
Returns:
|
2206
2231
|
-------
|
2207
2232
|
Pandas.DataFrame
|
2208
2233
|
Calculate rolling returns
|
2209
2234
|
|
2210
2235
|
"""
|
2211
|
-
ret_label = cast(tuple[str], self.tsdf.iloc[:, column].name)[0]
|
2236
|
+
ret_label = cast("tuple[str]", self.tsdf.iloc[:, column].name)[0]
|
2212
2237
|
retseries = (
|
2213
|
-
self.tsdf.iloc[:, column]
|
2238
|
+
Series(self.tsdf.iloc[:, column])
|
2214
2239
|
.ffill()
|
2215
2240
|
.pct_change()
|
2216
2241
|
.rolling(observations, min_periods=observations)
|
@@ -2241,15 +2266,15 @@ class _CommonModel(BaseModel):
|
|
2241
2266
|
interpolation: LiteralQuantileInterp, default: "lower"
|
2242
2267
|
Type of interpolation in Pandas.DataFrame.quantile() function.
|
2243
2268
|
|
2244
|
-
Returns
|
2269
|
+
Returns:
|
2245
2270
|
-------
|
2246
2271
|
Pandas.DataFrame
|
2247
2272
|
Calculate rolling annualized downside Value At Risk "VaR"
|
2248
2273
|
|
2249
2274
|
"""
|
2250
|
-
var_label = cast(tuple[str], self.tsdf.iloc[:, column].name)[0]
|
2275
|
+
var_label = cast("tuple[str]", self.tsdf.iloc[:, column].name)[0]
|
2251
2276
|
varseries = (
|
2252
|
-
self.tsdf.iloc[:, column]
|
2277
|
+
Series(self.tsdf.iloc[:, column])
|
2253
2278
|
.rolling(observations, min_periods=observations)
|
2254
2279
|
.apply(
|
2255
2280
|
lambda x: _var_down_calc(x, level=level, interpolation=interpolation),
|
@@ -2278,7 +2303,7 @@ class _CommonModel(BaseModel):
|
|
2278
2303
|
Allows locking the periods-in-a-year to simplify test cases and
|
2279
2304
|
comparisons
|
2280
2305
|
|
2281
|
-
Returns
|
2306
|
+
Returns:
|
2282
2307
|
-------
|
2283
2308
|
Pandas.DataFrame
|
2284
2309
|
Calculate rolling annualised volatilities
|
@@ -2288,8 +2313,8 @@ class _CommonModel(BaseModel):
|
|
2288
2313
|
time_factor = float(periods_in_a_year_fixed)
|
2289
2314
|
else:
|
2290
2315
|
time_factor = self.periods_in_a_year
|
2291
|
-
vol_label = cast(tuple[str, ValueType], self.tsdf.iloc[:, column].name)[0]
|
2292
|
-
dframe = self.tsdf.iloc[:, column].ffill().pct_change()
|
2316
|
+
vol_label = cast("tuple[str, ValueType]", self.tsdf.iloc[:, column].name)[0]
|
2317
|
+
dframe = Series(self.tsdf.iloc[:, column]).ffill().pct_change()
|
2293
2318
|
volseries = dframe.rolling(
|
2294
2319
|
observations,
|
2295
2320
|
min_periods=observations,
|