openseries 1.7.8__py3-none-any.whl → 1.8.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openseries/__init__.py +2 -1
- openseries/_common_model.py +107 -82
- openseries/_risk.py +3 -3
- openseries/datefixer.py +12 -6
- openseries/frame.py +93 -75
- openseries/load_plotly.py +5 -3
- openseries/{types.py → owntypes.py} +65 -4
- openseries/plotly_layouts.json +3 -3
- openseries/portfoliotools.py +29 -23
- openseries/series.py +45 -55
- openseries/simulation.py +6 -5
- openseries-1.8.1.dist-info/LICENSE.md +27 -0
- {openseries-1.7.8.dist-info → openseries-1.8.1.dist-info}/METADATA +55 -29
- openseries-1.8.1.dist-info/RECORD +16 -0
- {openseries-1.7.8.dist-info → openseries-1.8.1.dist-info}/WHEEL +1 -1
- openseries-1.7.8.dist-info/LICENSE.md +0 -27
- openseries-1.7.8.dist-info/RECORD +0 -16
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 TYPE_CHECKING,
|
8
|
+
from typing import TYPE_CHECKING, cast
|
9
9
|
|
10
10
|
from numpy import (
|
11
11
|
append,
|
@@ -21,7 +21,6 @@ from numpy import (
|
|
21
21
|
from numpy import (
|
22
22
|
sum as npsum,
|
23
23
|
)
|
24
|
-
from numpy.typing import NDArray
|
25
24
|
from pandas import (
|
26
25
|
DataFrame,
|
27
26
|
Series,
|
@@ -33,19 +32,24 @@ from plotly.offline import plot # type: ignore[import-untyped,unused-ignore]
|
|
33
32
|
from scipy.optimize import minimize # type: ignore[import-untyped,unused-ignore]
|
34
33
|
|
35
34
|
from .load_plotly import load_plotly_dict
|
36
|
-
from .
|
37
|
-
|
38
|
-
# noinspection PyProtectedMember
|
39
|
-
from .simulation import _random_generator
|
40
|
-
from .types import (
|
35
|
+
from .owntypes import (
|
36
|
+
AtLeastOneFrameError,
|
41
37
|
LiteralLinePlotMode,
|
42
38
|
LiteralMinimizeMethods,
|
43
39
|
LiteralPlotlyJSlib,
|
44
40
|
LiteralPlotlyOutput,
|
41
|
+
MixedValuetypesError,
|
45
42
|
ValueType,
|
46
43
|
)
|
44
|
+
from .series import OpenTimeSeries
|
45
|
+
|
46
|
+
# noinspection PyProtectedMember
|
47
|
+
from .simulation import _random_generator
|
47
48
|
|
48
49
|
if TYPE_CHECKING: # pragma: no cover
|
50
|
+
from collections.abc import Callable
|
51
|
+
|
52
|
+
from numpy.typing import NDArray
|
49
53
|
from pydantic import DirectoryPath
|
50
54
|
|
51
55
|
from .frame import OpenFrame
|
@@ -91,7 +95,7 @@ def simulate_portfolios(
|
|
91
95
|
log_ret = copi.tsdf.copy()
|
92
96
|
else:
|
93
97
|
msg = "Mix of series types will give inconsistent results"
|
94
|
-
raise
|
98
|
+
raise MixedValuetypesError(msg)
|
95
99
|
|
96
100
|
log_ret.columns = log_ret.columns.droplevel(level=1)
|
97
101
|
|
@@ -127,7 +131,7 @@ def simulate_portfolios(
|
|
127
131
|
|
128
132
|
|
129
133
|
# noinspection PyUnusedLocal
|
130
|
-
def efficient_frontier(
|
134
|
+
def efficient_frontier(
|
131
135
|
eframe: OpenFrame,
|
132
136
|
num_ports: int = 5000,
|
133
137
|
seed: int = 71,
|
@@ -175,7 +179,7 @@ def efficient_frontier( # noqa: C901
|
|
175
179
|
log_ret = copi.tsdf.copy()
|
176
180
|
else:
|
177
181
|
msg = "Mix of series types will give inconsistent results"
|
178
|
-
raise
|
182
|
+
raise MixedValuetypesError(msg)
|
179
183
|
|
180
184
|
log_ret.columns = log_ret.columns.droplevel(level=1)
|
181
185
|
|
@@ -199,7 +203,7 @@ def efficient_frontier( # noqa: C901
|
|
199
203
|
ret = npsum(lg_ret.mean() * weights) * per_in_yr
|
200
204
|
volatility = sqrt(weights.T @ (lg_ret.cov() * per_in_yr @ weights))
|
201
205
|
sr = ret / volatility
|
202
|
-
return cast(NDArray[float64], array([ret, volatility, sr]))
|
206
|
+
return cast("NDArray[float64]", array([ret, volatility, sr]))
|
203
207
|
|
204
208
|
def _diff_return(
|
205
209
|
lg_ret: DataFrame,
|
@@ -208,14 +212,14 @@ def efficient_frontier( # noqa: C901
|
|
208
212
|
poss_return: float,
|
209
213
|
) -> float64:
|
210
214
|
return cast(
|
211
|
-
float64,
|
215
|
+
"float64",
|
212
216
|
_get_ret_vol_sr(lg_ret=lg_ret, weights=weights, per_in_yr=per_in_yr)[0]
|
213
217
|
- poss_return,
|
214
218
|
)
|
215
219
|
|
216
220
|
def _neg_sharpe(weights: NDArray[float64]) -> float64:
|
217
221
|
return cast(
|
218
|
-
float64,
|
222
|
+
"float64",
|
219
223
|
_get_ret_vol_sr(
|
220
224
|
lg_ret=log_ret,
|
221
225
|
weights=weights,
|
@@ -228,7 +232,7 @@ def efficient_frontier( # noqa: C901
|
|
228
232
|
weights: NDArray[float64],
|
229
233
|
) -> float64:
|
230
234
|
return cast(
|
231
|
-
float64,
|
235
|
+
"float64",
|
232
236
|
_get_ret_vol_sr(
|
233
237
|
lg_ret=log_ret,
|
234
238
|
weights=weights,
|
@@ -261,7 +265,7 @@ def efficient_frontier( # noqa: C901
|
|
261
265
|
|
262
266
|
for possible_return in frontier_y:
|
263
267
|
cons = cast(
|
264
|
-
dict[str, str | Callable[[float, NDArray[float64]], float64]],
|
268
|
+
"dict[str, str | Callable[[float, NDArray[float64]], float64]]",
|
265
269
|
(
|
266
270
|
{"type": "eq", "fun": _check_sum},
|
267
271
|
{
|
@@ -413,14 +417,16 @@ def prepare_plot_data(
|
|
413
417
|
[
|
414
418
|
f"{wgt:.1%} {nm}"
|
415
419
|
for wgt, nm in zip(
|
416
|
-
cast(list[float], assets.weights),
|
420
|
+
cast("list[float]", assets.weights),
|
417
421
|
assets.columns_lvl_zero,
|
422
|
+
strict=True,
|
418
423
|
)
|
419
424
|
],
|
420
425
|
)
|
421
426
|
|
422
427
|
opt_text_list = [
|
423
|
-
f"{wgt:.1%} {nm}"
|
428
|
+
f"{wgt:.1%} {nm}"
|
429
|
+
for wgt, nm in zip(optimized[3:], assets.columns_lvl_zero, strict=True)
|
424
430
|
]
|
425
431
|
opt_text = "<br><br>Weights:<br>" + "<br>".join(opt_text_list)
|
426
432
|
vol: Series[float] = assets.vol
|
@@ -442,7 +448,7 @@ def prepare_plot_data(
|
|
442
448
|
return plotframe
|
443
449
|
|
444
450
|
|
445
|
-
def sharpeplot(
|
451
|
+
def sharpeplot(
|
446
452
|
sim_frame: DataFrame | None = None,
|
447
453
|
line_frame: DataFrame | None = None,
|
448
454
|
point_frame: DataFrame | None = None,
|
@@ -511,7 +517,7 @@ def sharpeplot( # noqa: C901
|
|
511
517
|
|
512
518
|
if sim_frame is None and line_frame is None and point_frame is None:
|
513
519
|
msg = "One of sim_frame, line_frame or point_frame must be provided."
|
514
|
-
raise
|
520
|
+
raise AtLeastOneFrameError(msg)
|
515
521
|
|
516
522
|
if sim_frame is not None:
|
517
523
|
returns.extend(list(sim_frame.loc[:, "ret"]))
|
@@ -549,10 +555,10 @@ def sharpeplot( # noqa: C901
|
|
549
555
|
|
550
556
|
if point_frame is not None:
|
551
557
|
colorway = cast(
|
552
|
-
dict[str, str | int | float | bool | list[str]],
|
558
|
+
"dict[str, str | int | float | bool | list[str]]",
|
553
559
|
fig["layout"],
|
554
560
|
).get("colorway")[: len(point_frame.columns)]
|
555
|
-
for col, clr in zip(point_frame.columns, colorway):
|
561
|
+
for col, clr in zip(point_frame.columns, colorway, strict=True):
|
556
562
|
returns.extend([point_frame.loc["ret", col]])
|
557
563
|
risk.extend([point_frame.loc["stdev", col]])
|
558
564
|
figure.add_scatter(
|
@@ -597,7 +603,7 @@ def sharpeplot( # noqa: C901
|
|
597
603
|
auto_open=auto_open,
|
598
604
|
auto_play=False,
|
599
605
|
link_text="",
|
600
|
-
include_plotlyjs=cast(bool, include_plotlyjs),
|
606
|
+
include_plotlyjs=cast("bool", include_plotlyjs),
|
601
607
|
config=fig["config"],
|
602
608
|
output_type=output_type,
|
603
609
|
)
|
@@ -608,7 +614,7 @@ def sharpeplot( # noqa: C901
|
|
608
614
|
fig=figure,
|
609
615
|
config=fig["config"],
|
610
616
|
auto_play=False,
|
611
|
-
include_plotlyjs=cast(bool, include_plotlyjs),
|
617
|
+
include_plotlyjs=cast("bool", include_plotlyjs),
|
612
618
|
full_html=False,
|
613
619
|
div_id=div_id,
|
614
620
|
)
|
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,31 +30,35 @@ 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
|
34
|
-
from .
|
36
|
+
from .owntypes import (
|
35
37
|
Countries,
|
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
|
|
@@ -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
|
@@ -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
|
@@ -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,13 @@ 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
269
|
valuetype = dframe.columns.get_level_values(1).to_numpy()[
|
263
270
|
column_nmbr
|
264
271
|
]
|
265
272
|
else:
|
266
|
-
label = cast(MultiIndex, dframe.columns).to_numpy()[column_nmbr]
|
273
|
+
label = cast("MultiIndex", dframe.columns).to_numpy()[column_nmbr]
|
267
274
|
else:
|
268
275
|
raise TypeError(msg)
|
269
276
|
|
@@ -289,7 +296,7 @@ class OpenTimeSeries(_CommonModel):
|
|
289
296
|
|
290
297
|
@classmethod
|
291
298
|
def from_fixed_rate(
|
292
|
-
cls
|
299
|
+
cls,
|
293
300
|
rate: float,
|
294
301
|
d_range: DatetimeIndex | None = None,
|
295
302
|
days: int | None = None,
|
@@ -299,7 +306,7 @@ class OpenTimeSeries(_CommonModel):
|
|
299
306
|
baseccy: CurrencyStringType = "SEK",
|
300
307
|
*,
|
301
308
|
local_ccy: bool = True,
|
302
|
-
) ->
|
309
|
+
) -> Self:
|
303
310
|
"""Create series from values accruing with a given fixed rate return.
|
304
311
|
|
305
312
|
Providing a date_range of type Pandas DatetimeIndex takes priority over
|
@@ -338,7 +345,7 @@ class OpenTimeSeries(_CommonModel):
|
|
338
345
|
)
|
339
346
|
elif not isinstance(d_range, Iterable) and not all([days, end_dt]):
|
340
347
|
msg = "If d_range is not provided both days and end_dt must be."
|
341
|
-
raise
|
348
|
+
raise IncorrectArgumentComboError(msg)
|
342
349
|
|
343
350
|
deltas = array(
|
344
351
|
[i.days for i in DatetimeIndex(d_range)[1:] - DatetimeIndex(d_range)[:-1]], # type: ignore[arg-type]
|
@@ -413,7 +420,7 @@ class OpenTimeSeries(_CommonModel):
|
|
413
420
|
"""
|
414
421
|
if not properties:
|
415
422
|
properties = cast(
|
416
|
-
list[LiteralSeriesProps],
|
423
|
+
"list[LiteralSeriesProps]",
|
417
424
|
OpenTimeSeriesPropertiesList.allowed_strings,
|
418
425
|
)
|
419
426
|
|
@@ -435,7 +442,9 @@ class OpenTimeSeries(_CommonModel):
|
|
435
442
|
returns.iloc[0] = 0
|
436
443
|
self.valuetype = ValueType.RTRN
|
437
444
|
arrays = [[self.label], [self.valuetype]]
|
438
|
-
returns.columns = MultiIndex.from_arrays(
|
445
|
+
returns.columns = MultiIndex.from_arrays(
|
446
|
+
arrays=arrays # type: ignore[arg-type,unused-ignore]
|
447
|
+
)
|
439
448
|
self.tsdf = returns.copy()
|
440
449
|
return self
|
441
450
|
|
@@ -513,7 +522,9 @@ class OpenTimeSeries(_CommonModel):
|
|
513
522
|
|
514
523
|
deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]])
|
515
524
|
# noinspection PyTypeChecker
|
516
|
-
arr = cumprod(
|
525
|
+
arr = cumprod( # type: ignore[assignment,unused-ignore]
|
526
|
+
a=insert(arr=1.0 + deltas * arr[:-1] / days_in_year, obj=0, values=1.0)
|
527
|
+
)
|
517
528
|
|
518
529
|
self.dates = [d.strftime("%Y-%m-%d") for d in self.tsdf.index]
|
519
530
|
self.values = list(arr)
|
@@ -622,26 +633,26 @@ class OpenTimeSeries(_CommonModel):
|
|
622
633
|
time_factor = float(periods_in_a_year_fixed)
|
623
634
|
else:
|
624
635
|
how_many = self.tsdf.loc[
|
625
|
-
cast(int, earlier) : cast(int, later),
|
636
|
+
cast("int", earlier) : cast("int", later),
|
626
637
|
self.tsdf.columns.to_numpy()[0],
|
627
638
|
].count()
|
628
639
|
fraction = (later - earlier).days / 365.25
|
629
640
|
time_factor = how_many / fraction
|
630
641
|
|
631
|
-
data = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy()
|
642
|
+
data = self.tsdf.loc[cast("int", earlier) : cast("int", later)].copy()
|
632
643
|
|
633
644
|
data[self.label, ValueType.RTRN] = (
|
634
645
|
data.loc[:, self.tsdf.columns.to_numpy()[0]].apply(log).diff()
|
635
646
|
)
|
636
647
|
|
637
648
|
rawdata = [
|
638
|
-
data.loc[:, cast(int, (self.label, ValueType.RTRN))]
|
649
|
+
data.loc[:, cast("int", (self.label, ValueType.RTRN))]
|
639
650
|
.iloc[1:day_chunk]
|
640
651
|
.std(ddof=dlta_degr_freedms)
|
641
652
|
* sqrt(time_factor),
|
642
653
|
]
|
643
654
|
|
644
|
-
for item in data.loc[:, cast(int, (self.label, ValueType.RTRN))].iloc[1:]:
|
655
|
+
for item in data.loc[:, cast("int", (self.label, ValueType.RTRN))].iloc[1:]:
|
645
656
|
prev = rawdata[-1]
|
646
657
|
rawdata.append(
|
647
658
|
sqrt(
|
@@ -682,7 +693,7 @@ class OpenTimeSeries(_CommonModel):
|
|
682
693
|
values = [1.0]
|
683
694
|
returns_input = True
|
684
695
|
else:
|
685
|
-
values = [cast(float, self.tsdf.iloc[0, 0])]
|
696
|
+
values = [cast("float", self.tsdf.iloc[0, 0])]
|
686
697
|
ra_df = self.tsdf.ffill().pct_change()
|
687
698
|
returns_input = False
|
688
699
|
ra_df = ra_df.dropna()
|
@@ -691,16 +702,16 @@ class OpenTimeSeries(_CommonModel):
|
|
691
702
|
dates: list[dt.date] = [prev]
|
692
703
|
|
693
704
|
for idx, row in ra_df.iterrows():
|
694
|
-
dates.append(cast(dt.date, idx))
|
705
|
+
dates.append(cast("dt.date", idx))
|
695
706
|
values.append(
|
696
707
|
values[-1]
|
697
708
|
* (
|
698
709
|
1
|
699
710
|
+ row.iloc[0]
|
700
|
-
+ adjustment * (cast(dt.date, idx) - prev).days / days_in_year
|
711
|
+
+ adjustment * (cast("dt.date", idx) - prev).days / days_in_year
|
701
712
|
),
|
702
713
|
)
|
703
|
-
prev = cast(dt.date, idx)
|
714
|
+
prev = cast("dt.date", idx)
|
704
715
|
self.tsdf = DataFrame(data=values, index=dates)
|
705
716
|
self.valuetype = ValueType.PRICE
|
706
717
|
self.tsdf.columns = MultiIndex.from_arrays(
|
@@ -750,7 +761,7 @@ class OpenTimeSeries(_CommonModel):
|
|
750
761
|
self.valuetype = lvl_one
|
751
762
|
else:
|
752
763
|
self.tsdf.columns = MultiIndex.from_arrays([[lvl_zero], [lvl_one]])
|
753
|
-
self.label, self.valuetype = lvl_zero, cast(ValueType, lvl_one)
|
764
|
+
self.label, self.valuetype = lvl_zero, cast("ValueType", lvl_one)
|
754
765
|
if delete_lvl_one:
|
755
766
|
self.tsdf.columns = self.tsdf.columns.droplevel(level=1)
|
756
767
|
return self
|
@@ -760,7 +771,7 @@ def timeseries_chain(
|
|
760
771
|
front: TypeOpenTimeSeries,
|
761
772
|
back: TypeOpenTimeSeries,
|
762
773
|
old_fee: float = 0.0,
|
763
|
-
) -> TypeOpenTimeSeries
|
774
|
+
) -> TypeOpenTimeSeries:
|
764
775
|
"""Chain two timeseries together.
|
765
776
|
|
766
777
|
The function assumes that the two series have at least one date in common.
|
@@ -776,7 +787,7 @@ def timeseries_chain(
|
|
776
787
|
|
777
788
|
Returns
|
778
789
|
-------
|
779
|
-
TypeOpenTimeSeries
|
790
|
+
TypeOpenTimeSeries
|
780
791
|
An OpenTimeSeries object or a subclass thereof
|
781
792
|
|
782
793
|
"""
|
@@ -788,14 +799,14 @@ def timeseries_chain(
|
|
788
799
|
|
789
800
|
if old.last_idx < first:
|
790
801
|
msg = "Timeseries dates must overlap to allow them to be chained."
|
791
|
-
raise
|
802
|
+
raise DateAlignmentError(msg)
|
792
803
|
|
793
804
|
while first not in old.tsdf.index:
|
794
805
|
idx += 1
|
795
806
|
first = new.tsdf.index[idx]
|
796
807
|
if first > old.tsdf.index[-1]:
|
797
808
|
msg = "Failed to find a matching date between series"
|
798
|
-
raise
|
809
|
+
raise DateAlignmentError(msg)
|
799
810
|
|
800
811
|
dates: list[str] = [x.strftime("%Y-%m-%d") for x in old.tsdf.index if x < first]
|
801
812
|
|
@@ -807,27 +818,6 @@ def timeseries_chain(
|
|
807
818
|
|
808
819
|
dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
|
809
820
|
|
810
|
-
# noinspection PyUnresolvedReferences
|
811
|
-
if back.__class__.__subclasscheck__(
|
812
|
-
OpenTimeSeries,
|
813
|
-
):
|
814
|
-
return OpenTimeSeries(
|
815
|
-
timeseries_id=new.timeseries_id,
|
816
|
-
instrument_id=new.instrument_id,
|
817
|
-
currency=new.currency,
|
818
|
-
dates=dates,
|
819
|
-
name=new.name,
|
820
|
-
label=new.name,
|
821
|
-
valuetype=new.valuetype,
|
822
|
-
values=list(values),
|
823
|
-
local_ccy=new.local_ccy,
|
824
|
-
tsdf=DataFrame(
|
825
|
-
data=values,
|
826
|
-
index=[d.date() for d in DatetimeIndex(dates)],
|
827
|
-
columns=[[new.label], [new.valuetype]],
|
828
|
-
dtype="float64",
|
829
|
-
),
|
830
|
-
)
|
831
821
|
return back.__class__(
|
832
822
|
timeseries_id=new.timeseries_id,
|
833
823
|
instrument_id=new.instrument_id,
|
@@ -862,7 +852,7 @@ def _check_if_none(item: Any) -> bool: # noqa: ANN401
|
|
862
852
|
|
863
853
|
"""
|
864
854
|
try:
|
865
|
-
return cast(bool, isnan(item))
|
855
|
+
return cast("bool", isnan(item))
|
866
856
|
except TypeError:
|
867
857
|
if item is None:
|
868
858
|
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
|
-
from .
|
28
|
+
from .owntypes import (
|
29
29
|
CountriesType,
|
30
30
|
DaysInYearType,
|
31
|
+
Self,
|
31
32
|
ValueType,
|
32
33
|
)
|
33
34
|
|
@@ -49,7 +50,7 @@ def _random_generator(seed: int | None) -> 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
|
|
@@ -121,7 +122,7 @@ class ReturnSimulation(BaseModel):
|
|
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],
|
@@ -138,7 +139,7 @@ class ReturnSimulation(BaseModel):
|
|
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)
|
@@ -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.
|