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/load_plotly.py
CHANGED
@@ -5,18 +5,19 @@ from __future__ import annotations
|
|
5
5
|
from json import load
|
6
6
|
from logging import warning
|
7
7
|
from pathlib import Path
|
8
|
+
from typing import TYPE_CHECKING
|
8
9
|
|
9
10
|
import requests
|
10
11
|
from requests.exceptions import ConnectionError
|
11
12
|
|
12
|
-
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from .types import CaptorLogoType, PlotlyLayoutType # pragma: no cover
|
13
15
|
|
14
16
|
__all__ = ["load_plotly_dict"]
|
15
17
|
|
16
18
|
|
17
19
|
def _check_remote_file_existence(url: str) -> bool:
|
18
|
-
"""
|
19
|
-
Check if remote file exists.
|
20
|
+
"""Check if remote file exists.
|
20
21
|
|
21
22
|
Parameters
|
22
23
|
----------
|
@@ -44,8 +45,7 @@ def load_plotly_dict(
|
|
44
45
|
*,
|
45
46
|
responsive: bool = True,
|
46
47
|
) -> tuple[PlotlyLayoutType, CaptorLogoType]:
|
47
|
-
"""
|
48
|
-
Load Plotly defaults.
|
48
|
+
"""Load Plotly defaults.
|
49
49
|
|
50
50
|
Parameters
|
51
51
|
----------
|
@@ -58,13 +58,13 @@ def load_plotly_dict(
|
|
58
58
|
A dictionary with the Plotly config and layout template
|
59
59
|
|
60
60
|
"""
|
61
|
-
project_root = Path(__file__).
|
61
|
+
project_root = Path(__file__).parent.parent
|
62
62
|
layoutfile = project_root.joinpath("openseries").joinpath("plotly_layouts.json")
|
63
63
|
logofile = project_root.joinpath("openseries").joinpath("plotly_captor_logo.json")
|
64
64
|
|
65
|
-
with
|
65
|
+
with layoutfile.open(mode="r", encoding="utf-8") as layout_file:
|
66
66
|
fig = load(layout_file)
|
67
|
-
with
|
67
|
+
with logofile.open(mode="r", encoding="utf-8") as logo_file:
|
68
68
|
logo = load(logo_file)
|
69
69
|
|
70
70
|
if _check_remote_file_existence(url=logo["source"]) is False:
|
openseries/portfoliotools.py
CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
5
5
|
|
6
6
|
from inspect import stack
|
7
7
|
from pathlib import Path
|
8
|
-
from typing import
|
8
|
+
from typing import TYPE_CHECKING, Callable, cast
|
9
9
|
|
10
10
|
from numpy import (
|
11
11
|
append,
|
@@ -30,15 +30,14 @@ from pandas import (
|
|
30
30
|
from plotly.graph_objs import Figure # type: ignore[import-untyped,unused-ignore]
|
31
31
|
from plotly.io import to_html # type: ignore[import-untyped,unused-ignore]
|
32
32
|
from plotly.offline import plot # type: ignore[import-untyped,unused-ignore]
|
33
|
-
from pydantic import DirectoryPath
|
34
33
|
from scipy.optimize import minimize # type: ignore[import-untyped,unused-ignore]
|
35
34
|
|
35
|
+
from .load_plotly import load_plotly_dict
|
36
|
+
from .series import OpenTimeSeries
|
37
|
+
|
36
38
|
# noinspection PyProtectedMember
|
37
|
-
from
|
38
|
-
from
|
39
|
-
from openseries.series import OpenTimeSeries
|
40
|
-
from openseries.simulation import _random_generator
|
41
|
-
from openseries.types import (
|
39
|
+
from .simulation import _random_generator
|
40
|
+
from .types import (
|
42
41
|
LiteralLinePlotMode,
|
43
42
|
LiteralMinimizeMethods,
|
44
43
|
LiteralPlotlyJSlib,
|
@@ -46,6 +45,11 @@ from openseries.types import (
|
|
46
45
|
ValueType,
|
47
46
|
)
|
48
47
|
|
48
|
+
if TYPE_CHECKING: # pragma: no cover
|
49
|
+
from pydantic import DirectoryPath
|
50
|
+
|
51
|
+
from .frame import OpenFrame
|
52
|
+
|
49
53
|
__all__ = [
|
50
54
|
"constrain_optimized_portfolios",
|
51
55
|
"efficient_frontier",
|
@@ -60,8 +64,7 @@ def simulate_portfolios(
|
|
60
64
|
num_ports: int,
|
61
65
|
seed: int,
|
62
66
|
) -> DataFrame:
|
63
|
-
"""
|
64
|
-
Generate random weights for simulated portfolios.
|
67
|
+
"""Generate random weights for simulated portfolios.
|
65
68
|
|
66
69
|
Parameters
|
67
70
|
----------
|
@@ -130,14 +133,13 @@ def efficient_frontier( # noqa: C901
|
|
130
133
|
eframe: OpenFrame,
|
131
134
|
num_ports: int = 5000,
|
132
135
|
seed: int = 71,
|
133
|
-
bounds:
|
136
|
+
bounds: tuple[tuple[float]] | None = None,
|
134
137
|
frontier_points: int = 200,
|
135
138
|
minimize_method: LiteralMinimizeMethods = "SLSQP",
|
136
139
|
*,
|
137
140
|
tweak: bool = True,
|
138
141
|
) -> tuple[DataFrame, DataFrame, NDArray[float64]]:
|
139
|
-
"""
|
140
|
-
Identify an efficient frontier.
|
142
|
+
"""Identify an efficient frontier.
|
141
143
|
|
142
144
|
Parameters
|
143
145
|
----------
|
@@ -258,7 +260,7 @@ def efficient_frontier( # noqa: C901
|
|
258
260
|
|
259
261
|
for possible_return in frontier_y:
|
260
262
|
cons = cast(
|
261
|
-
dict[str,
|
263
|
+
dict[str, str | Callable[[float, NDArray[float64]], float64]],
|
262
264
|
(
|
263
265
|
{"type": "eq", "fun": _check_sum},
|
264
266
|
{
|
@@ -319,11 +321,10 @@ def constrain_optimized_portfolios(
|
|
319
321
|
portfolioname: str = "Current Portfolio",
|
320
322
|
simulations: int = 10000,
|
321
323
|
curve_points: int = 200,
|
322
|
-
bounds:
|
324
|
+
bounds: tuple[tuple[float]] | None = None,
|
323
325
|
minimize_method: LiteralMinimizeMethods = "SLSQP",
|
324
326
|
) -> tuple[OpenFrame, OpenTimeSeries, OpenFrame, OpenTimeSeries]:
|
325
|
-
"""
|
326
|
-
Constrain optimized portfolios to those that improve on the current one.
|
327
|
+
"""Constrain optimized portfolios to those that improve on the current one.
|
327
328
|
|
328
329
|
Parameters
|
329
330
|
----------
|
@@ -391,8 +392,7 @@ def prepare_plot_data(
|
|
391
392
|
current: OpenTimeSeries,
|
392
393
|
optimized: NDArray[float64],
|
393
394
|
) -> DataFrame:
|
394
|
-
"""
|
395
|
-
Prepare date to be used as point_frame in the sharpeplot function.
|
395
|
+
"""Prepare date to be used as point_frame in the sharpeplot function.
|
396
396
|
|
397
397
|
Parameters
|
398
398
|
----------
|
@@ -443,13 +443,13 @@ def prepare_plot_data(
|
|
443
443
|
|
444
444
|
|
445
445
|
def sharpeplot( # noqa: C901
|
446
|
-
sim_frame:
|
447
|
-
line_frame:
|
448
|
-
point_frame:
|
446
|
+
sim_frame: DataFrame | None = None,
|
447
|
+
line_frame: DataFrame | None = None,
|
448
|
+
point_frame: DataFrame | None = None,
|
449
449
|
point_frame_mode: LiteralLinePlotMode = "markers",
|
450
|
-
filename:
|
451
|
-
directory:
|
452
|
-
titletext:
|
450
|
+
filename: str | None = None,
|
451
|
+
directory: DirectoryPath | None = None,
|
452
|
+
titletext: str | None = None,
|
453
453
|
output_type: LiteralPlotlyOutput = "file",
|
454
454
|
include_plotlyjs: LiteralPlotlyJSlib = "cdn",
|
455
455
|
*,
|
@@ -457,8 +457,7 @@ def sharpeplot( # noqa: C901
|
|
457
457
|
add_logo: bool = True,
|
458
458
|
auto_open: bool = True,
|
459
459
|
) -> tuple[Figure, str]:
|
460
|
-
"""
|
461
|
-
Create scatter plot coloured by Sharpe Ratio.
|
460
|
+
"""Create scatter plot coloured by Sharpe Ratio.
|
462
461
|
|
463
462
|
Parameters
|
464
463
|
----------
|
openseries/series.py
CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
5
5
|
import datetime as dt
|
6
6
|
from copy import deepcopy
|
7
7
|
from logging import warning
|
8
|
-
from typing import Any,
|
8
|
+
from typing import Any, TypeVar, cast
|
9
9
|
|
10
10
|
from numpy import (
|
11
11
|
append,
|
@@ -28,9 +28,9 @@ from pandas import (
|
|
28
28
|
from pydantic import model_validator
|
29
29
|
from typing_extensions import Self
|
30
30
|
|
31
|
-
from
|
32
|
-
from
|
33
|
-
from
|
31
|
+
from ._common_model import _CommonModel
|
32
|
+
from .datefixer import date_fix, do_resample_to_business_period_ends
|
33
|
+
from .types import (
|
34
34
|
Countries,
|
35
35
|
CountriesType,
|
36
36
|
Currency,
|
@@ -53,9 +53,7 @@ TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
|
|
53
53
|
|
54
54
|
# noinspection PyUnresolvedReferences
|
55
55
|
class OpenTimeSeries(_CommonModel):
|
56
|
-
|
57
|
-
"""
|
58
|
-
OpenTimeSeries objects are at the core of the openseries package.
|
56
|
+
"""OpenTimeSeries objects are at the core of the openseries package.
|
59
57
|
|
60
58
|
The intended use is to allow analyses of financial timeseries.
|
61
59
|
It is only intended for daily or less frequent data samples.
|
@@ -104,8 +102,8 @@ class OpenTimeSeries(_CommonModel):
|
|
104
102
|
currency: CurrencyStringType
|
105
103
|
domestic: CurrencyStringType = "SEK"
|
106
104
|
countries: CountriesType = "SE"
|
107
|
-
isin:
|
108
|
-
label:
|
105
|
+
isin: str | None = None
|
106
|
+
label: str | None = None
|
109
107
|
|
110
108
|
@model_validator(mode="after") # type: ignore[misc,unused-ignore]
|
111
109
|
def dates_and_values_validate(self: Self) -> Self:
|
@@ -134,8 +132,7 @@ class OpenTimeSeries(_CommonModel):
|
|
134
132
|
domestic_ccy: CurrencyStringType = "SEK",
|
135
133
|
countries: CountriesType = "SE",
|
136
134
|
) -> None:
|
137
|
-
"""
|
138
|
-
Set the domestic currency and calendar of the user.
|
135
|
+
"""Set the domestic currency and calendar of the user.
|
139
136
|
|
140
137
|
Parameters
|
141
138
|
----------
|
@@ -160,13 +157,12 @@ class OpenTimeSeries(_CommonModel):
|
|
160
157
|
valuetype: ValueType = ValueType.PRICE,
|
161
158
|
timeseries_id: DatabaseIdStringType = "",
|
162
159
|
instrument_id: DatabaseIdStringType = "",
|
163
|
-
isin:
|
160
|
+
isin: str | None = None,
|
164
161
|
baseccy: CurrencyStringType = "SEK",
|
165
162
|
*,
|
166
163
|
local_ccy: bool = True,
|
167
164
|
) -> OpenTimeSeries:
|
168
|
-
"""
|
169
|
-
Create series from a Pandas DataFrame or Series.
|
165
|
+
"""Create series from a Pandas DataFrame or Series.
|
170
166
|
|
171
167
|
Parameters
|
172
168
|
----------
|
@@ -217,19 +213,18 @@ class OpenTimeSeries(_CommonModel):
|
|
217
213
|
@classmethod
|
218
214
|
def from_df(
|
219
215
|
cls: type[OpenTimeSeries],
|
220
|
-
dframe:
|
216
|
+
dframe: DataFrame | Series[float],
|
221
217
|
column_nmbr: int = 0,
|
222
218
|
valuetype: ValueType = ValueType.PRICE,
|
223
219
|
baseccy: CurrencyStringType = "SEK",
|
224
220
|
*,
|
225
221
|
local_ccy: bool = True,
|
226
222
|
) -> OpenTimeSeries:
|
227
|
-
"""
|
228
|
-
Create series from a Pandas DataFrame or Series.
|
223
|
+
"""Create series from a Pandas DataFrame or Series.
|
229
224
|
|
230
225
|
Parameters
|
231
226
|
----------
|
232
|
-
dframe:
|
227
|
+
dframe: DataFrame | Series[float]
|
233
228
|
Pandas DataFrame or Series
|
234
229
|
column_nmbr : int, default: 0
|
235
230
|
Using iloc[:, column_nmbr] to pick column
|
@@ -299,17 +294,16 @@ class OpenTimeSeries(_CommonModel):
|
|
299
294
|
def from_fixed_rate(
|
300
295
|
cls: type[OpenTimeSeries],
|
301
296
|
rate: float,
|
302
|
-
d_range:
|
303
|
-
days:
|
304
|
-
end_dt:
|
297
|
+
d_range: DatetimeIndex | None = None,
|
298
|
+
days: int | None = None,
|
299
|
+
end_dt: dt.date | None = None,
|
305
300
|
label: str = "Series",
|
306
301
|
valuetype: ValueType = ValueType.PRICE,
|
307
302
|
baseccy: CurrencyStringType = "SEK",
|
308
303
|
*,
|
309
304
|
local_ccy: bool = True,
|
310
305
|
) -> OpenTimeSeries:
|
311
|
-
"""
|
312
|
-
Create series from values accruing with a given fixed rate return.
|
306
|
+
"""Create series from values accruing with a given fixed rate return.
|
313
307
|
|
314
308
|
Providing a date_range of type Pandas DatetimeIndex takes priority over
|
315
309
|
providing a combination of days and an end date.
|
@@ -380,8 +374,7 @@ class OpenTimeSeries(_CommonModel):
|
|
380
374
|
)
|
381
375
|
|
382
376
|
def from_deepcopy(self: Self) -> Self:
|
383
|
-
"""
|
384
|
-
Create copy of OpenTimeSeries object.
|
377
|
+
"""Create copy of OpenTimeSeries object.
|
385
378
|
|
386
379
|
Returns
|
387
380
|
-------
|
@@ -392,8 +385,7 @@ class OpenTimeSeries(_CommonModel):
|
|
392
385
|
return deepcopy(self)
|
393
386
|
|
394
387
|
def pandas_df(self: Self) -> Self:
|
395
|
-
"""
|
396
|
-
Populate .tsdf Pandas DataFrame from the .dates and .values lists.
|
388
|
+
"""Populate .tsdf Pandas DataFrame from the .dates and .values lists.
|
397
389
|
|
398
390
|
Returns
|
399
391
|
-------
|
@@ -413,10 +405,9 @@ class OpenTimeSeries(_CommonModel):
|
|
413
405
|
|
414
406
|
def all_properties(
|
415
407
|
self: Self,
|
416
|
-
properties:
|
408
|
+
properties: list[LiteralSeriesProps] | None = None,
|
417
409
|
) -> DataFrame:
|
418
|
-
"""
|
419
|
-
Calculate chosen properties.
|
410
|
+
"""Calculate chosen properties.
|
420
411
|
|
421
412
|
Parameters
|
422
413
|
----------
|
@@ -441,8 +432,7 @@ class OpenTimeSeries(_CommonModel):
|
|
441
432
|
return pdf
|
442
433
|
|
443
434
|
def value_to_ret(self: Self) -> Self:
|
444
|
-
"""
|
445
|
-
Convert series of values into series of returns.
|
435
|
+
"""Convert series of values into series of returns.
|
446
436
|
|
447
437
|
Returns
|
448
438
|
-------
|
@@ -450,7 +440,7 @@ class OpenTimeSeries(_CommonModel):
|
|
450
440
|
The returns of the values in the series
|
451
441
|
|
452
442
|
"""
|
453
|
-
self.tsdf = self.tsdf.pct_change(fill_method=
|
443
|
+
self.tsdf = self.tsdf.pct_change(fill_method=None) # type: ignore[arg-type]
|
454
444
|
self.tsdf.iloc[0] = 0
|
455
445
|
self.valuetype = ValueType.RTRN
|
456
446
|
self.tsdf.columns = MultiIndex.from_arrays(
|
@@ -462,8 +452,7 @@ class OpenTimeSeries(_CommonModel):
|
|
462
452
|
return self
|
463
453
|
|
464
454
|
def value_to_diff(self: Self, periods: int = 1) -> Self:
|
465
|
-
"""
|
466
|
-
Convert series of values to series of their period differences.
|
455
|
+
"""Convert series of values to series of their period differences.
|
467
456
|
|
468
457
|
Parameters
|
469
458
|
----------
|
@@ -489,8 +478,7 @@ class OpenTimeSeries(_CommonModel):
|
|
489
478
|
return self
|
490
479
|
|
491
480
|
def to_cumret(self: Self) -> Self:
|
492
|
-
"""
|
493
|
-
Convert series of returns into cumulative series of values.
|
481
|
+
"""Convert series of returns into cumulative series of values.
|
494
482
|
|
495
483
|
Returns
|
496
484
|
-------
|
@@ -520,8 +508,7 @@ class OpenTimeSeries(_CommonModel):
|
|
520
508
|
days_in_year: int = 365,
|
521
509
|
divider: float = 1.0,
|
522
510
|
) -> Self:
|
523
|
-
"""
|
524
|
-
Convert series of 1-day rates into series of cumulative values.
|
511
|
+
"""Convert series of 1-day rates into series of cumulative values.
|
525
512
|
|
526
513
|
Parameters
|
527
514
|
----------
|
@@ -555,14 +542,13 @@ class OpenTimeSeries(_CommonModel):
|
|
555
542
|
|
556
543
|
def resample(
|
557
544
|
self: Self,
|
558
|
-
freq:
|
545
|
+
freq: LiteralBizDayFreq | str = "BME",
|
559
546
|
) -> Self:
|
560
|
-
"""
|
561
|
-
Resamples the timeseries frequency.
|
547
|
+
"""Resamples the timeseries frequency.
|
562
548
|
|
563
549
|
Parameters
|
564
550
|
----------
|
565
|
-
freq:
|
551
|
+
freq: LiteralBizDayFreq | str, default "BME"
|
566
552
|
The date offset string that sets the resampled frequency
|
567
553
|
|
568
554
|
Returns
|
@@ -581,8 +567,7 @@ class OpenTimeSeries(_CommonModel):
|
|
581
567
|
freq: LiteralBizDayFreq = "BME",
|
582
568
|
method: LiteralPandasReindexMethod = "nearest",
|
583
569
|
) -> Self:
|
584
|
-
"""
|
585
|
-
Resamples timeseries frequency to the business calendar month end dates.
|
570
|
+
"""Resamples timeseries frequency to the business calendar month end dates.
|
586
571
|
|
587
572
|
Stubs left in place. Stubs will be aligned to the shortest stub.
|
588
573
|
|
@@ -616,13 +601,12 @@ class OpenTimeSeries(_CommonModel):
|
|
616
601
|
lmbda: float = 0.94,
|
617
602
|
day_chunk: int = 11,
|
618
603
|
dlta_degr_freedms: int = 0,
|
619
|
-
months_from_last:
|
620
|
-
from_date:
|
621
|
-
to_date:
|
622
|
-
periods_in_a_year_fixed:
|
604
|
+
months_from_last: int | None = None,
|
605
|
+
from_date: dt.date | None = None,
|
606
|
+
to_date: dt.date | None = None,
|
607
|
+
periods_in_a_year_fixed: DaysInYearType | None = None,
|
623
608
|
) -> Series[float]:
|
624
|
-
"""
|
625
|
-
Exponentially Weighted Moving Average Model for Volatility.
|
609
|
+
"""Exponentially Weighted Moving Average Model for Volatility.
|
626
610
|
|
627
611
|
https://www.investopedia.com/articles/07/ewma.asp.
|
628
612
|
|
@@ -694,8 +678,7 @@ class OpenTimeSeries(_CommonModel):
|
|
694
678
|
adjustment: float,
|
695
679
|
days_in_year: int = 365,
|
696
680
|
) -> Self:
|
697
|
-
"""
|
698
|
-
Add or subtract a fee from the timeseries return.
|
681
|
+
"""Add or subtract a fee from the timeseries return.
|
699
682
|
|
700
683
|
Parameters
|
701
684
|
----------
|
@@ -721,7 +704,7 @@ class OpenTimeSeries(_CommonModel):
|
|
721
704
|
returns_input = True
|
722
705
|
else:
|
723
706
|
values = [cast(float, self.tsdf.iloc[0, 0])]
|
724
|
-
ra_df = self.tsdf.pct_change(fill_method=
|
707
|
+
ra_df = self.tsdf.pct_change(fill_method=None) # type: ignore[arg-type]
|
725
708
|
returns_input = False
|
726
709
|
ra_df = ra_df.dropna()
|
727
710
|
|
@@ -754,13 +737,12 @@ class OpenTimeSeries(_CommonModel):
|
|
754
737
|
|
755
738
|
def set_new_label(
|
756
739
|
self: Self,
|
757
|
-
lvl_zero:
|
758
|
-
lvl_one:
|
740
|
+
lvl_zero: str | None = None,
|
741
|
+
lvl_one: ValueType | None = None,
|
759
742
|
*,
|
760
743
|
delete_lvl_one: bool = False,
|
761
744
|
) -> Self:
|
762
|
-
"""
|
763
|
-
Set the column labels of the .tsdf Pandas Dataframe.
|
745
|
+
"""Set the column labels of the .tsdf Pandas Dataframe.
|
764
746
|
|
765
747
|
Parameters
|
766
748
|
----------
|
@@ -799,9 +781,8 @@ def timeseries_chain(
|
|
799
781
|
front: TypeOpenTimeSeries,
|
800
782
|
back: TypeOpenTimeSeries,
|
801
783
|
old_fee: float = 0.0,
|
802
|
-
) ->
|
803
|
-
"""
|
804
|
-
Chain two timeseries together.
|
784
|
+
) -> TypeOpenTimeSeries | OpenTimeSeries:
|
785
|
+
"""Chain two timeseries together.
|
805
786
|
|
806
787
|
The function assumes that the two series have at least one date in common.
|
807
788
|
|
@@ -816,7 +797,7 @@ def timeseries_chain(
|
|
816
797
|
|
817
798
|
Returns
|
818
799
|
-------
|
819
|
-
|
800
|
+
TypeOpenTimeSeries | OpenTimeSeries
|
820
801
|
An OpenTimeSeries object or a subclass thereof
|
821
802
|
|
822
803
|
"""
|
@@ -887,8 +868,7 @@ def timeseries_chain(
|
|
887
868
|
|
888
869
|
|
889
870
|
def _check_if_none(item: Any) -> bool: # noqa: ANN401
|
890
|
-
"""
|
891
|
-
Check if a variable is None or equivalent.
|
871
|
+
"""Check if a variable is None or equivalent.
|
892
872
|
|
893
873
|
Parameters
|
894
874
|
----------
|
openseries/simulation.py
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
import
|
6
|
-
|
5
|
+
from typing import TYPE_CHECKING, cast
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
import datetime as dt # pragma: no cover
|
7
9
|
|
8
10
|
from numpy import multiply, sqrt
|
9
11
|
from numpy.random import PCG64, Generator, SeedSequence
|
@@ -22,8 +24,8 @@ from pydantic import (
|
|
22
24
|
)
|
23
25
|
from typing_extensions import Self
|
24
26
|
|
25
|
-
from
|
26
|
-
from
|
27
|
+
from .datefixer import generate_calendar_date_range
|
28
|
+
from .types import (
|
27
29
|
CountriesType,
|
28
30
|
DaysInYearType,
|
29
31
|
ValueType,
|
@@ -32,9 +34,8 @@ from openseries.types import (
|
|
32
34
|
__all__ = ["ReturnSimulation"]
|
33
35
|
|
34
36
|
|
35
|
-
def _random_generator(seed:
|
36
|
-
"""
|
37
|
-
Make a Numpy Random Generator object.
|
37
|
+
def _random_generator(seed: int | None) -> Generator:
|
38
|
+
"""Make a Numpy Random Generator object.
|
38
39
|
|
39
40
|
Parameters
|
40
41
|
----------
|
@@ -48,14 +49,12 @@ def _random_generator(seed: Optional[int]) -> Generator:
|
|
48
49
|
|
49
50
|
"""
|
50
51
|
ss = SeedSequence(entropy=seed)
|
51
|
-
bg = PCG64(seed=cast(
|
52
|
+
bg = PCG64(seed=cast(int | None, ss))
|
52
53
|
return Generator(bit_generator=bg)
|
53
54
|
|
54
55
|
|
55
56
|
class ReturnSimulation(BaseModel):
|
56
|
-
|
57
|
-
"""
|
58
|
-
The class ReturnSimulation allows for simulating financial timeseries.
|
57
|
+
"""The class ReturnSimulation allows for simulating financial timeseries.
|
59
58
|
|
60
59
|
Parameters
|
61
60
|
----------
|
@@ -91,7 +90,7 @@ class ReturnSimulation(BaseModel):
|
|
91
90
|
jumps_lamda: NonNegativeFloat = 0.0
|
92
91
|
jumps_sigma: NonNegativeFloat = 0.0
|
93
92
|
jumps_mu: float = 0.0
|
94
|
-
seed:
|
93
|
+
seed: int | None = None
|
95
94
|
|
96
95
|
model_config = ConfigDict(
|
97
96
|
arbitrary_types_allowed=True,
|
@@ -101,8 +100,7 @@ class ReturnSimulation(BaseModel):
|
|
101
100
|
|
102
101
|
@property
|
103
102
|
def results(self: Self) -> DataFrame:
|
104
|
-
"""
|
105
|
-
Simulation data.
|
103
|
+
"""Simulation data.
|
106
104
|
|
107
105
|
Returns
|
108
106
|
-------
|
@@ -114,8 +112,7 @@ class ReturnSimulation(BaseModel):
|
|
114
112
|
|
115
113
|
@property
|
116
114
|
def realized_mean_return(self: Self) -> float:
|
117
|
-
"""
|
118
|
-
Annualized arithmetic mean of returns.
|
115
|
+
"""Annualized arithmetic mean of returns.
|
119
116
|
|
120
117
|
Returns
|
121
118
|
-------
|
@@ -133,8 +130,7 @@ class ReturnSimulation(BaseModel):
|
|
133
130
|
|
134
131
|
@property
|
135
132
|
def realized_vol(self: Self) -> float:
|
136
|
-
"""
|
137
|
-
Annualized volatility.
|
133
|
+
"""Annualized volatility.
|
138
134
|
|
139
135
|
Returns
|
140
136
|
-------
|
@@ -159,10 +155,9 @@ class ReturnSimulation(BaseModel):
|
|
159
155
|
trading_days: PositiveInt,
|
160
156
|
seed: int,
|
161
157
|
trading_days_in_year: DaysInYearType = 252,
|
162
|
-
randomizer:
|
158
|
+
randomizer: Generator | None = None,
|
163
159
|
) -> ReturnSimulation:
|
164
|
-
"""
|
165
|
-
Create a Normal distribution simulation.
|
160
|
+
"""Create a Normal distribution simulation.
|
166
161
|
|
167
162
|
Parameters
|
168
163
|
----------
|
@@ -216,10 +211,9 @@ class ReturnSimulation(BaseModel):
|
|
216
211
|
trading_days: PositiveInt,
|
217
212
|
seed: int,
|
218
213
|
trading_days_in_year: DaysInYearType = 252,
|
219
|
-
randomizer:
|
214
|
+
randomizer: Generator | None = None,
|
220
215
|
) -> ReturnSimulation:
|
221
|
-
"""
|
222
|
-
Create a Lognormal distribution simulation.
|
216
|
+
"""Create a Lognormal distribution simulation.
|
223
217
|
|
224
218
|
Parameters
|
225
219
|
----------
|
@@ -276,10 +270,9 @@ class ReturnSimulation(BaseModel):
|
|
276
270
|
trading_days: PositiveInt,
|
277
271
|
seed: int,
|
278
272
|
trading_days_in_year: DaysInYearType = 252,
|
279
|
-
randomizer:
|
273
|
+
randomizer: Generator | None = None,
|
280
274
|
) -> ReturnSimulation:
|
281
|
-
"""
|
282
|
-
Create a Geometric Brownian Motion simulation.
|
275
|
+
"""Create a Geometric Brownian Motion simulation.
|
283
276
|
|
284
277
|
Parameters
|
285
278
|
----------
|
@@ -342,10 +335,9 @@ class ReturnSimulation(BaseModel):
|
|
342
335
|
jumps_sigma: NonNegativeFloat = 0.0,
|
343
336
|
jumps_mu: float = 0.0,
|
344
337
|
trading_days_in_year: DaysInYearType = 252,
|
345
|
-
randomizer:
|
338
|
+
randomizer: Generator | None = None,
|
346
339
|
) -> ReturnSimulation:
|
347
|
-
"""
|
348
|
-
Create a Merton Jump-Diffusion model simulation.
|
340
|
+
"""Create a Merton Jump-Diffusion model simulation.
|
349
341
|
|
350
342
|
Parameters
|
351
343
|
----------
|
@@ -424,12 +416,11 @@ class ReturnSimulation(BaseModel):
|
|
424
416
|
def to_dataframe(
|
425
417
|
self: Self,
|
426
418
|
name: str,
|
427
|
-
start:
|
428
|
-
end:
|
419
|
+
start: dt.date | None = None,
|
420
|
+
end: dt.date | None = None,
|
429
421
|
countries: CountriesType = "SE",
|
430
422
|
) -> DataFrame:
|
431
|
-
"""
|
432
|
-
Create a pandas.DataFrame from simulation(s).
|
423
|
+
"""Create a pandas.DataFrame from simulation(s).
|
433
424
|
|
434
425
|
Parameters
|
435
426
|
----------
|