openseries 1.2.2__py3-none-any.whl → 1.2.4__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 +1 -0
- openseries/common_model.py +265 -171
- openseries/datefixer.py +94 -111
- openseries/frame.py +252 -160
- openseries/load_plotly.py +11 -21
- openseries/risk.py +45 -23
- openseries/series.py +135 -110
- openseries/simulation.py +157 -109
- openseries/types.py +88 -21
- {openseries-1.2.2.dist-info → openseries-1.2.4.dist-info}/METADATA +11 -12
- openseries-1.2.4.dist-info/RECORD +15 -0
- {openseries-1.2.2.dist-info → openseries-1.2.4.dist-info}/WHEEL +1 -1
- openseries-1.2.2.dist-info/RECORD +0 -15
- {openseries-1.2.2.dist-info → openseries-1.2.4.dist-info}/LICENSE.md +0 -0
openseries/series.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
"""
|
2
|
-
Defining the OpenTimeSeries class
|
3
|
-
"""
|
1
|
+
"""Defining the OpenTimeSeries class."""
|
4
2
|
from __future__ import annotations
|
5
|
-
|
3
|
+
|
6
4
|
import datetime as dt
|
5
|
+
from copy import deepcopy
|
7
6
|
from re import compile as re_compile
|
8
|
-
from typing import
|
7
|
+
from typing import Any, Optional, TypeVar, Union, cast
|
8
|
+
|
9
9
|
from numpy import (
|
10
10
|
array,
|
11
11
|
cumprod,
|
@@ -19,9 +19,9 @@ from numpy.typing import NDArray
|
|
19
19
|
from pandas import (
|
20
20
|
DataFrame,
|
21
21
|
DatetimeIndex,
|
22
|
-
date_range,
|
23
22
|
MultiIndex,
|
24
23
|
Series,
|
24
|
+
date_range,
|
25
25
|
)
|
26
26
|
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
27
27
|
from stdnum import isin as isincode
|
@@ -29,34 +29,36 @@ from stdnum.exceptions import InvalidChecksum
|
|
29
29
|
|
30
30
|
from openseries.common_model import CommonModel
|
31
31
|
from openseries.datefixer import (
|
32
|
-
date_fix,
|
33
32
|
align_dataframe_to_local_cdays,
|
33
|
+
date_fix,
|
34
34
|
do_resample_to_business_period_ends,
|
35
35
|
get_calc_range,
|
36
36
|
)
|
37
|
+
from openseries.risk import (
|
38
|
+
drawdown_details,
|
39
|
+
ewma_calc,
|
40
|
+
)
|
37
41
|
from openseries.types import (
|
38
42
|
CountriesType,
|
39
43
|
CurrencyStringType,
|
40
44
|
DatabaseIdStringType,
|
41
45
|
DateListType,
|
42
|
-
ValueListType,
|
43
46
|
LiteralBizDayFreq,
|
44
|
-
LiteralPandasResampleConvention,
|
45
47
|
LiteralPandasReindexMethod,
|
48
|
+
LiteralPandasResampleConvention,
|
46
49
|
LiteralSeriesProps,
|
47
50
|
OpenTimeSeriesPropertiesList,
|
51
|
+
ValueListType,
|
48
52
|
ValueType,
|
49
53
|
)
|
50
|
-
from openseries.risk import (
|
51
|
-
drawdown_details,
|
52
|
-
ewma_calc,
|
53
|
-
)
|
54
54
|
|
55
55
|
TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
|
56
56
|
|
57
57
|
|
58
|
-
class OpenTimeSeries(BaseModel, CommonModel):
|
59
|
-
|
58
|
+
class OpenTimeSeries(BaseModel, CommonModel): # type: ignore[misc]
|
59
|
+
|
60
|
+
"""
|
61
|
+
Object of the class OpenTimeSeries.
|
60
62
|
|
61
63
|
Parameters
|
62
64
|
----------
|
@@ -90,8 +92,8 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
90
92
|
Placeholder for a name of the timeseries
|
91
93
|
"""
|
92
94
|
|
93
|
-
timeseriesId: DatabaseIdStringType
|
94
|
-
instrumentId: DatabaseIdStringType
|
95
|
+
timeseriesId: DatabaseIdStringType # noqa: N815
|
96
|
+
instrumentId: DatabaseIdStringType # noqa: N815
|
95
97
|
name: str
|
96
98
|
valuetype: ValueType
|
97
99
|
dates: DateListType
|
@@ -111,23 +113,21 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
111
113
|
extra="allow",
|
112
114
|
)
|
113
115
|
|
114
|
-
@field_validator("isin")
|
115
|
-
def check_isincode( #
|
116
|
-
|
117
|
-
) -> str:
|
118
|
-
"""Pydantic validator to ensure that the ISIN code is valid if provided"""
|
116
|
+
@field_validator("isin") # type: ignore[misc]
|
117
|
+
def check_isincode(cls: TypeOpenTimeSeries, isin_code: str) -> str: # noqa: N805
|
118
|
+
"""Pydantic validator to ensure that the ISIN code is valid if provided."""
|
119
119
|
if isin_code:
|
120
120
|
try:
|
121
121
|
isincode.validate(isin_code)
|
122
122
|
except InvalidChecksum as exc:
|
123
123
|
raise ValueError(
|
124
|
-
"The ISIN code's checksum or check digit is invalid."
|
124
|
+
"The ISIN code's checksum or check digit is invalid.",
|
125
125
|
) from exc
|
126
126
|
return isin_code
|
127
127
|
|
128
|
-
@model_validator(mode="after")
|
129
|
-
def check_dates_unique(self) -> OpenTimeSeries:
|
130
|
-
"""Pydantic validator to ensure that the dates are unique"""
|
128
|
+
@model_validator(mode="after") # type: ignore[misc]
|
129
|
+
def check_dates_unique(self: TypeOpenTimeSeries) -> OpenTimeSeries:
|
130
|
+
"""Pydantic validator to ensure that the dates are unique."""
|
131
131
|
dates_list_length = len(self.dates)
|
132
132
|
dates_set_length = len(set(self.dates))
|
133
133
|
if dates_list_length != dates_set_length:
|
@@ -136,17 +136,18 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
136
136
|
|
137
137
|
@classmethod
|
138
138
|
def setup_class(
|
139
|
-
cls:
|
139
|
+
cls: type[TypeOpenTimeSeries],
|
140
140
|
domestic_ccy: CurrencyStringType = "SEK",
|
141
141
|
countries: CountriesType = "SE",
|
142
142
|
) -> None:
|
143
|
-
"""
|
143
|
+
"""
|
144
|
+
Set the domestic currency and calendar of the user.
|
144
145
|
|
145
146
|
Parameters
|
146
147
|
----------
|
147
|
-
domestic_ccy :
|
148
|
+
domestic_ccy : CurrencyStringType, default: "SEK"
|
148
149
|
Currency code according to ISO 4217
|
149
|
-
countries:
|
150
|
+
countries: CountriesType, default: "SE"
|
150
151
|
(List of) country code(s) according to ISO 3166-1 alpha-2
|
151
152
|
"""
|
152
153
|
ccy_pattern = re_compile(r"^[A-Z]{3}$")
|
@@ -155,7 +156,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
155
156
|
ccy_ok = ccy_pattern.match(domestic_ccy)
|
156
157
|
except TypeError as exc:
|
157
158
|
raise ValueError(
|
158
|
-
"domestic currency must be a code according to ISO 4217"
|
159
|
+
"domestic currency must be a code according to ISO 4217",
|
159
160
|
) from exc
|
160
161
|
if not ccy_ok:
|
161
162
|
raise ValueError("domestic currency must be a code according to ISO 4217")
|
@@ -163,7 +164,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
163
164
|
if not ctry_pattern.match(countries):
|
164
165
|
raise ValueError(
|
165
166
|
"countries must be a country code according to "
|
166
|
-
"ISO 3166-1 alpha-2"
|
167
|
+
"ISO 3166-1 alpha-2",
|
167
168
|
)
|
168
169
|
elif isinstance(countries, list):
|
169
170
|
try:
|
@@ -171,17 +172,17 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
171
172
|
except TypeError as exc:
|
172
173
|
raise ValueError(
|
173
174
|
"countries must be a list of country codes "
|
174
|
-
"according to ISO 3166-1 alpha-2"
|
175
|
+
"according to ISO 3166-1 alpha-2",
|
175
176
|
) from exc
|
176
177
|
if not all_ctries:
|
177
178
|
raise ValueError(
|
178
179
|
"countries must be a list of country codes "
|
179
|
-
"according to ISO 3166-1 alpha-2"
|
180
|
+
"according to ISO 3166-1 alpha-2",
|
180
181
|
)
|
181
182
|
else:
|
182
183
|
raise ValueError(
|
183
184
|
"countries must be a (list of) country code(s) "
|
184
|
-
"according to ISO 3166-1 alpha-2"
|
185
|
+
"according to ISO 3166-1 alpha-2",
|
185
186
|
)
|
186
187
|
|
187
188
|
cls.domestic = domestic_ccy
|
@@ -189,7 +190,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
189
190
|
|
190
191
|
@classmethod
|
191
192
|
def from_arrays(
|
192
|
-
cls:
|
193
|
+
cls: type[TypeOpenTimeSeries],
|
193
194
|
name: str,
|
194
195
|
dates: DateListType,
|
195
196
|
values: ValueListType,
|
@@ -200,7 +201,8 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
200
201
|
baseccy: CurrencyStringType = "SEK",
|
201
202
|
local_ccy: bool = True,
|
202
203
|
) -> TypeOpenTimeSeries:
|
203
|
-
"""
|
204
|
+
"""
|
205
|
+
Create series from a Pandas DataFrame or Series.
|
204
206
|
|
205
207
|
Parameters
|
206
208
|
----------
|
@@ -228,7 +230,6 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
228
230
|
OpenTimeSeries
|
229
231
|
An OpenTimeSeries object
|
230
232
|
"""
|
231
|
-
|
232
233
|
return cls(
|
233
234
|
name=name,
|
234
235
|
label=name,
|
@@ -250,14 +251,15 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
250
251
|
|
251
252
|
@classmethod
|
252
253
|
def from_df(
|
253
|
-
cls:
|
254
|
+
cls: type[TypeOpenTimeSeries],
|
254
255
|
dframe: Union[DataFrame, Series],
|
255
256
|
column_nmbr: int = 0,
|
256
257
|
valuetype: ValueType = ValueType.PRICE,
|
257
258
|
baseccy: CurrencyStringType = "SEK",
|
258
259
|
local_ccy: bool = True,
|
259
260
|
) -> TypeOpenTimeSeries:
|
260
|
-
"""
|
261
|
+
"""
|
262
|
+
Create series from a Pandas DataFrame or Series.
|
261
263
|
|
262
264
|
Parameters
|
263
265
|
----------
|
@@ -277,38 +279,39 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
277
279
|
OpenTimeSeries
|
278
280
|
An OpenTimeSeries object
|
279
281
|
"""
|
280
|
-
|
281
282
|
if isinstance(dframe, Series):
|
282
283
|
if isinstance(dframe.name, tuple):
|
283
284
|
label, _ = dframe.name
|
284
285
|
else:
|
285
286
|
label = dframe.name
|
286
|
-
values = dframe.
|
287
|
+
values = dframe.to_numpy().tolist()
|
287
288
|
else:
|
288
289
|
values = dframe.iloc[:, column_nmbr].tolist()
|
289
290
|
if isinstance(dframe.columns, MultiIndex):
|
290
291
|
if check_if_none(
|
291
|
-
dframe.columns.get_level_values(0).
|
292
|
+
dframe.columns.get_level_values(0).to_numpy()[column_nmbr],
|
292
293
|
):
|
293
294
|
print(
|
294
295
|
"checked item",
|
295
|
-
dframe.columns.get_level_values(0).
|
296
|
+
dframe.columns.get_level_values(0).to_numpy()[column_nmbr],
|
296
297
|
)
|
297
298
|
label = "Series"
|
298
299
|
print(f"label missing. Adding '{label}' as label")
|
299
300
|
else:
|
300
|
-
label = dframe.columns.get_level_values(0).
|
301
|
+
label = dframe.columns.get_level_values(0).to_numpy()[column_nmbr]
|
301
302
|
if check_if_none(
|
302
|
-
dframe.columns.get_level_values(1).
|
303
|
+
dframe.columns.get_level_values(1).to_numpy()[column_nmbr],
|
303
304
|
):
|
304
305
|
valuetype = ValueType.PRICE
|
305
306
|
print(
|
306
|
-
f"valuetype missing. Adding '{valuetype.value}' as valuetype"
|
307
|
+
f"valuetype missing. Adding '{valuetype.value}' as valuetype",
|
307
308
|
)
|
308
309
|
else:
|
309
|
-
valuetype = dframe.columns.get_level_values(1).
|
310
|
+
valuetype = dframe.columns.get_level_values(1).to_numpy()[
|
311
|
+
column_nmbr
|
312
|
+
]
|
310
313
|
else:
|
311
|
-
label = dframe.columns.
|
314
|
+
label = dframe.columns.to_numpy()[column_nmbr]
|
312
315
|
dates = [date_fix(d).strftime("%Y-%m-%d") for d in dframe.index]
|
313
316
|
|
314
317
|
return cls(
|
@@ -331,7 +334,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
331
334
|
|
332
335
|
@classmethod
|
333
336
|
def from_fixed_rate(
|
334
|
-
cls:
|
337
|
+
cls: type[TypeOpenTimeSeries],
|
335
338
|
rate: float,
|
336
339
|
d_range: Optional[DatetimeIndex] = None,
|
337
340
|
days: Optional[int] = None,
|
@@ -341,7 +344,8 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
341
344
|
baseccy: CurrencyStringType = "SEK",
|
342
345
|
local_ccy: bool = True,
|
343
346
|
) -> TypeOpenTimeSeries:
|
344
|
-
"""
|
347
|
+
"""
|
348
|
+
Create series from values accruing with a given fixed rate return.
|
345
349
|
|
346
350
|
Providing a date_range of type Pandas DatetimeIndex takes priority over
|
347
351
|
providing a combination of days and an end date.
|
@@ -374,11 +378,11 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
374
378
|
"""
|
375
379
|
if not isinstance(d_range, DatetimeIndex) and all([days, end_dt]):
|
376
380
|
d_range = DatetimeIndex(
|
377
|
-
[d.date() for d in date_range(periods=days, end=end_dt, freq="D")]
|
381
|
+
[d.date() for d in date_range(periods=days, end=end_dt, freq="D")],
|
378
382
|
)
|
379
383
|
elif not isinstance(d_range, DatetimeIndex) and not all([days, end_dt]):
|
380
384
|
raise ValueError(
|
381
|
-
"If d_range is not provided both days and end_dt must be."
|
385
|
+
"If d_range is not provided both days and end_dt must be.",
|
382
386
|
)
|
383
387
|
|
384
388
|
deltas = array(
|
@@ -386,7 +390,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
386
390
|
i.days
|
387
391
|
for i in cast(DatetimeIndex, d_range)[1:]
|
388
392
|
- cast(DatetimeIndex, d_range)[:-1]
|
389
|
-
]
|
393
|
+
],
|
390
394
|
)
|
391
395
|
arr = list(cumprod(insert(1 + deltas * rate / 365, 0, 1.0)))
|
392
396
|
d_range = [d.strftime("%Y-%m-%d") for d in cast(DatetimeIndex, d_range)]
|
@@ -410,19 +414,19 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
410
414
|
)
|
411
415
|
|
412
416
|
def from_deepcopy(self: TypeOpenTimeSeries) -> TypeOpenTimeSeries:
|
413
|
-
"""
|
417
|
+
"""
|
418
|
+
Create copy of OpenTimeSeries object.
|
414
419
|
|
415
420
|
Returns
|
416
421
|
-------
|
417
422
|
OpenTimeSeries
|
418
423
|
An OpenTimeSeries object
|
419
424
|
"""
|
420
|
-
|
421
425
|
return deepcopy(self)
|
422
426
|
|
423
427
|
def pandas_df(self: TypeOpenTimeSeries) -> TypeOpenTimeSeries:
|
424
|
-
"""
|
425
|
-
.values lists
|
428
|
+
"""
|
429
|
+
Populate .tsdf Pandas DataFrame from the .dates and .values lists.
|
426
430
|
|
427
431
|
Returns
|
428
432
|
-------
|
@@ -437,7 +441,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
437
441
|
)
|
438
442
|
dframe.index = [d.date() for d in DatetimeIndex(dframe.index)]
|
439
443
|
|
440
|
-
dframe.sort_index(
|
444
|
+
dframe = dframe.sort_index()
|
441
445
|
self.tsdf = dframe
|
442
446
|
|
443
447
|
return self
|
@@ -448,7 +452,8 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
448
452
|
from_dt: Optional[dt.date] = None,
|
449
453
|
to_dt: Optional[dt.date] = None,
|
450
454
|
) -> tuple[dt.date, dt.date]:
|
451
|
-
"""
|
455
|
+
"""
|
456
|
+
Create user defined date range.
|
452
457
|
|
453
458
|
Parameters
|
454
459
|
----------
|
@@ -466,12 +471,15 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
466
471
|
Start and end date of the chosen date range
|
467
472
|
"""
|
468
473
|
return get_calc_range(
|
469
|
-
data=self.tsdf,
|
474
|
+
data=self.tsdf,
|
475
|
+
months_offset=months_offset,
|
476
|
+
from_dt=from_dt,
|
477
|
+
to_dt=to_dt,
|
470
478
|
)
|
471
479
|
|
472
480
|
def align_index_to_local_cdays(self: TypeOpenTimeSeries) -> TypeOpenTimeSeries:
|
473
|
-
"""
|
474
|
-
local calendar business days
|
481
|
+
"""
|
482
|
+
Align the index .tsdf with local calendar business days.
|
475
483
|
|
476
484
|
Returns
|
477
485
|
-------
|
@@ -479,14 +487,17 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
479
487
|
An OpenTimeSeries object
|
480
488
|
"""
|
481
489
|
self.tsdf = align_dataframe_to_local_cdays(
|
482
|
-
data=self.tsdf,
|
490
|
+
data=self.tsdf,
|
491
|
+
countries=self.countries,
|
483
492
|
)
|
484
493
|
return self
|
485
494
|
|
486
495
|
def all_properties(
|
487
|
-
self: TypeOpenTimeSeries,
|
496
|
+
self: TypeOpenTimeSeries,
|
497
|
+
properties: Optional[list[LiteralSeriesProps]] = None,
|
488
498
|
) -> DataFrame:
|
489
|
-
"""
|
499
|
+
"""
|
500
|
+
Calculate chosen properties.
|
490
501
|
|
491
502
|
Parameters
|
492
503
|
----------
|
@@ -498,10 +509,10 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
498
509
|
pandas.DataFrame
|
499
510
|
Properties of the OpenTimeSeries
|
500
511
|
"""
|
501
|
-
|
502
512
|
if not properties:
|
503
513
|
properties = cast(
|
504
|
-
list[LiteralSeriesProps],
|
514
|
+
list[LiteralSeriesProps],
|
515
|
+
OpenTimeSeriesPropertiesList.allowed_strings,
|
505
516
|
)
|
506
517
|
|
507
518
|
props = OpenTimeSeriesPropertiesList(*properties)
|
@@ -512,24 +523,26 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
512
523
|
@property
|
513
524
|
def worst_month(self: TypeOpenTimeSeries) -> float:
|
514
525
|
"""
|
526
|
+
Most negative month.
|
527
|
+
|
515
528
|
Returns
|
516
529
|
-------
|
517
530
|
float
|
518
531
|
Most negative month
|
519
532
|
"""
|
520
|
-
|
521
533
|
resdf = self.tsdf.copy()
|
522
534
|
resdf.index = DatetimeIndex(resdf.index)
|
523
535
|
return float((resdf.resample("BM").last().pct_change().min()).iloc[0])
|
524
536
|
|
525
537
|
def value_to_ret(self: TypeOpenTimeSeries) -> TypeOpenTimeSeries:
|
526
538
|
"""
|
539
|
+
Convert series of values into series of returns.
|
540
|
+
|
527
541
|
Returns
|
528
542
|
-------
|
529
543
|
OpenTimeSeries
|
530
544
|
The returns of the values in the series
|
531
545
|
"""
|
532
|
-
|
533
546
|
self.tsdf = self.tsdf.pct_change()
|
534
547
|
self.tsdf.iloc[0] = 0
|
535
548
|
self.valuetype = ValueType.RTRN
|
@@ -537,9 +550,11 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
537
550
|
return self
|
538
551
|
|
539
552
|
def value_to_diff(
|
540
|
-
self: TypeOpenTimeSeries,
|
553
|
+
self: TypeOpenTimeSeries,
|
554
|
+
periods: int = 1,
|
541
555
|
) -> TypeOpenTimeSeries:
|
542
|
-
"""
|
556
|
+
"""
|
557
|
+
Convert series of values to series of their period differences.
|
543
558
|
|
544
559
|
Parameters
|
545
560
|
----------
|
@@ -552,7 +567,6 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
552
567
|
OpenTimeSeries
|
553
568
|
An OpenTimeSeries object
|
554
569
|
"""
|
555
|
-
|
556
570
|
self.tsdf = self.tsdf.diff(periods=periods)
|
557
571
|
self.tsdf.iloc[0] = 0
|
558
572
|
self.valuetype = ValueType.RTRN
|
@@ -560,7 +574,8 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
560
574
|
return self
|
561
575
|
|
562
576
|
def to_cumret(self: TypeOpenTimeSeries) -> TypeOpenTimeSeries:
|
563
|
-
"""
|
577
|
+
"""
|
578
|
+
Convert series of returns into cumulative series of values.
|
564
579
|
|
565
580
|
Returns
|
566
581
|
-------
|
@@ -569,7 +584,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
569
584
|
"""
|
570
585
|
if not any(
|
571
586
|
x == ValueType.RTRN
|
572
|
-
for x in cast(MultiIndex, self.tsdf.columns).get_level_values(1).
|
587
|
+
for x in cast(MultiIndex, self.tsdf.columns).get_level_values(1).to_numpy()
|
573
588
|
):
|
574
589
|
self.value_to_ret()
|
575
590
|
|
@@ -580,9 +595,12 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
580
595
|
return self
|
581
596
|
|
582
597
|
def from_1d_rate_to_cumret(
|
583
|
-
self: TypeOpenTimeSeries,
|
598
|
+
self: TypeOpenTimeSeries,
|
599
|
+
days_in_year: int = 365,
|
600
|
+
divider: float = 1.0,
|
584
601
|
) -> TypeOpenTimeSeries:
|
585
|
-
"""
|
602
|
+
"""
|
603
|
+
Convert series of 1-day rates into series of cumulative values.
|
586
604
|
|
587
605
|
Parameters
|
588
606
|
----------
|
@@ -596,7 +614,6 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
596
614
|
OpenTimeSeries
|
597
615
|
An OpenTimeSeries object
|
598
616
|
"""
|
599
|
-
|
600
617
|
arr = array(self.values) / divider
|
601
618
|
|
602
619
|
deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]])
|
@@ -610,9 +627,11 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
610
627
|
return self
|
611
628
|
|
612
629
|
def resample(
|
613
|
-
self: TypeOpenTimeSeries,
|
630
|
+
self: TypeOpenTimeSeries,
|
631
|
+
freq: Union[LiteralBizDayFreq, str] = "BM",
|
614
632
|
) -> TypeOpenTimeSeries:
|
615
|
-
"""
|
633
|
+
"""
|
634
|
+
Resamples the timeseries frequency.
|
616
635
|
|
617
636
|
Parameters
|
618
637
|
----------
|
@@ -625,7 +644,6 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
625
644
|
OpenTimeSeries
|
626
645
|
An OpenTimeSeries object
|
627
646
|
"""
|
628
|
-
|
629
647
|
self.tsdf.index = DatetimeIndex(self.tsdf.index)
|
630
648
|
self.tsdf = self.tsdf.resample(freq).last()
|
631
649
|
self.tsdf.index = [d.date() for d in DatetimeIndex(self.tsdf.index)]
|
@@ -637,9 +655,10 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
637
655
|
convention: LiteralPandasResampleConvention = "end",
|
638
656
|
method: LiteralPandasReindexMethod = "nearest",
|
639
657
|
) -> TypeOpenTimeSeries:
|
640
|
-
"""
|
641
|
-
|
642
|
-
|
658
|
+
"""
|
659
|
+
Resamples timeseries frequency to the business calendar month end dates.
|
660
|
+
|
661
|
+
Stubs left in place. Stubs will be aligned to the shortest stub.
|
643
662
|
|
644
663
|
Parameters
|
645
664
|
----------
|
@@ -655,7 +674,6 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
655
674
|
OpenTimeSeries
|
656
675
|
An OpenTimeSeries object
|
657
676
|
"""
|
658
|
-
|
659
677
|
head = self.tsdf.iloc[0].copy()
|
660
678
|
tail = self.tsdf.iloc[-1].copy()
|
661
679
|
dates = do_resample_to_business_period_ends(
|
@@ -671,13 +689,14 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
671
689
|
|
672
690
|
def drawdown_details(self: TypeOpenTimeSeries) -> DataFrame:
|
673
691
|
"""
|
692
|
+
Details of the maximum drawdown.
|
693
|
+
|
674
694
|
Returns
|
675
695
|
-------
|
676
696
|
Pandas.DataFrame
|
677
697
|
Calculates 'Max Drawdown', 'Start of drawdown', 'Date of bottom',
|
678
698
|
'Days from start to bottom', & 'Average fall per day'
|
679
699
|
"""
|
680
|
-
|
681
700
|
dddf = self.tsdf.copy()
|
682
701
|
dddf.index = DatetimeIndex(dddf.index)
|
683
702
|
return drawdown_details(dddf).to_frame()
|
@@ -692,8 +711,10 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
692
711
|
to_date: Optional[dt.date] = None,
|
693
712
|
periods_in_a_year_fixed: Optional[int] = None,
|
694
713
|
) -> Series:
|
695
|
-
"""
|
696
|
-
|
714
|
+
"""
|
715
|
+
Exponentially Weighted Moving Average Model for Volatility.
|
716
|
+
|
717
|
+
https://www.investopedia.com/articles/07/ewma.asp.
|
697
718
|
|
698
719
|
Parameters
|
699
720
|
----------
|
@@ -718,13 +739,13 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
718
739
|
Pandas.DataFrame
|
719
740
|
Series EWMA volatility
|
720
741
|
"""
|
721
|
-
|
722
742
|
earlier, later = self.calc_range(months_from_last, from_date, to_date)
|
723
743
|
if periods_in_a_year_fixed:
|
724
744
|
time_factor = float(periods_in_a_year_fixed)
|
725
745
|
else:
|
726
746
|
how_many = self.tsdf.loc[
|
727
|
-
cast(int, earlier) : cast(int, later),
|
747
|
+
cast(int, earlier) : cast(int, later),
|
748
|
+
self.tsdf.columns.to_numpy()[0],
|
728
749
|
].count()
|
729
750
|
fraction = (later - earlier).days / 365.25
|
730
751
|
time_factor = how_many / fraction
|
@@ -732,14 +753,14 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
732
753
|
data = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy()
|
733
754
|
|
734
755
|
data[self.label, "Returns"] = (
|
735
|
-
data.loc[:, self.tsdf.columns.
|
756
|
+
data.loc[:, self.tsdf.columns.to_numpy()[0]].apply(log).diff()
|
736
757
|
)
|
737
758
|
|
738
759
|
rawdata = [
|
739
760
|
data.loc[:, (self.label, "Returns")]
|
740
761
|
.iloc[1:day_chunk]
|
741
762
|
.std(ddof=dlta_degr_freedms)
|
742
|
-
* sqrt(time_factor)
|
763
|
+
* sqrt(time_factor),
|
743
764
|
]
|
744
765
|
|
745
766
|
for item in data.loc[:, (self.label, "Returns")].iloc[1:]:
|
@@ -750,7 +771,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
750
771
|
prev_ewma=previous,
|
751
772
|
time_factor=time_factor,
|
752
773
|
lmbda=lmbda,
|
753
|
-
)
|
774
|
+
),
|
754
775
|
)
|
755
776
|
|
756
777
|
data.loc[:, (self.label, ValueType.EWMA)] = rawdata
|
@@ -758,9 +779,12 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
758
779
|
return data.loc[:, (self.label, ValueType.EWMA)]
|
759
780
|
|
760
781
|
def running_adjustment(
|
761
|
-
self: TypeOpenTimeSeries,
|
782
|
+
self: TypeOpenTimeSeries,
|
783
|
+
adjustment: float,
|
784
|
+
days_in_year: int = 365,
|
762
785
|
) -> TypeOpenTimeSeries:
|
763
|
-
"""
|
786
|
+
"""
|
787
|
+
Add (+) or subtract (-) a fee from the timeseries return.
|
764
788
|
|
765
789
|
Parameters
|
766
790
|
----------
|
@@ -778,7 +802,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
778
802
|
values: list[float]
|
779
803
|
if any(
|
780
804
|
x == ValueType.RTRN
|
781
|
-
for x in cast(MultiIndex, self.tsdf.columns).get_level_values(1).
|
805
|
+
for x in cast(MultiIndex, self.tsdf.columns).get_level_values(1).to_numpy()
|
782
806
|
):
|
783
807
|
ra_df = self.tsdf.copy()
|
784
808
|
values = [1.0]
|
@@ -787,7 +811,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
787
811
|
values = [self.tsdf.iloc[0, 0]]
|
788
812
|
ra_df = self.tsdf.pct_change().copy()
|
789
813
|
returns_input = False
|
790
|
-
ra_df.dropna(
|
814
|
+
ra_df = ra_df.dropna()
|
791
815
|
|
792
816
|
prev = self.first_idx
|
793
817
|
idx: dt.date
|
@@ -797,7 +821,7 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
797
821
|
dates.append(idx)
|
798
822
|
values.append(
|
799
823
|
values[-1]
|
800
|
-
* (1 + row.iloc[0] + adjustment * (idx - prev).days / days_in_year)
|
824
|
+
* (1 + row.iloc[0] + adjustment * (idx - prev).days / days_in_year),
|
801
825
|
)
|
802
826
|
prev = idx
|
803
827
|
self.tsdf = DataFrame(data=values, index=dates)
|
@@ -814,8 +838,8 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
814
838
|
lvl_one: Optional[ValueType] = None,
|
815
839
|
delete_lvl_one: bool = False,
|
816
840
|
) -> TypeOpenTimeSeries:
|
817
|
-
"""
|
818
|
-
|
841
|
+
"""
|
842
|
+
Set the column labels of the .tsdf Pandas Dataframe.
|
819
843
|
|
820
844
|
Parameters
|
821
845
|
----------
|
@@ -831,10 +855,9 @@ class OpenTimeSeries(BaseModel, CommonModel):
|
|
831
855
|
OpenTimeSeries
|
832
856
|
An OpenTimeSeries object
|
833
857
|
"""
|
834
|
-
|
835
858
|
if lvl_zero is None and lvl_one is None:
|
836
859
|
self.tsdf.columns = MultiIndex.from_arrays(
|
837
|
-
[[self.label], [self.valuetype]]
|
860
|
+
[[self.label], [self.valuetype]],
|
838
861
|
)
|
839
862
|
elif lvl_zero is not None and lvl_one is None:
|
840
863
|
self.tsdf.columns = MultiIndex.from_arrays([[lvl_zero], [self.valuetype]])
|
@@ -855,7 +878,8 @@ def timeseries_chain(
|
|
855
878
|
back: TypeOpenTimeSeries,
|
856
879
|
old_fee: float = 0.0,
|
857
880
|
) -> Union[TypeOpenTimeSeries, OpenTimeSeries]:
|
858
|
-
"""
|
881
|
+
"""
|
882
|
+
Chain two timeseries together.
|
859
883
|
|
860
884
|
Parameters
|
861
885
|
----------
|
@@ -868,7 +892,7 @@ def timeseries_chain(
|
|
868
892
|
|
869
893
|
Returns
|
870
894
|
-------
|
871
|
-
TypeOpenTimeSeries
|
895
|
+
Union[TypeOpenTimeSeries, OpenTimeSeries]
|
872
896
|
An OpenTimeSeries object or a subclass thereof
|
873
897
|
"""
|
874
898
|
old = front.from_deepcopy()
|
@@ -889,17 +913,17 @@ def timeseries_chain(
|
|
889
913
|
raise ValueError("Failed to find a matching date between series")
|
890
914
|
|
891
915
|
dates: list[str] = [x.strftime("%Y-%m-%d") for x in olddf.index if x < first]
|
892
|
-
values = array([x[0] for x in old.tsdf.
|
916
|
+
values = array([x[0] for x in old.tsdf.to_numpy()][: len(dates)])
|
893
917
|
values = cast(
|
894
918
|
NDArray[float64],
|
895
919
|
list(values * new.tsdf.iloc[:, 0].loc[first] / olddf.iloc[:, 0].loc[first]),
|
896
920
|
)
|
897
921
|
|
898
922
|
dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
|
899
|
-
values += [x[0] for x in new.tsdf.
|
923
|
+
values += [x[0] for x in new.tsdf.to_numpy()]
|
900
924
|
|
901
|
-
if back.__class__.__subclasscheck__(
|
902
|
-
OpenTimeSeries
|
925
|
+
if back.__class__.__subclasscheck__(
|
926
|
+
OpenTimeSeries,
|
903
927
|
):
|
904
928
|
return OpenTimeSeries(
|
905
929
|
timeseriesId=new.timeseriesId,
|
@@ -937,8 +961,9 @@ def timeseries_chain(
|
|
937
961
|
)
|
938
962
|
|
939
963
|
|
940
|
-
def check_if_none(item: Any) -> bool:
|
941
|
-
"""
|
964
|
+
def check_if_none(item: Any) -> bool: # noqa: ANN401
|
965
|
+
"""
|
966
|
+
Check if a variable is None or equivalent.
|
942
967
|
|
943
968
|
Parameters
|
944
969
|
----------
|