openseries 1.6.0__py3-none-any.whl → 1.7.1__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 +0 -2
- openseries/_common_model.py +261 -324
- openseries/_risk.py +9 -10
- openseries/datefixer.py +42 -63
- openseries/frame.py +125 -161
- openseries/load_plotly.py +8 -8
- openseries/portfoliotools.py +39 -35
- openseries/series.py +65 -93
- openseries/simulation.py +39 -50
- openseries/types.py +17 -24
- {openseries-1.6.0.dist-info → openseries-1.7.1.dist-info}/LICENSE.md +1 -1
- {openseries-1.6.0.dist-info → openseries-1.7.1.dist-info}/METADATA +4 -5
- openseries-1.7.1.dist-info/RECORD +16 -0
- openseries-1.6.0.dist-info/RECORD +0 -16
- {openseries-1.6.0.dist-info → openseries-1.7.1.dist-info}/WHEEL +0 -0
openseries/frame.py
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
"""Defining the OpenFrame class."""
|
2
2
|
|
3
|
-
# mypy: disable-error-code="index,assignment"
|
3
|
+
# mypy: disable-error-code="index,assignment,arg-type"
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
-
import datetime as dt
|
7
6
|
from copy import deepcopy
|
8
7
|
from functools import reduce
|
9
8
|
from logging import warning
|
10
|
-
from typing import
|
9
|
+
from typing import TYPE_CHECKING, cast
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
import datetime as dt # pragma: no cover
|
11
13
|
|
12
14
|
import statsmodels.api as sm # type: ignore[import-untyped,unused-ignore]
|
13
15
|
from numpy import (
|
@@ -40,10 +42,10 @@ from statsmodels.regression.linear_model import ( # type: ignore[import-untyped
|
|
40
42
|
)
|
41
43
|
from typing_extensions import Self
|
42
44
|
|
43
|
-
from
|
44
|
-
from
|
45
|
-
from
|
46
|
-
from
|
45
|
+
from ._common_model import _CommonModel
|
46
|
+
from .datefixer import _do_resample_to_business_period_ends
|
47
|
+
from .series import OpenTimeSeries
|
48
|
+
from .types import (
|
47
49
|
CountriesType,
|
48
50
|
DaysInYearType,
|
49
51
|
LiteralBizDayFreq,
|
@@ -62,11 +64,9 @@ from openseries.types import (
|
|
62
64
|
__all__ = ["OpenFrame"]
|
63
65
|
|
64
66
|
|
65
|
-
# noinspection PyUnresolvedReferences
|
67
|
+
# noinspection PyUnresolvedReferences,PyTypeChecker
|
66
68
|
class OpenFrame(_CommonModel):
|
67
|
-
|
68
|
-
"""
|
69
|
-
OpenFrame objects hold OpenTimeSeries in the list constituents.
|
69
|
+
"""OpenFrame objects hold OpenTimeSeries in the list constituents.
|
70
70
|
|
71
71
|
The intended use is to allow comparisons across these timeseries.
|
72
72
|
|
@@ -86,7 +86,7 @@ class OpenFrame(_CommonModel):
|
|
86
86
|
|
87
87
|
constituents: list[OpenTimeSeries]
|
88
88
|
tsdf: DataFrame = DataFrame(dtype="float64")
|
89
|
-
weights:
|
89
|
+
weights: list[float] | None = None
|
90
90
|
|
91
91
|
# noinspection PyMethodParameters
|
92
92
|
@field_validator("constituents") # type: ignore[misc]
|
@@ -104,10 +104,9 @@ class OpenFrame(_CommonModel):
|
|
104
104
|
def __init__(
|
105
105
|
self: Self,
|
106
106
|
constituents: list[OpenTimeSeries],
|
107
|
-
weights:
|
107
|
+
weights: list[float] | None = None,
|
108
108
|
) -> None:
|
109
|
-
"""
|
110
|
-
OpenFrame objects hold OpenTimeSeries in the list constituents.
|
109
|
+
"""OpenFrame objects hold OpenTimeSeries in the list constituents.
|
111
110
|
|
112
111
|
The intended use is to allow comparisons across these timeseries.
|
113
112
|
|
@@ -144,8 +143,7 @@ class OpenFrame(_CommonModel):
|
|
144
143
|
warning("OpenFrame() was passed an empty list.")
|
145
144
|
|
146
145
|
def from_deepcopy(self: Self) -> Self:
|
147
|
-
"""
|
148
|
-
Create copy of the OpenFrame object.
|
146
|
+
"""Create copy of the OpenFrame object.
|
149
147
|
|
150
148
|
Returns
|
151
149
|
-------
|
@@ -159,8 +157,7 @@ class OpenFrame(_CommonModel):
|
|
159
157
|
self: Self,
|
160
158
|
how: LiteralHowMerge = "outer",
|
161
159
|
) -> Self:
|
162
|
-
"""
|
163
|
-
Merge index of Pandas Dataframes of the constituent OpenTimeSeries.
|
160
|
+
"""Merge index of Pandas Dataframes of the constituent OpenTimeSeries.
|
164
161
|
|
165
162
|
Parameters
|
166
163
|
----------
|
@@ -202,10 +199,9 @@ class OpenFrame(_CommonModel):
|
|
202
199
|
|
203
200
|
def all_properties(
|
204
201
|
self: Self,
|
205
|
-
properties:
|
202
|
+
properties: list[LiteralFrameProps] | None = None,
|
206
203
|
) -> DataFrame:
|
207
|
-
"""
|
208
|
-
Calculate chosen timeseries properties.
|
204
|
+
"""Calculate chosen timeseries properties.
|
209
205
|
|
210
206
|
Parameters
|
211
207
|
----------
|
@@ -229,8 +225,7 @@ class OpenFrame(_CommonModel):
|
|
229
225
|
|
230
226
|
@property
|
231
227
|
def lengths_of_items(self: Self) -> Series[int]:
|
232
|
-
"""
|
233
|
-
Number of observations of all constituents.
|
228
|
+
"""Number of observations of all constituents.
|
234
229
|
|
235
230
|
Returns
|
236
231
|
-------
|
@@ -247,8 +242,7 @@ class OpenFrame(_CommonModel):
|
|
247
242
|
|
248
243
|
@property
|
249
244
|
def item_count(self: Self) -> int:
|
250
|
-
"""
|
251
|
-
Number of constituents.
|
245
|
+
"""Number of constituents.
|
252
246
|
|
253
247
|
Returns
|
254
248
|
-------
|
@@ -260,8 +254,7 @@ class OpenFrame(_CommonModel):
|
|
260
254
|
|
261
255
|
@property
|
262
256
|
def columns_lvl_zero(self: Self) -> list[str]:
|
263
|
-
"""
|
264
|
-
Level 0 values of the MultiIndex columns in the .tsdf DataFrame.
|
257
|
+
"""Level 0 values of the MultiIndex columns in the .tsdf DataFrame.
|
265
258
|
|
266
259
|
Returns
|
267
260
|
-------
|
@@ -273,8 +266,7 @@ class OpenFrame(_CommonModel):
|
|
273
266
|
|
274
267
|
@property
|
275
268
|
def columns_lvl_one(self: Self) -> list[ValueType]:
|
276
|
-
"""
|
277
|
-
Level 1 values of the MultiIndex columns in the .tsdf DataFrame.
|
269
|
+
"""Level 1 values of the MultiIndex columns in the .tsdf DataFrame.
|
278
270
|
|
279
271
|
Returns
|
280
272
|
-------
|
@@ -286,8 +278,7 @@ class OpenFrame(_CommonModel):
|
|
286
278
|
|
287
279
|
@property
|
288
280
|
def first_indices(self: Self) -> Series[dt.date]:
|
289
|
-
"""
|
290
|
-
The first dates in the timeseries of all constituents.
|
281
|
+
"""The first dates in the timeseries of all constituents.
|
291
282
|
|
292
283
|
Returns
|
293
284
|
-------
|
@@ -304,8 +295,7 @@ class OpenFrame(_CommonModel):
|
|
304
295
|
|
305
296
|
@property
|
306
297
|
def last_indices(self: Self) -> Series[dt.date]:
|
307
|
-
"""
|
308
|
-
The last dates in the timeseries of all constituents.
|
298
|
+
"""The last dates in the timeseries of all constituents.
|
309
299
|
|
310
300
|
Returns
|
311
301
|
-------
|
@@ -322,8 +312,7 @@ class OpenFrame(_CommonModel):
|
|
322
312
|
|
323
313
|
@property
|
324
314
|
def span_of_days_all(self: Self) -> Series[int]:
|
325
|
-
"""
|
326
|
-
Number of days from the first date to the last for all items in the frame.
|
315
|
+
"""Number of days from the first date to the last for all items in the frame.
|
327
316
|
|
328
317
|
Returns
|
329
318
|
-------
|
@@ -340,8 +329,7 @@ class OpenFrame(_CommonModel):
|
|
340
329
|
)
|
341
330
|
|
342
331
|
def value_to_ret(self: Self) -> Self:
|
343
|
-
"""
|
344
|
-
Convert series of values into series of returns.
|
332
|
+
"""Convert series of values into series of returns.
|
345
333
|
|
346
334
|
Returns
|
347
335
|
-------
|
@@ -349,7 +337,7 @@ class OpenFrame(_CommonModel):
|
|
349
337
|
The returns of the values in the series
|
350
338
|
|
351
339
|
"""
|
352
|
-
self.tsdf = self.tsdf.pct_change(fill_method=
|
340
|
+
self.tsdf = self.tsdf.pct_change(fill_method=None)
|
353
341
|
self.tsdf.iloc[0] = 0
|
354
342
|
new_labels = [ValueType.RTRN] * self.item_count
|
355
343
|
arrays = [self.tsdf.columns.get_level_values(0), new_labels]
|
@@ -357,8 +345,7 @@ class OpenFrame(_CommonModel):
|
|
357
345
|
return self
|
358
346
|
|
359
347
|
def value_to_diff(self: Self, periods: int = 1) -> Self:
|
360
|
-
"""
|
361
|
-
Convert series of values to series of their period differences.
|
348
|
+
"""Convert series of values to series of their period differences.
|
362
349
|
|
363
350
|
Parameters
|
364
351
|
----------
|
@@ -380,8 +367,7 @@ class OpenFrame(_CommonModel):
|
|
380
367
|
return self
|
381
368
|
|
382
369
|
def to_cumret(self: Self) -> Self:
|
383
|
-
"""
|
384
|
-
Convert series of returns into cumulative series of values.
|
370
|
+
"""Convert series of returns into cumulative series of values.
|
385
371
|
|
386
372
|
Returns
|
387
373
|
-------
|
@@ -404,14 +390,13 @@ class OpenFrame(_CommonModel):
|
|
404
390
|
|
405
391
|
def resample(
|
406
392
|
self: Self,
|
407
|
-
freq:
|
393
|
+
freq: LiteralBizDayFreq | str = "BME",
|
408
394
|
) -> Self:
|
409
|
-
"""
|
410
|
-
Resample the timeseries frequency.
|
395
|
+
"""Resample the timeseries frequency.
|
411
396
|
|
412
397
|
Parameters
|
413
398
|
----------
|
414
|
-
freq:
|
399
|
+
freq: LiteralBizDayFreq | str, default "BME"
|
415
400
|
The date offset string that sets the resampled frequency
|
416
401
|
|
417
402
|
Returns
|
@@ -438,8 +423,7 @@ class OpenFrame(_CommonModel):
|
|
438
423
|
countries: CountriesType = "SE",
|
439
424
|
method: LiteralPandasReindexMethod = "nearest",
|
440
425
|
) -> Self:
|
441
|
-
"""
|
442
|
-
Resamples timeseries frequency to the business calendar month end dates.
|
426
|
+
"""Resamples timeseries frequency to the business calendar month end dates.
|
443
427
|
|
444
428
|
Stubs left in place. Stubs will be aligned to the shortest stub.
|
445
429
|
|
@@ -459,21 +443,18 @@ class OpenFrame(_CommonModel):
|
|
459
443
|
An OpenFrame object
|
460
444
|
|
461
445
|
"""
|
462
|
-
head: Series[float] = self.tsdf.loc[self.first_indices.max()].copy()
|
463
|
-
tail: Series[float] = self.tsdf.loc[self.last_indices.min()].copy()
|
464
|
-
dates = do_resample_to_business_period_ends(
|
465
|
-
data=self.tsdf,
|
466
|
-
head=head,
|
467
|
-
tail=tail,
|
468
|
-
freq=freq,
|
469
|
-
countries=countries,
|
470
|
-
)
|
471
|
-
self.tsdf = self.tsdf.reindex([deyt.date() for deyt in dates], method=method)
|
472
446
|
for xerie in self.constituents:
|
447
|
+
dates = _do_resample_to_business_period_ends(
|
448
|
+
data=xerie.tsdf,
|
449
|
+
freq=freq,
|
450
|
+
countries=countries,
|
451
|
+
)
|
473
452
|
xerie.tsdf = xerie.tsdf.reindex(
|
474
|
-
[deyt.date() for deyt in dates],
|
475
|
-
method=method,
|
453
|
+
[deyt.date() for deyt in dates], method=method,
|
476
454
|
)
|
455
|
+
|
456
|
+
self._set_tsdf()
|
457
|
+
|
477
458
|
return self
|
478
459
|
|
479
460
|
def ewma_risk(
|
@@ -483,13 +464,12 @@ class OpenFrame(_CommonModel):
|
|
483
464
|
dlta_degr_freedms: int = 0,
|
484
465
|
first_column: int = 0,
|
485
466
|
second_column: int = 1,
|
486
|
-
months_from_last:
|
487
|
-
from_date:
|
488
|
-
to_date:
|
489
|
-
periods_in_a_year_fixed:
|
467
|
+
months_from_last: int | None = None,
|
468
|
+
from_date: dt.date | None = None,
|
469
|
+
to_date: dt.date | None = None,
|
470
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
490
471
|
) -> DataFrame:
|
491
|
-
"""
|
492
|
-
Exponentially Weighted Moving Average Volatilities and Correlation.
|
472
|
+
"""Exponentially Weighted Moving Average Volatilities and Correlation.
|
493
473
|
|
494
474
|
Exponentially Weighted Moving Average (EWMA) for Volatilities and
|
495
475
|
Correlation. https://www.investopedia.com/articles/07/ewma.asp.
|
@@ -601,8 +581,7 @@ class OpenFrame(_CommonModel):
|
|
601
581
|
|
602
582
|
@property
|
603
583
|
def correl_matrix(self: Self) -> DataFrame:
|
604
|
-
"""
|
605
|
-
Correlation matrix.
|
584
|
+
"""Correlation matrix.
|
606
585
|
|
607
586
|
Returns
|
608
587
|
-------
|
@@ -610,7 +589,7 @@ class OpenFrame(_CommonModel):
|
|
610
589
|
Correlation matrix
|
611
590
|
|
612
591
|
"""
|
613
|
-
corr_matrix = self.tsdf.pct_change(fill_method=
|
592
|
+
corr_matrix = self.tsdf.pct_change(fill_method=None).corr(
|
614
593
|
method="pearson",
|
615
594
|
min_periods=1,
|
616
595
|
)
|
@@ -623,8 +602,7 @@ class OpenFrame(_CommonModel):
|
|
623
602
|
self: Self,
|
624
603
|
new_series: OpenTimeSeries,
|
625
604
|
) -> Self:
|
626
|
-
"""
|
627
|
-
To add an OpenTimeSeries object.
|
605
|
+
"""To add an OpenTimeSeries object.
|
628
606
|
|
629
607
|
Parameters
|
630
608
|
----------
|
@@ -643,8 +621,7 @@ class OpenFrame(_CommonModel):
|
|
643
621
|
return self
|
644
622
|
|
645
623
|
def delete_timeseries(self: Self, lvl_zero_item: str) -> Self:
|
646
|
-
"""
|
647
|
-
To delete an OpenTimeSeries object.
|
624
|
+
"""To delete an OpenTimeSeries object.
|
648
625
|
|
649
626
|
Parameters
|
650
627
|
----------
|
@@ -674,12 +651,11 @@ class OpenFrame(_CommonModel):
|
|
674
651
|
|
675
652
|
def trunc_frame(
|
676
653
|
self: Self,
|
677
|
-
start_cut:
|
678
|
-
end_cut:
|
654
|
+
start_cut: dt.date | None = None,
|
655
|
+
end_cut: dt.date | None = None,
|
679
656
|
where: LiteralTrunc = "both",
|
680
657
|
) -> Self:
|
681
|
-
"""
|
682
|
-
Truncate DataFrame such that all timeseries have the same time span.
|
658
|
+
"""Truncate DataFrame such that all timeseries have the same time span.
|
683
659
|
|
684
660
|
Parameters
|
685
661
|
----------
|
@@ -733,8 +709,7 @@ class OpenFrame(_CommonModel):
|
|
733
709
|
*,
|
734
710
|
base_zero: bool = True,
|
735
711
|
) -> None:
|
736
|
-
"""
|
737
|
-
Calculate cumulative relative return between two series.
|
712
|
+
"""Calculate cumulative relative return between two series.
|
738
713
|
|
739
714
|
Parameters
|
740
715
|
----------
|
@@ -766,14 +741,13 @@ class OpenFrame(_CommonModel):
|
|
766
741
|
|
767
742
|
def tracking_error_func(
|
768
743
|
self: Self,
|
769
|
-
base_column:
|
770
|
-
months_from_last:
|
771
|
-
from_date:
|
772
|
-
to_date:
|
773
|
-
periods_in_a_year_fixed:
|
744
|
+
base_column: tuple[str, ValueType] | int = -1,
|
745
|
+
months_from_last: int | None = None,
|
746
|
+
from_date: dt.date | None = None,
|
747
|
+
to_date: dt.date | None = None,
|
748
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
774
749
|
) -> Series[float]:
|
775
|
-
"""
|
776
|
-
Tracking Error.
|
750
|
+
"""Tracking Error.
|
777
751
|
|
778
752
|
Calculates Tracking Error which is the standard deviation of the
|
779
753
|
difference between the fund and its index returns.
|
@@ -781,7 +755,7 @@ class OpenFrame(_CommonModel):
|
|
781
755
|
|
782
756
|
Parameters
|
783
757
|
----------
|
784
|
-
base_column:
|
758
|
+
base_column: tuple[str, ValueType] | int, default: -1
|
785
759
|
Column of timeseries that is the denominator in the ratio.
|
786
760
|
months_from_last : int, optional
|
787
761
|
number of months offset as positive integer. Overrides use of from_date
|
@@ -846,8 +820,7 @@ class OpenFrame(_CommonModel):
|
|
846
820
|
# noinspection PyTypeChecker
|
847
821
|
relative = 1.0 + longdf - shortdf
|
848
822
|
vol = float(
|
849
|
-
relative.pct_change(fill_method=
|
850
|
-
* sqrt(time_factor),
|
823
|
+
relative.pct_change(fill_method=None).std() * sqrt(time_factor),
|
851
824
|
)
|
852
825
|
terrors.append(vol)
|
853
826
|
|
@@ -860,14 +833,13 @@ class OpenFrame(_CommonModel):
|
|
860
833
|
|
861
834
|
def info_ratio_func(
|
862
835
|
self: Self,
|
863
|
-
base_column:
|
864
|
-
months_from_last:
|
865
|
-
from_date:
|
866
|
-
to_date:
|
867
|
-
periods_in_a_year_fixed:
|
836
|
+
base_column: tuple[str, ValueType] | int = -1,
|
837
|
+
months_from_last: int | None = None,
|
838
|
+
from_date: dt.date | None = None,
|
839
|
+
to_date: dt.date | None = None,
|
840
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
868
841
|
) -> Series[float]:
|
869
|
-
"""
|
870
|
-
Information Ratio.
|
842
|
+
"""Information Ratio.
|
871
843
|
|
872
844
|
The Information Ratio equals ( fund return less index return ) divided
|
873
845
|
by the Tracking Error. And the Tracking Error is the standard deviation of
|
@@ -876,7 +848,7 @@ class OpenFrame(_CommonModel):
|
|
876
848
|
|
877
849
|
Parameters
|
878
850
|
----------
|
879
|
-
base_column:
|
851
|
+
base_column: tuple[str, ValueType] | int, default: -1
|
880
852
|
Column of timeseries that is the denominator in the ratio.
|
881
853
|
months_from_last : int, optional
|
882
854
|
number of months offset as positive integer. Overrides use of from_date
|
@@ -941,12 +913,10 @@ class OpenFrame(_CommonModel):
|
|
941
913
|
# noinspection PyTypeChecker
|
942
914
|
relative = 1.0 + longdf - shortdf
|
943
915
|
ret = float(
|
944
|
-
relative.pct_change(fill_method=
|
945
|
-
* time_factor,
|
916
|
+
relative.pct_change(fill_method=None).mean() * time_factor,
|
946
917
|
)
|
947
918
|
vol = float(
|
948
|
-
relative.pct_change(fill_method=
|
949
|
-
* sqrt(time_factor),
|
919
|
+
relative.pct_change(fill_method=None).std() * sqrt(time_factor),
|
950
920
|
)
|
951
921
|
ratios.append(ret / vol)
|
952
922
|
|
@@ -960,14 +930,13 @@ class OpenFrame(_CommonModel):
|
|
960
930
|
def capture_ratio_func( # noqa: C901
|
961
931
|
self: Self,
|
962
932
|
ratio: LiteralCaptureRatio,
|
963
|
-
base_column:
|
964
|
-
months_from_last:
|
965
|
-
from_date:
|
966
|
-
to_date:
|
967
|
-
periods_in_a_year_fixed:
|
933
|
+
base_column: tuple[str, ValueType] | int = -1,
|
934
|
+
months_from_last: int | None = None,
|
935
|
+
from_date: dt.date | None = None,
|
936
|
+
to_date: dt.date | None = None,
|
937
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
968
938
|
) -> Series[float]:
|
969
|
-
"""
|
970
|
-
Capture Ratio.
|
939
|
+
"""Capture Ratio.
|
971
940
|
|
972
941
|
The Up (Down) Capture Ratio is calculated by dividing the CAGR
|
973
942
|
of the asset during periods that the benchmark returns are positive (negative)
|
@@ -982,7 +951,7 @@ class OpenFrame(_CommonModel):
|
|
982
951
|
----------
|
983
952
|
ratio: LiteralCaptureRatio
|
984
953
|
The ratio to calculate
|
985
|
-
base_column:
|
954
|
+
base_column: tuple[str, ValueType] | int, default: -1
|
986
955
|
Column of timeseries that is the denominator in the ratio.
|
987
956
|
months_from_last : int, optional
|
988
957
|
number of months offset as positive integer. Overrides use of from_date
|
@@ -1047,8 +1016,8 @@ class OpenFrame(_CommonModel):
|
|
1047
1016
|
]
|
1048
1017
|
if ratio == "up":
|
1049
1018
|
uparray = (
|
1050
|
-
longdf.pct_change(fill_method=
|
1051
|
-
shortdf.pct_change(fill_method=
|
1019
|
+
longdf.pct_change(fill_method=None)[
|
1020
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1052
1021
|
> loss_limit
|
1053
1022
|
]
|
1054
1023
|
.add(1)
|
@@ -1056,8 +1025,8 @@ class OpenFrame(_CommonModel):
|
|
1056
1025
|
)
|
1057
1026
|
up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
|
1058
1027
|
upidxarray = (
|
1059
|
-
shortdf.pct_change(fill_method=
|
1060
|
-
shortdf.pct_change(fill_method=
|
1028
|
+
shortdf.pct_change(fill_method=None)[
|
1029
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1061
1030
|
> loss_limit
|
1062
1031
|
]
|
1063
1032
|
.add(1)
|
@@ -1069,8 +1038,8 @@ class OpenFrame(_CommonModel):
|
|
1069
1038
|
ratios.append(up_rtrn / up_idx_return)
|
1070
1039
|
elif ratio == "down":
|
1071
1040
|
downarray = (
|
1072
|
-
longdf.pct_change(fill_method=
|
1073
|
-
shortdf.pct_change(fill_method=
|
1041
|
+
longdf.pct_change(fill_method=None)[
|
1042
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1074
1043
|
< loss_limit
|
1075
1044
|
]
|
1076
1045
|
.add(1)
|
@@ -1080,8 +1049,8 @@ class OpenFrame(_CommonModel):
|
|
1080
1049
|
downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
|
1081
1050
|
)
|
1082
1051
|
downidxarray = (
|
1083
|
-
shortdf.pct_change(fill_method=
|
1084
|
-
shortdf.pct_change(fill_method=
|
1052
|
+
shortdf.pct_change(fill_method=None)[
|
1053
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1085
1054
|
< loss_limit
|
1086
1055
|
]
|
1087
1056
|
.add(1)
|
@@ -1094,8 +1063,8 @@ class OpenFrame(_CommonModel):
|
|
1094
1063
|
ratios.append(down_return / down_idx_return)
|
1095
1064
|
elif ratio == "both":
|
1096
1065
|
uparray = (
|
1097
|
-
longdf.pct_change(fill_method=
|
1098
|
-
shortdf.pct_change(fill_method=
|
1066
|
+
longdf.pct_change(fill_method=None)[
|
1067
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1099
1068
|
> loss_limit
|
1100
1069
|
]
|
1101
1070
|
.add(1)
|
@@ -1103,8 +1072,8 @@ class OpenFrame(_CommonModel):
|
|
1103
1072
|
)
|
1104
1073
|
up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
|
1105
1074
|
upidxarray = (
|
1106
|
-
shortdf.pct_change(fill_method=
|
1107
|
-
shortdf.pct_change(fill_method=
|
1075
|
+
shortdf.pct_change(fill_method=None)[
|
1076
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1108
1077
|
> loss_limit
|
1109
1078
|
]
|
1110
1079
|
.add(1)
|
@@ -1114,8 +1083,8 @@ class OpenFrame(_CommonModel):
|
|
1114
1083
|
upidxarray.prod() ** (1 / (len(upidxarray) / time_factor)) - 1
|
1115
1084
|
)
|
1116
1085
|
downarray = (
|
1117
|
-
longdf.pct_change(fill_method=
|
1118
|
-
shortdf.pct_change(fill_method=
|
1086
|
+
longdf.pct_change(fill_method=None)[
|
1087
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1119
1088
|
< loss_limit
|
1120
1089
|
]
|
1121
1090
|
.add(1)
|
@@ -1125,8 +1094,8 @@ class OpenFrame(_CommonModel):
|
|
1125
1094
|
downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
|
1126
1095
|
)
|
1127
1096
|
downidxarray = (
|
1128
|
-
shortdf.pct_change(fill_method=
|
1129
|
-
shortdf.pct_change(fill_method=
|
1097
|
+
shortdf.pct_change(fill_method=None)[
|
1098
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1130
1099
|
< loss_limit
|
1131
1100
|
]
|
1132
1101
|
.add(1)
|
@@ -1139,6 +1108,9 @@ class OpenFrame(_CommonModel):
|
|
1139
1108
|
ratios.append(
|
1140
1109
|
(up_rtrn / up_idx_return) / (down_return / down_idx_return),
|
1141
1110
|
)
|
1111
|
+
else:
|
1112
|
+
msg = "ratio must be one of 'up', 'down' or 'both'."
|
1113
|
+
raise ValueError(msg)
|
1142
1114
|
|
1143
1115
|
if ratio == "up":
|
1144
1116
|
resultname = f"Up Capture Ratios vs {short_label}"
|
@@ -1156,21 +1128,20 @@ class OpenFrame(_CommonModel):
|
|
1156
1128
|
|
1157
1129
|
def beta(
|
1158
1130
|
self: Self,
|
1159
|
-
asset:
|
1160
|
-
market:
|
1131
|
+
asset: tuple[str, ValueType] | int,
|
1132
|
+
market: tuple[str, ValueType] | int,
|
1161
1133
|
dlta_degr_freedms: int = 1,
|
1162
1134
|
) -> float:
|
1163
|
-
"""
|
1164
|
-
Market Beta.
|
1135
|
+
"""Market Beta.
|
1165
1136
|
|
1166
1137
|
Calculates Beta as Co-variance of asset & market divided by Variance
|
1167
1138
|
of the market. https://www.investopedia.com/terms/b/beta.asp.
|
1168
1139
|
|
1169
1140
|
Parameters
|
1170
1141
|
----------
|
1171
|
-
asset:
|
1142
|
+
asset: tuple[str, ValueType] | int
|
1172
1143
|
The column of the asset
|
1173
|
-
market:
|
1144
|
+
market: tuple[str, ValueType] | int
|
1174
1145
|
The column of the market against which Beta is measured
|
1175
1146
|
dlta_degr_freedms: int, default: 1
|
1176
1147
|
Variance bias factor taking the value 0 or 1.
|
@@ -1238,15 +1209,14 @@ class OpenFrame(_CommonModel):
|
|
1238
1209
|
|
1239
1210
|
def ord_least_squares_fit(
|
1240
1211
|
self: Self,
|
1241
|
-
y_column:
|
1242
|
-
x_column:
|
1212
|
+
y_column: tuple[str, ValueType] | int,
|
1213
|
+
x_column: tuple[str, ValueType] | int,
|
1243
1214
|
method: LiteralOlsFitMethod = "pinv",
|
1244
1215
|
cov_type: LiteralOlsFitCovType = "nonrobust",
|
1245
1216
|
*,
|
1246
1217
|
fitted_series: bool = True,
|
1247
1218
|
) -> OLSResults:
|
1248
|
-
"""
|
1249
|
-
Ordinary Least Squares fit.
|
1219
|
+
"""Ordinary Least Squares fit.
|
1250
1220
|
|
1251
1221
|
Performs a linear regression and adds a new column with a fitted line
|
1252
1222
|
using Ordinary Least Squares fit
|
@@ -1254,9 +1224,9 @@ class OpenFrame(_CommonModel):
|
|
1254
1224
|
|
1255
1225
|
Parameters
|
1256
1226
|
----------
|
1257
|
-
y_column:
|
1227
|
+
y_column: tuple[str, ValueType] | int
|
1258
1228
|
The column level values of the dependent variable y
|
1259
|
-
x_column:
|
1229
|
+
x_column: tuple[str, ValueType] | int
|
1260
1230
|
The column level values of the exogenous variable x
|
1261
1231
|
method: LiteralOlsFitMethod, default: pinv
|
1262
1232
|
Method to solve least squares problem
|
@@ -1309,13 +1279,12 @@ class OpenFrame(_CommonModel):
|
|
1309
1279
|
|
1310
1280
|
def jensen_alpha( # noqa: C901
|
1311
1281
|
self: Self,
|
1312
|
-
asset:
|
1313
|
-
market:
|
1282
|
+
asset: tuple[str, ValueType] | int,
|
1283
|
+
market: tuple[str, ValueType] | int,
|
1314
1284
|
riskfree_rate: float = 0.0,
|
1315
1285
|
dlta_degr_freedms: int = 1,
|
1316
1286
|
) -> float:
|
1317
|
-
"""
|
1318
|
-
Jensen's alpha.
|
1287
|
+
"""Jensen's alpha.
|
1319
1288
|
|
1320
1289
|
The Jensen's measure, or Jensen's alpha, is a risk-adjusted performance
|
1321
1290
|
measure that represents the average return on a portfolio or investment,
|
@@ -1326,9 +1295,9 @@ class OpenFrame(_CommonModel):
|
|
1326
1295
|
|
1327
1296
|
Parameters
|
1328
1297
|
----------
|
1329
|
-
asset:
|
1298
|
+
asset: tuple[str, ValueType] | int
|
1330
1299
|
The column of the asset
|
1331
|
-
market:
|
1300
|
+
market: tuple[str, ValueType] | int
|
1332
1301
|
The column of the market against which Jensen's alpha is measured
|
1333
1302
|
riskfree_rate : float, default: 0.0
|
1334
1303
|
The return of the zero volatility riskfree asset
|
@@ -1448,10 +1417,9 @@ class OpenFrame(_CommonModel):
|
|
1448
1417
|
def make_portfolio(
|
1449
1418
|
self: Self,
|
1450
1419
|
name: str,
|
1451
|
-
weight_strat:
|
1420
|
+
weight_strat: LiteralPortfolioWeightings | None = None,
|
1452
1421
|
) -> DataFrame:
|
1453
|
-
"""
|
1454
|
-
Calculate a basket timeseries based on the supplied weights.
|
1422
|
+
"""Calculate a basket timeseries based on the supplied weights.
|
1455
1423
|
|
1456
1424
|
Parameters
|
1457
1425
|
----------
|
@@ -1479,7 +1447,7 @@ class OpenFrame(_CommonModel):
|
|
1479
1447
|
x == ValueType.RTRN
|
1480
1448
|
for x in self.tsdf.columns.get_level_values(1).to_numpy()
|
1481
1449
|
):
|
1482
|
-
dframe = dframe.pct_change(fill_method=
|
1450
|
+
dframe = dframe.pct_change(fill_method=None)
|
1483
1451
|
dframe.iloc[0] = 0
|
1484
1452
|
if weight_strat:
|
1485
1453
|
if weight_strat == "eq_weights":
|
@@ -1503,10 +1471,9 @@ class OpenFrame(_CommonModel):
|
|
1503
1471
|
long_column: int = 0,
|
1504
1472
|
short_column: int = 1,
|
1505
1473
|
observations: int = 21,
|
1506
|
-
periods_in_a_year_fixed:
|
1474
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
1507
1475
|
) -> DataFrame:
|
1508
|
-
"""
|
1509
|
-
Calculate rolling Information Ratio.
|
1476
|
+
"""Calculate rolling Information Ratio.
|
1510
1477
|
|
1511
1478
|
The Information Ratio equals ( fund return less index return ) divided by
|
1512
1479
|
the Tracking Error. And the Tracking Error is the standard deviation of the
|
@@ -1548,13 +1515,13 @@ class OpenFrame(_CommonModel):
|
|
1548
1515
|
)
|
1549
1516
|
|
1550
1517
|
retseries = (
|
1551
|
-
relative.pct_change(fill_method=
|
1518
|
+
relative.pct_change(fill_method=None)
|
1552
1519
|
.rolling(observations, min_periods=observations)
|
1553
1520
|
.sum()
|
1554
1521
|
)
|
1555
1522
|
retdf = retseries.dropna().to_frame()
|
1556
1523
|
|
1557
|
-
voldf = relative.pct_change(fill_method=
|
1524
|
+
voldf = relative.pct_change(fill_method=None).rolling(
|
1558
1525
|
observations,
|
1559
1526
|
min_periods=observations,
|
1560
1527
|
).std() * sqrt(time_factor)
|
@@ -1572,8 +1539,7 @@ class OpenFrame(_CommonModel):
|
|
1572
1539
|
observations: int = 21,
|
1573
1540
|
dlta_degr_freedms: int = 1,
|
1574
1541
|
) -> DataFrame:
|
1575
|
-
"""
|
1576
|
-
Calculate rolling Market Beta.
|
1542
|
+
"""Calculate rolling Market Beta.
|
1577
1543
|
|
1578
1544
|
Calculates Beta as Co-variance of asset & market divided by Variance
|
1579
1545
|
of the market. https://www.investopedia.com/terms/b/beta.asp.
|
@@ -1599,8 +1565,7 @@ class OpenFrame(_CommonModel):
|
|
1599
1565
|
asset_label = cast(tuple[str, str], self.tsdf.iloc[:, asset_column].name)[0]
|
1600
1566
|
beta_label = f"{asset_label} / {market_label}"
|
1601
1567
|
|
1602
|
-
rolling
|
1603
|
-
rolling = rolling.pct_change(fill_method=cast(str, None)).rolling(
|
1568
|
+
rolling = self.tsdf.pct_change(fill_method=None).rolling(
|
1604
1569
|
observations,
|
1605
1570
|
min_periods=observations,
|
1606
1571
|
)
|
@@ -1630,8 +1595,7 @@ class OpenFrame(_CommonModel):
|
|
1630
1595
|
second_column: int = 1,
|
1631
1596
|
observations: int = 21,
|
1632
1597
|
) -> DataFrame:
|
1633
|
-
"""
|
1634
|
-
Calculate rolling Correlation.
|
1598
|
+
"""Calculate rolling Correlation.
|
1635
1599
|
|
1636
1600
|
Calculates correlation between two series. The period with
|
1637
1601
|
at least the given number of observations is the first period calculated.
|
@@ -1658,11 +1622,11 @@ class OpenFrame(_CommonModel):
|
|
1658
1622
|
)
|
1659
1623
|
first_series = (
|
1660
1624
|
self.tsdf.iloc[:, first_column]
|
1661
|
-
.pct_change(fill_method=
|
1625
|
+
.pct_change(fill_method=None)[1:]
|
1662
1626
|
.rolling(observations, min_periods=observations)
|
1663
1627
|
)
|
1664
1628
|
second_series = self.tsdf.iloc[:, second_column].pct_change(
|
1665
|
-
fill_method=
|
1629
|
+
fill_method=None,
|
1666
1630
|
)[1:]
|
1667
1631
|
corrdf = first_series.corr(other=second_series).dropna().to_frame()
|
1668
1632
|
corrdf.columns = MultiIndex.from_arrays(
|