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/series.py
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
"""Defining the OpenTimeSeries class."""
|
2
2
|
|
3
|
+
# mypy: disable-error-code="no-any-return"
|
3
4
|
from __future__ import annotations
|
4
5
|
|
5
|
-
import datetime as dt
|
6
6
|
from collections.abc import Iterable
|
7
7
|
from copy import deepcopy
|
8
|
-
from logging import
|
9
|
-
from typing import Any, TypeVar, cast
|
8
|
+
from logging import getLogger
|
9
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
10
|
+
|
11
|
+
if TYPE_CHECKING: # pragma: no cover
|
12
|
+
import datetime as dt
|
10
13
|
|
11
14
|
from numpy import (
|
12
15
|
append,
|
@@ -27,7 +30,6 @@ from pandas import (
|
|
27
30
|
date_range,
|
28
31
|
)
|
29
32
|
from pydantic import field_validator, model_validator
|
30
|
-
from typing_extensions import Self
|
31
33
|
|
32
34
|
from ._common_model import _CommonModel
|
33
35
|
from .datefixer import _do_resample_to_business_period_ends, date_fix
|
@@ -36,22 +38,27 @@ from .owntypes import (
|
|
36
38
|
CountriesType,
|
37
39
|
Currency,
|
38
40
|
CurrencyStringType,
|
41
|
+
DateAlignmentError,
|
39
42
|
DateListType,
|
40
43
|
DaysInYearType,
|
44
|
+
IncorrectArgumentComboError,
|
41
45
|
LiteralBizDayFreq,
|
42
46
|
LiteralPandasReindexMethod,
|
43
47
|
LiteralSeriesProps,
|
44
48
|
OpenTimeSeriesPropertiesList,
|
49
|
+
Self,
|
45
50
|
ValueListType,
|
46
51
|
ValueType,
|
47
52
|
)
|
48
53
|
|
54
|
+
logger = getLogger(__name__)
|
55
|
+
|
49
56
|
__all__ = ["OpenTimeSeries", "timeseries_chain"]
|
50
57
|
|
51
58
|
TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
|
52
59
|
|
53
60
|
|
54
|
-
# noinspection PyUnresolvedReferences
|
61
|
+
# noinspection PyUnresolvedReferences,PyNestedDecorators
|
55
62
|
class OpenTimeSeries(_CommonModel):
|
56
63
|
"""OpenTimeSeries objects are at the core of the openseries package.
|
57
64
|
|
@@ -105,14 +112,14 @@ class OpenTimeSeries(_CommonModel):
|
|
105
112
|
isin: str | None = None
|
106
113
|
label: str | None = None
|
107
114
|
|
108
|
-
@field_validator("domestic", mode="before")
|
115
|
+
@field_validator("domestic", mode="before") # type: ignore[misc]
|
109
116
|
@classmethod
|
110
117
|
def _validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType:
|
111
118
|
"""Pydantic validator to ensure domestic field is validated."""
|
112
119
|
_ = Currency(ccy=value)
|
113
120
|
return value
|
114
121
|
|
115
|
-
@field_validator("countries", mode="before")
|
122
|
+
@field_validator("countries", mode="before") # type: ignore[misc]
|
116
123
|
@classmethod
|
117
124
|
def _validate_countries(cls, value: CountriesType) -> CountriesType:
|
118
125
|
"""Pydantic validator to ensure countries field is validated."""
|
@@ -142,7 +149,7 @@ class OpenTimeSeries(_CommonModel):
|
|
142
149
|
|
143
150
|
@classmethod
|
144
151
|
def from_arrays(
|
145
|
-
cls
|
152
|
+
cls,
|
146
153
|
name: str,
|
147
154
|
dates: DateListType,
|
148
155
|
values: ValueListType,
|
@@ -153,7 +160,7 @@ class OpenTimeSeries(_CommonModel):
|
|
153
160
|
baseccy: CurrencyStringType = "SEK",
|
154
161
|
*,
|
155
162
|
local_ccy: bool = True,
|
156
|
-
) ->
|
163
|
+
) -> Self:
|
157
164
|
"""Create series from a Pandas DataFrame or Series.
|
158
165
|
|
159
166
|
Parameters
|
@@ -177,7 +184,7 @@ class OpenTimeSeries(_CommonModel):
|
|
177
184
|
local_ccy: bool, default: True
|
178
185
|
Boolean flag indicating if timeseries is in local currency
|
179
186
|
|
180
|
-
Returns
|
187
|
+
Returns:
|
181
188
|
-------
|
182
189
|
OpenTimeSeries
|
183
190
|
An OpenTimeSeries object
|
@@ -204,14 +211,14 @@ class OpenTimeSeries(_CommonModel):
|
|
204
211
|
|
205
212
|
@classmethod
|
206
213
|
def from_df(
|
207
|
-
cls
|
214
|
+
cls,
|
208
215
|
dframe: Series[float] | DataFrame,
|
209
216
|
column_nmbr: int = 0,
|
210
217
|
valuetype: ValueType = ValueType.PRICE,
|
211
218
|
baseccy: CurrencyStringType = "SEK",
|
212
219
|
*,
|
213
220
|
local_ccy: bool = True,
|
214
|
-
) ->
|
221
|
+
) -> Self:
|
215
222
|
"""Create series from a Pandas DataFrame or Series.
|
216
223
|
|
217
224
|
Parameters
|
@@ -227,7 +234,7 @@ class OpenTimeSeries(_CommonModel):
|
|
227
234
|
local_ccy: bool, default: True
|
228
235
|
Boolean flag indicating if timeseries is in local currency
|
229
236
|
|
230
|
-
Returns
|
237
|
+
Returns:
|
231
238
|
-------
|
232
239
|
OpenTimeSeries
|
233
240
|
An OpenTimeSeries object
|
@@ -240,7 +247,7 @@ class OpenTimeSeries(_CommonModel):
|
|
240
247
|
label, _ = dframe.name
|
241
248
|
else:
|
242
249
|
label = dframe.name
|
243
|
-
values = cast(list[float], dframe.to_numpy().tolist())
|
250
|
+
values = cast("list[float]", dframe.to_numpy().tolist())
|
244
251
|
elif isinstance(dframe, DataFrame):
|
245
252
|
values = dframe.iloc[:, column_nmbr].to_list()
|
246
253
|
if isinstance(dframe.columns, MultiIndex):
|
@@ -249,7 +256,7 @@ class OpenTimeSeries(_CommonModel):
|
|
249
256
|
):
|
250
257
|
label = "Series"
|
251
258
|
msg = f"Label missing. Adding: {label}"
|
252
|
-
warning(msg=msg)
|
259
|
+
logger.warning(msg=msg)
|
253
260
|
else:
|
254
261
|
label = dframe.columns.get_level_values(0).to_numpy()[column_nmbr]
|
255
262
|
if _check_if_none(
|
@@ -257,13 +264,14 @@ class OpenTimeSeries(_CommonModel):
|
|
257
264
|
):
|
258
265
|
valuetype = ValueType.PRICE
|
259
266
|
msg = f"valuetype missing. Adding: {valuetype.value}"
|
260
|
-
warning(msg=msg)
|
267
|
+
logger.warning(msg=msg)
|
261
268
|
else:
|
262
|
-
valuetype =
|
263
|
-
|
264
|
-
|
269
|
+
valuetype = cast(
|
270
|
+
"ValueType",
|
271
|
+
dframe.columns.get_level_values(1).to_numpy()[column_nmbr],
|
272
|
+
)
|
265
273
|
else:
|
266
|
-
label = cast(MultiIndex, dframe.columns).to_numpy()[column_nmbr]
|
274
|
+
label = cast("MultiIndex", dframe.columns).to_numpy()[column_nmbr]
|
267
275
|
else:
|
268
276
|
raise TypeError(msg)
|
269
277
|
|
@@ -289,7 +297,7 @@ class OpenTimeSeries(_CommonModel):
|
|
289
297
|
|
290
298
|
@classmethod
|
291
299
|
def from_fixed_rate(
|
292
|
-
cls
|
300
|
+
cls,
|
293
301
|
rate: float,
|
294
302
|
d_range: DatetimeIndex | None = None,
|
295
303
|
days: int | None = None,
|
@@ -299,7 +307,7 @@ class OpenTimeSeries(_CommonModel):
|
|
299
307
|
baseccy: CurrencyStringType = "SEK",
|
300
308
|
*,
|
301
309
|
local_ccy: bool = True,
|
302
|
-
) ->
|
310
|
+
) -> Self:
|
303
311
|
"""Create series from values accruing with a given fixed rate return.
|
304
312
|
|
305
313
|
Providing a date_range of type Pandas DatetimeIndex takes priority over
|
@@ -326,7 +334,7 @@ class OpenTimeSeries(_CommonModel):
|
|
326
334
|
local_ccy: bool, default: True
|
327
335
|
Boolean flag indicating if timeseries is in local currency
|
328
336
|
|
329
|
-
Returns
|
337
|
+
Returns:
|
330
338
|
-------
|
331
339
|
OpenTimeSeries
|
332
340
|
An OpenTimeSeries object
|
@@ -338,7 +346,7 @@ class OpenTimeSeries(_CommonModel):
|
|
338
346
|
)
|
339
347
|
elif not isinstance(d_range, Iterable) and not all([days, end_dt]):
|
340
348
|
msg = "If d_range is not provided both days and end_dt must be."
|
341
|
-
raise
|
349
|
+
raise IncorrectArgumentComboError(msg)
|
342
350
|
|
343
351
|
deltas = array(
|
344
352
|
[i.days for i in DatetimeIndex(d_range)[1:] - DatetimeIndex(d_range)[:-1]], # type: ignore[arg-type]
|
@@ -367,7 +375,7 @@ class OpenTimeSeries(_CommonModel):
|
|
367
375
|
def from_deepcopy(self: Self) -> Self:
|
368
376
|
"""Create copy of OpenTimeSeries object.
|
369
377
|
|
370
|
-
Returns
|
378
|
+
Returns:
|
371
379
|
-------
|
372
380
|
OpenTimeSeries
|
373
381
|
An OpenTimeSeries object
|
@@ -378,7 +386,7 @@ class OpenTimeSeries(_CommonModel):
|
|
378
386
|
def pandas_df(self: Self) -> Self:
|
379
387
|
"""Populate .tsdf Pandas DataFrame from the .dates and .values lists.
|
380
388
|
|
381
|
-
Returns
|
389
|
+
Returns:
|
382
390
|
-------
|
383
391
|
OpenTimeSeries
|
384
392
|
An OpenTimeSeries object
|
@@ -405,7 +413,7 @@ class OpenTimeSeries(_CommonModel):
|
|
405
413
|
properties: list[LiteralSeriesProps], optional
|
406
414
|
The properties to calculate. Defaults to calculating all available.
|
407
415
|
|
408
|
-
Returns
|
416
|
+
Returns:
|
409
417
|
-------
|
410
418
|
pandas.DataFrame
|
411
419
|
Properties of the OpenTimeSeries
|
@@ -413,7 +421,7 @@ class OpenTimeSeries(_CommonModel):
|
|
413
421
|
"""
|
414
422
|
if not properties:
|
415
423
|
properties = cast(
|
416
|
-
list[LiteralSeriesProps],
|
424
|
+
"list[LiteralSeriesProps]",
|
417
425
|
OpenTimeSeriesPropertiesList.allowed_strings,
|
418
426
|
)
|
419
427
|
|
@@ -425,7 +433,7 @@ class OpenTimeSeries(_CommonModel):
|
|
425
433
|
def value_to_ret(self: Self) -> Self:
|
426
434
|
"""Convert series of values into series of returns.
|
427
435
|
|
428
|
-
Returns
|
436
|
+
Returns:
|
429
437
|
-------
|
430
438
|
OpenTimeSeries
|
431
439
|
The returns of the values in the series
|
@@ -435,7 +443,9 @@ class OpenTimeSeries(_CommonModel):
|
|
435
443
|
returns.iloc[0] = 0
|
436
444
|
self.valuetype = ValueType.RTRN
|
437
445
|
arrays = [[self.label], [self.valuetype]]
|
438
|
-
returns.columns = MultiIndex.from_arrays(
|
446
|
+
returns.columns = MultiIndex.from_arrays(
|
447
|
+
arrays=arrays # type: ignore[arg-type]
|
448
|
+
)
|
439
449
|
self.tsdf = returns.copy()
|
440
450
|
return self
|
441
451
|
|
@@ -448,7 +458,7 @@ class OpenTimeSeries(_CommonModel):
|
|
448
458
|
The number of periods between observations over which difference
|
449
459
|
is calculated
|
450
460
|
|
451
|
-
Returns
|
461
|
+
Returns:
|
452
462
|
-------
|
453
463
|
OpenTimeSeries
|
454
464
|
An OpenTimeSeries object
|
@@ -468,7 +478,7 @@ class OpenTimeSeries(_CommonModel):
|
|
468
478
|
def to_cumret(self: Self) -> Self:
|
469
479
|
"""Convert series of returns into cumulative series of values.
|
470
480
|
|
471
|
-
Returns
|
481
|
+
Returns:
|
472
482
|
-------
|
473
483
|
OpenTimeSeries
|
474
484
|
An OpenTimeSeries object
|
@@ -503,7 +513,7 @@ class OpenTimeSeries(_CommonModel):
|
|
503
513
|
divider: float, default 100.0
|
504
514
|
Convenience divider for when the 1-day rate is not scaled correctly
|
505
515
|
|
506
|
-
Returns
|
516
|
+
Returns:
|
507
517
|
-------
|
508
518
|
OpenTimeSeries
|
509
519
|
An OpenTimeSeries object
|
@@ -513,7 +523,7 @@ class OpenTimeSeries(_CommonModel):
|
|
513
523
|
|
514
524
|
deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]])
|
515
525
|
# noinspection PyTypeChecker
|
516
|
-
arr = cumprod( # type: ignore[assignment
|
526
|
+
arr = cumprod( # type: ignore[assignment]
|
517
527
|
a=insert(arr=1.0 + deltas * arr[:-1] / days_in_year, obj=0, values=1.0)
|
518
528
|
)
|
519
529
|
|
@@ -540,7 +550,7 @@ class OpenTimeSeries(_CommonModel):
|
|
540
550
|
freq: LiteralBizDayFreq | str, default "BME"
|
541
551
|
The date offset string that sets the resampled frequency
|
542
552
|
|
543
|
-
Returns
|
553
|
+
Returns:
|
544
554
|
-------
|
545
555
|
OpenTimeSeries
|
546
556
|
An OpenTimeSeries object
|
@@ -567,7 +577,7 @@ class OpenTimeSeries(_CommonModel):
|
|
567
577
|
method: LiteralPandasReindexMethod, default: nearest
|
568
578
|
Controls the method used to align values across columns
|
569
579
|
|
570
|
-
Returns
|
580
|
+
Returns:
|
571
581
|
-------
|
572
582
|
OpenTimeSeries
|
573
583
|
An OpenTimeSeries object
|
@@ -613,7 +623,7 @@ class OpenTimeSeries(_CommonModel):
|
|
613
623
|
periods_in_a_year_fixed : DaysInYearType, optional
|
614
624
|
Allows locking the periods-in-a-year to simplify test cases and comparisons
|
615
625
|
|
616
|
-
Returns
|
626
|
+
Returns:
|
617
627
|
-------
|
618
628
|
Pandas.Series[float]
|
619
629
|
Series EWMA volatility
|
@@ -624,26 +634,26 @@ class OpenTimeSeries(_CommonModel):
|
|
624
634
|
time_factor = float(periods_in_a_year_fixed)
|
625
635
|
else:
|
626
636
|
how_many = self.tsdf.loc[
|
627
|
-
cast(int, earlier) : cast(int, later),
|
637
|
+
cast("int", earlier) : cast("int", later),
|
628
638
|
self.tsdf.columns.to_numpy()[0],
|
629
639
|
].count()
|
630
640
|
fraction = (later - earlier).days / 365.25
|
631
|
-
time_factor = how_many / fraction
|
641
|
+
time_factor = cast("int", how_many) / fraction
|
632
642
|
|
633
|
-
data = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy()
|
643
|
+
data = self.tsdf.loc[cast("int", earlier) : cast("int", later)].copy()
|
634
644
|
|
635
645
|
data[self.label, ValueType.RTRN] = (
|
636
646
|
data.loc[:, self.tsdf.columns.to_numpy()[0]].apply(log).diff()
|
637
647
|
)
|
638
648
|
|
639
649
|
rawdata = [
|
640
|
-
data.loc[:, cast(int, (self.label, ValueType.RTRN))]
|
650
|
+
data.loc[:, cast("int", (self.label, ValueType.RTRN))]
|
641
651
|
.iloc[1:day_chunk]
|
642
652
|
.std(ddof=dlta_degr_freedms)
|
643
653
|
* sqrt(time_factor),
|
644
654
|
]
|
645
655
|
|
646
|
-
for item in data.loc[:, cast(int, (self.label, ValueType.RTRN))].iloc[1:]:
|
656
|
+
for item in data.loc[:, cast("int", (self.label, ValueType.RTRN))].iloc[1:]:
|
647
657
|
prev = rawdata[-1]
|
648
658
|
rawdata.append(
|
649
659
|
sqrt(
|
@@ -673,7 +683,7 @@ class OpenTimeSeries(_CommonModel):
|
|
673
683
|
The calculation divisor and
|
674
684
|
assumed number of days in a calendar year
|
675
685
|
|
676
|
-
Returns
|
686
|
+
Returns:
|
677
687
|
-------
|
678
688
|
OpenTimeSeries
|
679
689
|
An OpenTimeSeries object
|
@@ -684,7 +694,7 @@ class OpenTimeSeries(_CommonModel):
|
|
684
694
|
values = [1.0]
|
685
695
|
returns_input = True
|
686
696
|
else:
|
687
|
-
values = [cast(float, self.tsdf.iloc[0, 0])]
|
697
|
+
values = [cast("float", self.tsdf.iloc[0, 0])]
|
688
698
|
ra_df = self.tsdf.ffill().pct_change()
|
689
699
|
returns_input = False
|
690
700
|
ra_df = ra_df.dropna()
|
@@ -693,16 +703,16 @@ class OpenTimeSeries(_CommonModel):
|
|
693
703
|
dates: list[dt.date] = [prev]
|
694
704
|
|
695
705
|
for idx, row in ra_df.iterrows():
|
696
|
-
dates.append(cast(dt.date, idx))
|
706
|
+
dates.append(cast("dt.date", idx))
|
697
707
|
values.append(
|
698
708
|
values[-1]
|
699
709
|
* (
|
700
710
|
1
|
701
711
|
+ row.iloc[0]
|
702
|
-
+ adjustment * (cast(dt.date, idx) - prev).days / days_in_year
|
712
|
+
+ adjustment * (cast("dt.date", idx) - prev).days / days_in_year
|
703
713
|
),
|
704
714
|
)
|
705
|
-
prev = cast(dt.date, idx)
|
715
|
+
prev = cast("dt.date", idx)
|
706
716
|
self.tsdf = DataFrame(data=values, index=dates)
|
707
717
|
self.valuetype = ValueType.PRICE
|
708
718
|
self.tsdf.columns = MultiIndex.from_arrays(
|
@@ -734,7 +744,7 @@ class OpenTimeSeries(_CommonModel):
|
|
734
744
|
delete_lvl_one: bool, default: False
|
735
745
|
If True the level one label is deleted
|
736
746
|
|
737
|
-
Returns
|
747
|
+
Returns:
|
738
748
|
-------
|
739
749
|
OpenTimeSeries
|
740
750
|
An OpenTimeSeries object
|
@@ -752,7 +762,7 @@ class OpenTimeSeries(_CommonModel):
|
|
752
762
|
self.valuetype = lvl_one
|
753
763
|
else:
|
754
764
|
self.tsdf.columns = MultiIndex.from_arrays([[lvl_zero], [lvl_one]])
|
755
|
-
self.label, self.valuetype = lvl_zero, cast(ValueType, lvl_one)
|
765
|
+
self.label, self.valuetype = lvl_zero, cast("ValueType", lvl_one)
|
756
766
|
if delete_lvl_one:
|
757
767
|
self.tsdf.columns = self.tsdf.columns.droplevel(level=1)
|
758
768
|
return self
|
@@ -762,7 +772,7 @@ def timeseries_chain(
|
|
762
772
|
front: TypeOpenTimeSeries,
|
763
773
|
back: TypeOpenTimeSeries,
|
764
774
|
old_fee: float = 0.0,
|
765
|
-
) -> TypeOpenTimeSeries
|
775
|
+
) -> TypeOpenTimeSeries:
|
766
776
|
"""Chain two timeseries together.
|
767
777
|
|
768
778
|
The function assumes that the two series have at least one date in common.
|
@@ -776,9 +786,9 @@ def timeseries_chain(
|
|
776
786
|
old_fee: float, default: 0.0
|
777
787
|
Fee to apply to earlier series
|
778
788
|
|
779
|
-
Returns
|
789
|
+
Returns:
|
780
790
|
-------
|
781
|
-
TypeOpenTimeSeries
|
791
|
+
TypeOpenTimeSeries
|
782
792
|
An OpenTimeSeries object or a subclass thereof
|
783
793
|
|
784
794
|
"""
|
@@ -790,46 +800,26 @@ def timeseries_chain(
|
|
790
800
|
|
791
801
|
if old.last_idx < first:
|
792
802
|
msg = "Timeseries dates must overlap to allow them to be chained."
|
793
|
-
raise
|
803
|
+
raise DateAlignmentError(msg)
|
794
804
|
|
795
805
|
while first not in old.tsdf.index:
|
796
806
|
idx += 1
|
797
807
|
first = new.tsdf.index[idx]
|
798
808
|
if first > old.tsdf.index[-1]:
|
799
809
|
msg = "Failed to find a matching date between series"
|
800
|
-
raise
|
810
|
+
raise DateAlignmentError(msg)
|
801
811
|
|
802
812
|
dates: list[str] = [x.strftime("%Y-%m-%d") for x in old.tsdf.index if x < first]
|
803
813
|
|
804
|
-
old_values = old.tsdf.iloc[: len(dates), 0]
|
814
|
+
old_values = Series(old.tsdf.iloc[: len(dates), 0])
|
805
815
|
old_values = old_values.mul(
|
806
|
-
new.tsdf.iloc[:, 0].loc[first]
|
816
|
+
Series(new.tsdf.iloc[:, 0]).loc[first]
|
817
|
+
/ Series(old.tsdf.iloc[:, 0]).loc[first],
|
807
818
|
)
|
808
819
|
values = append(old_values, new.tsdf.iloc[:, 0])
|
809
820
|
|
810
821
|
dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
|
811
822
|
|
812
|
-
# noinspection PyUnresolvedReferences
|
813
|
-
if back.__class__.__subclasscheck__(
|
814
|
-
OpenTimeSeries,
|
815
|
-
):
|
816
|
-
return OpenTimeSeries(
|
817
|
-
timeseries_id=new.timeseries_id,
|
818
|
-
instrument_id=new.instrument_id,
|
819
|
-
currency=new.currency,
|
820
|
-
dates=dates,
|
821
|
-
name=new.name,
|
822
|
-
label=new.name,
|
823
|
-
valuetype=new.valuetype,
|
824
|
-
values=list(values),
|
825
|
-
local_ccy=new.local_ccy,
|
826
|
-
tsdf=DataFrame(
|
827
|
-
data=values,
|
828
|
-
index=[d.date() for d in DatetimeIndex(dates)],
|
829
|
-
columns=[[new.label], [new.valuetype]],
|
830
|
-
dtype="float64",
|
831
|
-
),
|
832
|
-
)
|
833
823
|
return back.__class__(
|
834
824
|
timeseries_id=new.timeseries_id,
|
835
825
|
instrument_id=new.instrument_id,
|
@@ -857,14 +847,14 @@ def _check_if_none(item: Any) -> bool: # noqa: ANN401
|
|
857
847
|
item : Any
|
858
848
|
variable to be checked
|
859
849
|
|
860
|
-
Returns
|
850
|
+
Returns:
|
861
851
|
-------
|
862
852
|
bool
|
863
853
|
Answer to whether the variable is None or equivalent
|
864
854
|
|
865
855
|
"""
|
866
856
|
try:
|
867
|
-
return cast(bool, isnan(item))
|
857
|
+
return cast("bool", isnan(item))
|
868
858
|
except TypeError:
|
869
859
|
if item is None:
|
870
860
|
return True
|
openseries/simulation.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Defining the ReturnSimulation class."""
|
2
2
|
|
3
|
+
# mypy: disable-error-code="no-any-return"
|
3
4
|
from __future__ import annotations
|
4
5
|
|
5
6
|
from typing import TYPE_CHECKING, cast
|
@@ -22,12 +23,12 @@ from pydantic import (
|
|
22
23
|
PositiveFloat,
|
23
24
|
PositiveInt,
|
24
25
|
)
|
25
|
-
from typing_extensions import Self
|
26
26
|
|
27
27
|
from .datefixer import generate_calendar_date_range
|
28
28
|
from .owntypes import (
|
29
29
|
CountriesType,
|
30
30
|
DaysInYearType,
|
31
|
+
Self,
|
31
32
|
ValueType,
|
32
33
|
)
|
33
34
|
|
@@ -42,18 +43,18 @@ def _random_generator(seed: int | None) -> Generator:
|
|
42
43
|
seed: int, optional
|
43
44
|
Random seed
|
44
45
|
|
45
|
-
Returns
|
46
|
+
Returns:
|
46
47
|
-------
|
47
48
|
numpy.random.Generator
|
48
49
|
Numpy random process generator
|
49
50
|
|
50
51
|
"""
|
51
52
|
ss = SeedSequence(entropy=seed)
|
52
|
-
bg = PCG64(seed=cast(int | None, ss))
|
53
|
+
bg = PCG64(seed=cast("int | None", ss))
|
53
54
|
return Generator(bit_generator=bg)
|
54
55
|
|
55
56
|
|
56
|
-
class ReturnSimulation(BaseModel):
|
57
|
+
class ReturnSimulation(BaseModel): # type: ignore[misc]
|
57
58
|
"""The class ReturnSimulation allows for simulating financial timeseries.
|
58
59
|
|
59
60
|
Parameters
|
@@ -102,7 +103,7 @@ class ReturnSimulation(BaseModel):
|
|
102
103
|
def results(self: Self) -> DataFrame:
|
103
104
|
"""Simulation data.
|
104
105
|
|
105
|
-
Returns
|
106
|
+
Returns:
|
106
107
|
-------
|
107
108
|
pandas.DataFrame
|
108
109
|
Simulation data
|
@@ -114,14 +115,14 @@ class ReturnSimulation(BaseModel):
|
|
114
115
|
def realized_mean_return(self: Self) -> float:
|
115
116
|
"""Annualized arithmetic mean of returns.
|
116
117
|
|
117
|
-
Returns
|
118
|
+
Returns:
|
118
119
|
-------
|
119
120
|
float
|
120
121
|
Annualized arithmetic mean of returns
|
121
122
|
|
122
123
|
"""
|
123
124
|
return cast(
|
124
|
-
float,
|
125
|
+
"float",
|
125
126
|
(
|
126
127
|
self.results.ffill().pct_change().mean() * self.trading_days_in_year
|
127
128
|
).iloc[0],
|
@@ -131,14 +132,14 @@ class ReturnSimulation(BaseModel):
|
|
131
132
|
def realized_vol(self: Self) -> float:
|
132
133
|
"""Annualized volatility.
|
133
134
|
|
134
|
-
Returns
|
135
|
+
Returns:
|
135
136
|
-------
|
136
137
|
float
|
137
138
|
Annualized volatility
|
138
139
|
|
139
140
|
"""
|
140
141
|
return cast(
|
141
|
-
float,
|
142
|
+
"float",
|
142
143
|
(
|
143
144
|
self.results.ffill().pct_change().std()
|
144
145
|
* sqrt(self.trading_days_in_year)
|
@@ -175,7 +176,7 @@ class ReturnSimulation(BaseModel):
|
|
175
176
|
randomizer: numpy.random.Generator, optional
|
176
177
|
Random process generator
|
177
178
|
|
178
|
-
Returns
|
179
|
+
Returns:
|
179
180
|
-------
|
180
181
|
ReturnSimulation
|
181
182
|
Normal distribution simulation
|
@@ -230,7 +231,7 @@ class ReturnSimulation(BaseModel):
|
|
230
231
|
randomizer: numpy.random.Generator, optional
|
231
232
|
Random process generator
|
232
233
|
|
233
|
-
Returns
|
234
|
+
Returns:
|
234
235
|
-------
|
235
236
|
ReturnSimulation
|
236
237
|
Lognormal distribution simulation
|
@@ -288,7 +289,7 @@ class ReturnSimulation(BaseModel):
|
|
288
289
|
randomizer: numpy.random.Generator, optional
|
289
290
|
Random process generator
|
290
291
|
|
291
|
-
Returns
|
292
|
+
Returns:
|
292
293
|
-------
|
293
294
|
ReturnSimulation
|
294
295
|
Geometric Brownian Motion simulation
|
@@ -359,7 +360,7 @@ class ReturnSimulation(BaseModel):
|
|
359
360
|
randomizer: numpy.random.Generator, optional
|
360
361
|
Random process generator
|
361
362
|
|
362
|
-
Returns
|
363
|
+
Returns:
|
363
364
|
-------
|
364
365
|
ReturnSimulation
|
365
366
|
Merton Jump-Diffusion model simulation
|
@@ -430,7 +431,7 @@ class ReturnSimulation(BaseModel):
|
|
430
431
|
countries: CountriesType, default: "SE"
|
431
432
|
(List of) country code(s) according to ISO 3166-1 alpha-2
|
432
433
|
|
433
|
-
Returns
|
434
|
+
Returns:
|
434
435
|
-------
|
435
436
|
pandas.DataFrame
|
436
437
|
The simulation(s) data
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# BSD 3-Clause License
|
2
|
+
|
3
|
+
## Copyright (c) Captor Fund Management AB
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without modification, are
|
6
|
+
permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this list of
|
9
|
+
conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
12
|
+
of conditions and the following disclaimer in the documentation and/or other
|
13
|
+
materials provided with the distribution.
|
14
|
+
|
15
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be
|
16
|
+
used to endorse or promote products derived from this software without specific prior
|
17
|
+
written permission.
|
18
|
+
|
19
|
+
This software is provided by the copyright holders and contributors “as is” and any
|
20
|
+
express or implied warranties, including, but not limited to, the implied warranties of
|
21
|
+
merchantability and fitness for a particular purpose, are disclaimed. In no event shall
|
22
|
+
the copyright holder or contributors be liable for any direct, indirect, incidental,
|
23
|
+
special, exemplary, or consequential damages (including, but not limited to, procurement
|
24
|
+
of substitute goods or services; loss of use, data, or profits; or business interruption)
|
25
|
+
however caused and on any theory of liability, whether in contract, strict liability,
|
26
|
+
or tort (including negligence or otherwise) arising in any way out of the use of this
|
27
|
+
software, even if advised of the possibility of such damage.
|