openseries 1.6.0__py3-none-any.whl → 1.7.0__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/_common_model.py +233 -292
- openseries/_risk.py +9 -10
- openseries/datefixer.py +31 -36
- openseries/frame.py +113 -149
- openseries/load_plotly.py +8 -8
- openseries/portfoliotools.py +25 -26
- openseries/series.py +44 -64
- openseries/simulation.py +25 -34
- openseries/types.py +17 -24
- {openseries-1.6.0.dist-info → openseries-1.7.0.dist-info}/LICENSE.md +1 -1
- {openseries-1.6.0.dist-info → openseries-1.7.0.dist-info}/METADATA +4 -5
- openseries-1.7.0.dist-info/RECORD +16 -0
- openseries-1.6.0.dist-info/RECORD +0 -16
- {openseries-1.6.0.dist-info → openseries-1.7.0.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
|
|
@@ -483,13 +467,12 @@ class OpenFrame(_CommonModel):
|
|
483
467
|
dlta_degr_freedms: int = 0,
|
484
468
|
first_column: int = 0,
|
485
469
|
second_column: int = 1,
|
486
|
-
months_from_last:
|
487
|
-
from_date:
|
488
|
-
to_date:
|
489
|
-
periods_in_a_year_fixed:
|
470
|
+
months_from_last: int | None = None,
|
471
|
+
from_date: dt.date | None = None,
|
472
|
+
to_date: dt.date | None = None,
|
473
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
490
474
|
) -> DataFrame:
|
491
|
-
"""
|
492
|
-
Exponentially Weighted Moving Average Volatilities and Correlation.
|
475
|
+
"""Exponentially Weighted Moving Average Volatilities and Correlation.
|
493
476
|
|
494
477
|
Exponentially Weighted Moving Average (EWMA) for Volatilities and
|
495
478
|
Correlation. https://www.investopedia.com/articles/07/ewma.asp.
|
@@ -601,8 +584,7 @@ class OpenFrame(_CommonModel):
|
|
601
584
|
|
602
585
|
@property
|
603
586
|
def correl_matrix(self: Self) -> DataFrame:
|
604
|
-
"""
|
605
|
-
Correlation matrix.
|
587
|
+
"""Correlation matrix.
|
606
588
|
|
607
589
|
Returns
|
608
590
|
-------
|
@@ -610,7 +592,7 @@ class OpenFrame(_CommonModel):
|
|
610
592
|
Correlation matrix
|
611
593
|
|
612
594
|
"""
|
613
|
-
corr_matrix = self.tsdf.pct_change(fill_method=
|
595
|
+
corr_matrix = self.tsdf.pct_change(fill_method=None).corr(
|
614
596
|
method="pearson",
|
615
597
|
min_periods=1,
|
616
598
|
)
|
@@ -623,8 +605,7 @@ class OpenFrame(_CommonModel):
|
|
623
605
|
self: Self,
|
624
606
|
new_series: OpenTimeSeries,
|
625
607
|
) -> Self:
|
626
|
-
"""
|
627
|
-
To add an OpenTimeSeries object.
|
608
|
+
"""To add an OpenTimeSeries object.
|
628
609
|
|
629
610
|
Parameters
|
630
611
|
----------
|
@@ -643,8 +624,7 @@ class OpenFrame(_CommonModel):
|
|
643
624
|
return self
|
644
625
|
|
645
626
|
def delete_timeseries(self: Self, lvl_zero_item: str) -> Self:
|
646
|
-
"""
|
647
|
-
To delete an OpenTimeSeries object.
|
627
|
+
"""To delete an OpenTimeSeries object.
|
648
628
|
|
649
629
|
Parameters
|
650
630
|
----------
|
@@ -674,12 +654,11 @@ class OpenFrame(_CommonModel):
|
|
674
654
|
|
675
655
|
def trunc_frame(
|
676
656
|
self: Self,
|
677
|
-
start_cut:
|
678
|
-
end_cut:
|
657
|
+
start_cut: dt.date | None = None,
|
658
|
+
end_cut: dt.date | None = None,
|
679
659
|
where: LiteralTrunc = "both",
|
680
660
|
) -> Self:
|
681
|
-
"""
|
682
|
-
Truncate DataFrame such that all timeseries have the same time span.
|
661
|
+
"""Truncate DataFrame such that all timeseries have the same time span.
|
683
662
|
|
684
663
|
Parameters
|
685
664
|
----------
|
@@ -733,8 +712,7 @@ class OpenFrame(_CommonModel):
|
|
733
712
|
*,
|
734
713
|
base_zero: bool = True,
|
735
714
|
) -> None:
|
736
|
-
"""
|
737
|
-
Calculate cumulative relative return between two series.
|
715
|
+
"""Calculate cumulative relative return between two series.
|
738
716
|
|
739
717
|
Parameters
|
740
718
|
----------
|
@@ -766,14 +744,13 @@ class OpenFrame(_CommonModel):
|
|
766
744
|
|
767
745
|
def tracking_error_func(
|
768
746
|
self: Self,
|
769
|
-
base_column:
|
770
|
-
months_from_last:
|
771
|
-
from_date:
|
772
|
-
to_date:
|
773
|
-
periods_in_a_year_fixed:
|
747
|
+
base_column: tuple[str, ValueType] | int = -1,
|
748
|
+
months_from_last: int | None = None,
|
749
|
+
from_date: dt.date | None = None,
|
750
|
+
to_date: dt.date | None = None,
|
751
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
774
752
|
) -> Series[float]:
|
775
|
-
"""
|
776
|
-
Tracking Error.
|
753
|
+
"""Tracking Error.
|
777
754
|
|
778
755
|
Calculates Tracking Error which is the standard deviation of the
|
779
756
|
difference between the fund and its index returns.
|
@@ -781,7 +758,7 @@ class OpenFrame(_CommonModel):
|
|
781
758
|
|
782
759
|
Parameters
|
783
760
|
----------
|
784
|
-
base_column:
|
761
|
+
base_column: tuple[str, ValueType] | int, default: -1
|
785
762
|
Column of timeseries that is the denominator in the ratio.
|
786
763
|
months_from_last : int, optional
|
787
764
|
number of months offset as positive integer. Overrides use of from_date
|
@@ -846,8 +823,7 @@ class OpenFrame(_CommonModel):
|
|
846
823
|
# noinspection PyTypeChecker
|
847
824
|
relative = 1.0 + longdf - shortdf
|
848
825
|
vol = float(
|
849
|
-
relative.pct_change(fill_method=
|
850
|
-
* sqrt(time_factor),
|
826
|
+
relative.pct_change(fill_method=None).std() * sqrt(time_factor),
|
851
827
|
)
|
852
828
|
terrors.append(vol)
|
853
829
|
|
@@ -860,14 +836,13 @@ class OpenFrame(_CommonModel):
|
|
860
836
|
|
861
837
|
def info_ratio_func(
|
862
838
|
self: Self,
|
863
|
-
base_column:
|
864
|
-
months_from_last:
|
865
|
-
from_date:
|
866
|
-
to_date:
|
867
|
-
periods_in_a_year_fixed:
|
839
|
+
base_column: tuple[str, ValueType] | int = -1,
|
840
|
+
months_from_last: int | None = None,
|
841
|
+
from_date: dt.date | None = None,
|
842
|
+
to_date: dt.date | None = None,
|
843
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
868
844
|
) -> Series[float]:
|
869
|
-
"""
|
870
|
-
Information Ratio.
|
845
|
+
"""Information Ratio.
|
871
846
|
|
872
847
|
The Information Ratio equals ( fund return less index return ) divided
|
873
848
|
by the Tracking Error. And the Tracking Error is the standard deviation of
|
@@ -876,7 +851,7 @@ class OpenFrame(_CommonModel):
|
|
876
851
|
|
877
852
|
Parameters
|
878
853
|
----------
|
879
|
-
base_column:
|
854
|
+
base_column: tuple[str, ValueType] | int, default: -1
|
880
855
|
Column of timeseries that is the denominator in the ratio.
|
881
856
|
months_from_last : int, optional
|
882
857
|
number of months offset as positive integer. Overrides use of from_date
|
@@ -941,12 +916,10 @@ class OpenFrame(_CommonModel):
|
|
941
916
|
# noinspection PyTypeChecker
|
942
917
|
relative = 1.0 + longdf - shortdf
|
943
918
|
ret = float(
|
944
|
-
relative.pct_change(fill_method=
|
945
|
-
* time_factor,
|
919
|
+
relative.pct_change(fill_method=None).mean() * time_factor,
|
946
920
|
)
|
947
921
|
vol = float(
|
948
|
-
relative.pct_change(fill_method=
|
949
|
-
* sqrt(time_factor),
|
922
|
+
relative.pct_change(fill_method=None).std() * sqrt(time_factor),
|
950
923
|
)
|
951
924
|
ratios.append(ret / vol)
|
952
925
|
|
@@ -960,14 +933,13 @@ class OpenFrame(_CommonModel):
|
|
960
933
|
def capture_ratio_func( # noqa: C901
|
961
934
|
self: Self,
|
962
935
|
ratio: LiteralCaptureRatio,
|
963
|
-
base_column:
|
964
|
-
months_from_last:
|
965
|
-
from_date:
|
966
|
-
to_date:
|
967
|
-
periods_in_a_year_fixed:
|
936
|
+
base_column: tuple[str, ValueType] | int = -1,
|
937
|
+
months_from_last: int | None = None,
|
938
|
+
from_date: dt.date | None = None,
|
939
|
+
to_date: dt.date | None = None,
|
940
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
968
941
|
) -> Series[float]:
|
969
|
-
"""
|
970
|
-
Capture Ratio.
|
942
|
+
"""Capture Ratio.
|
971
943
|
|
972
944
|
The Up (Down) Capture Ratio is calculated by dividing the CAGR
|
973
945
|
of the asset during periods that the benchmark returns are positive (negative)
|
@@ -982,7 +954,7 @@ class OpenFrame(_CommonModel):
|
|
982
954
|
----------
|
983
955
|
ratio: LiteralCaptureRatio
|
984
956
|
The ratio to calculate
|
985
|
-
base_column:
|
957
|
+
base_column: tuple[str, ValueType] | int, default: -1
|
986
958
|
Column of timeseries that is the denominator in the ratio.
|
987
959
|
months_from_last : int, optional
|
988
960
|
number of months offset as positive integer. Overrides use of from_date
|
@@ -1047,8 +1019,8 @@ class OpenFrame(_CommonModel):
|
|
1047
1019
|
]
|
1048
1020
|
if ratio == "up":
|
1049
1021
|
uparray = (
|
1050
|
-
longdf.pct_change(fill_method=
|
1051
|
-
shortdf.pct_change(fill_method=
|
1022
|
+
longdf.pct_change(fill_method=None)[
|
1023
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1052
1024
|
> loss_limit
|
1053
1025
|
]
|
1054
1026
|
.add(1)
|
@@ -1056,8 +1028,8 @@ class OpenFrame(_CommonModel):
|
|
1056
1028
|
)
|
1057
1029
|
up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
|
1058
1030
|
upidxarray = (
|
1059
|
-
shortdf.pct_change(fill_method=
|
1060
|
-
shortdf.pct_change(fill_method=
|
1031
|
+
shortdf.pct_change(fill_method=None)[
|
1032
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1061
1033
|
> loss_limit
|
1062
1034
|
]
|
1063
1035
|
.add(1)
|
@@ -1069,8 +1041,8 @@ class OpenFrame(_CommonModel):
|
|
1069
1041
|
ratios.append(up_rtrn / up_idx_return)
|
1070
1042
|
elif ratio == "down":
|
1071
1043
|
downarray = (
|
1072
|
-
longdf.pct_change(fill_method=
|
1073
|
-
shortdf.pct_change(fill_method=
|
1044
|
+
longdf.pct_change(fill_method=None)[
|
1045
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1074
1046
|
< loss_limit
|
1075
1047
|
]
|
1076
1048
|
.add(1)
|
@@ -1080,8 +1052,8 @@ class OpenFrame(_CommonModel):
|
|
1080
1052
|
downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
|
1081
1053
|
)
|
1082
1054
|
downidxarray = (
|
1083
|
-
shortdf.pct_change(fill_method=
|
1084
|
-
shortdf.pct_change(fill_method=
|
1055
|
+
shortdf.pct_change(fill_method=None)[
|
1056
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1085
1057
|
< loss_limit
|
1086
1058
|
]
|
1087
1059
|
.add(1)
|
@@ -1094,8 +1066,8 @@ class OpenFrame(_CommonModel):
|
|
1094
1066
|
ratios.append(down_return / down_idx_return)
|
1095
1067
|
elif ratio == "both":
|
1096
1068
|
uparray = (
|
1097
|
-
longdf.pct_change(fill_method=
|
1098
|
-
shortdf.pct_change(fill_method=
|
1069
|
+
longdf.pct_change(fill_method=None)[
|
1070
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1099
1071
|
> loss_limit
|
1100
1072
|
]
|
1101
1073
|
.add(1)
|
@@ -1103,8 +1075,8 @@ class OpenFrame(_CommonModel):
|
|
1103
1075
|
)
|
1104
1076
|
up_rtrn = uparray.prod() ** (1 / (len(uparray) / time_factor)) - 1
|
1105
1077
|
upidxarray = (
|
1106
|
-
shortdf.pct_change(fill_method=
|
1107
|
-
shortdf.pct_change(fill_method=
|
1078
|
+
shortdf.pct_change(fill_method=None)[
|
1079
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1108
1080
|
> loss_limit
|
1109
1081
|
]
|
1110
1082
|
.add(1)
|
@@ -1114,8 +1086,8 @@ class OpenFrame(_CommonModel):
|
|
1114
1086
|
upidxarray.prod() ** (1 / (len(upidxarray) / time_factor)) - 1
|
1115
1087
|
)
|
1116
1088
|
downarray = (
|
1117
|
-
longdf.pct_change(fill_method=
|
1118
|
-
shortdf.pct_change(fill_method=
|
1089
|
+
longdf.pct_change(fill_method=None)[
|
1090
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1119
1091
|
< loss_limit
|
1120
1092
|
]
|
1121
1093
|
.add(1)
|
@@ -1125,8 +1097,8 @@ class OpenFrame(_CommonModel):
|
|
1125
1097
|
downarray.prod() ** (1 / (len(downarray) / time_factor)) - 1
|
1126
1098
|
)
|
1127
1099
|
downidxarray = (
|
1128
|
-
shortdf.pct_change(fill_method=
|
1129
|
-
shortdf.pct_change(fill_method=
|
1100
|
+
shortdf.pct_change(fill_method=None)[
|
1101
|
+
shortdf.pct_change(fill_method=None).to_numpy()
|
1130
1102
|
< loss_limit
|
1131
1103
|
]
|
1132
1104
|
.add(1)
|
@@ -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(
|