openseries 1.8.0__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 +2 -2
- openseries/datefixer.py +10 -4
- openseries/frame.py +89 -71
- openseries/load_plotly.py +4 -2
- openseries/owntypes.py +64 -3
- openseries/plotly_layouts.json +3 -3
- openseries/portfoliotools.py +19 -16
- openseries/series.py +41 -53
- openseries/simulation.py +5 -4
- openseries-1.8.1.dist-info/LICENSE.md +27 -0
- {openseries-1.8.0.dist-info → openseries-1.8.1.dist-info}/METADATA +41 -13
- openseries-1.8.1.dist-info/RECORD +16 -0
- {openseries-1.8.0.dist-info → openseries-1.8.1.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/portfoliotools.py
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
# mypy: disable-error-code="index,assignment"
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
-
from collections.abc import Callable
|
7
6
|
from inspect import stack
|
8
7
|
from pathlib import Path
|
9
8
|
from typing import TYPE_CHECKING, cast
|
@@ -22,7 +21,6 @@ from numpy import (
|
|
22
21
|
from numpy import (
|
23
22
|
sum as npsum,
|
24
23
|
)
|
25
|
-
from numpy.typing import NDArray
|
26
24
|
from pandas import (
|
27
25
|
DataFrame,
|
28
26
|
Series,
|
@@ -35,10 +33,12 @@ from scipy.optimize import minimize # type: ignore[import-untyped,unused-ignore
|
|
35
33
|
|
36
34
|
from .load_plotly import load_plotly_dict
|
37
35
|
from .owntypes import (
|
36
|
+
AtLeastOneFrameError,
|
38
37
|
LiteralLinePlotMode,
|
39
38
|
LiteralMinimizeMethods,
|
40
39
|
LiteralPlotlyJSlib,
|
41
40
|
LiteralPlotlyOutput,
|
41
|
+
MixedValuetypesError,
|
42
42
|
ValueType,
|
43
43
|
)
|
44
44
|
from .series import OpenTimeSeries
|
@@ -47,6 +47,9 @@ from .series import OpenTimeSeries
|
|
47
47
|
from .simulation import _random_generator
|
48
48
|
|
49
49
|
if TYPE_CHECKING: # pragma: no cover
|
50
|
+
from collections.abc import Callable
|
51
|
+
|
52
|
+
from numpy.typing import NDArray
|
50
53
|
from pydantic import DirectoryPath
|
51
54
|
|
52
55
|
from .frame import OpenFrame
|
@@ -92,7 +95,7 @@ def simulate_portfolios(
|
|
92
95
|
log_ret = copi.tsdf.copy()
|
93
96
|
else:
|
94
97
|
msg = "Mix of series types will give inconsistent results"
|
95
|
-
raise
|
98
|
+
raise MixedValuetypesError(msg)
|
96
99
|
|
97
100
|
log_ret.columns = log_ret.columns.droplevel(level=1)
|
98
101
|
|
@@ -128,7 +131,7 @@ def simulate_portfolios(
|
|
128
131
|
|
129
132
|
|
130
133
|
# noinspection PyUnusedLocal
|
131
|
-
def efficient_frontier(
|
134
|
+
def efficient_frontier(
|
132
135
|
eframe: OpenFrame,
|
133
136
|
num_ports: int = 5000,
|
134
137
|
seed: int = 71,
|
@@ -176,7 +179,7 @@ def efficient_frontier( # noqa: C901
|
|
176
179
|
log_ret = copi.tsdf.copy()
|
177
180
|
else:
|
178
181
|
msg = "Mix of series types will give inconsistent results"
|
179
|
-
raise
|
182
|
+
raise MixedValuetypesError(msg)
|
180
183
|
|
181
184
|
log_ret.columns = log_ret.columns.droplevel(level=1)
|
182
185
|
|
@@ -200,7 +203,7 @@ def efficient_frontier( # noqa: C901
|
|
200
203
|
ret = npsum(lg_ret.mean() * weights) * per_in_yr
|
201
204
|
volatility = sqrt(weights.T @ (lg_ret.cov() * per_in_yr @ weights))
|
202
205
|
sr = ret / volatility
|
203
|
-
return cast(NDArray[float64], array([ret, volatility, sr]))
|
206
|
+
return cast("NDArray[float64]", array([ret, volatility, sr]))
|
204
207
|
|
205
208
|
def _diff_return(
|
206
209
|
lg_ret: DataFrame,
|
@@ -209,14 +212,14 @@ def efficient_frontier( # noqa: C901
|
|
209
212
|
poss_return: float,
|
210
213
|
) -> float64:
|
211
214
|
return cast(
|
212
|
-
float64,
|
215
|
+
"float64",
|
213
216
|
_get_ret_vol_sr(lg_ret=lg_ret, weights=weights, per_in_yr=per_in_yr)[0]
|
214
217
|
- poss_return,
|
215
218
|
)
|
216
219
|
|
217
220
|
def _neg_sharpe(weights: NDArray[float64]) -> float64:
|
218
221
|
return cast(
|
219
|
-
float64,
|
222
|
+
"float64",
|
220
223
|
_get_ret_vol_sr(
|
221
224
|
lg_ret=log_ret,
|
222
225
|
weights=weights,
|
@@ -229,7 +232,7 @@ def efficient_frontier( # noqa: C901
|
|
229
232
|
weights: NDArray[float64],
|
230
233
|
) -> float64:
|
231
234
|
return cast(
|
232
|
-
float64,
|
235
|
+
"float64",
|
233
236
|
_get_ret_vol_sr(
|
234
237
|
lg_ret=log_ret,
|
235
238
|
weights=weights,
|
@@ -262,7 +265,7 @@ def efficient_frontier( # noqa: C901
|
|
262
265
|
|
263
266
|
for possible_return in frontier_y:
|
264
267
|
cons = cast(
|
265
|
-
dict[str, str | Callable[[float, NDArray[float64]], float64]],
|
268
|
+
"dict[str, str | Callable[[float, NDArray[float64]], float64]]",
|
266
269
|
(
|
267
270
|
{"type": "eq", "fun": _check_sum},
|
268
271
|
{
|
@@ -414,7 +417,7 @@ def prepare_plot_data(
|
|
414
417
|
[
|
415
418
|
f"{wgt:.1%} {nm}"
|
416
419
|
for wgt, nm in zip(
|
417
|
-
cast(list[float], assets.weights),
|
420
|
+
cast("list[float]", assets.weights),
|
418
421
|
assets.columns_lvl_zero,
|
419
422
|
strict=True,
|
420
423
|
)
|
@@ -445,7 +448,7 @@ def prepare_plot_data(
|
|
445
448
|
return plotframe
|
446
449
|
|
447
450
|
|
448
|
-
def sharpeplot(
|
451
|
+
def sharpeplot(
|
449
452
|
sim_frame: DataFrame | None = None,
|
450
453
|
line_frame: DataFrame | None = None,
|
451
454
|
point_frame: DataFrame | None = None,
|
@@ -514,7 +517,7 @@ def sharpeplot( # noqa: C901
|
|
514
517
|
|
515
518
|
if sim_frame is None and line_frame is None and point_frame is None:
|
516
519
|
msg = "One of sim_frame, line_frame or point_frame must be provided."
|
517
|
-
raise
|
520
|
+
raise AtLeastOneFrameError(msg)
|
518
521
|
|
519
522
|
if sim_frame is not None:
|
520
523
|
returns.extend(list(sim_frame.loc[:, "ret"]))
|
@@ -552,7 +555,7 @@ def sharpeplot( # noqa: C901
|
|
552
555
|
|
553
556
|
if point_frame is not None:
|
554
557
|
colorway = cast(
|
555
|
-
dict[str, str | int | float | bool | list[str]],
|
558
|
+
"dict[str, str | int | float | bool | list[str]]",
|
556
559
|
fig["layout"],
|
557
560
|
).get("colorway")[: len(point_frame.columns)]
|
558
561
|
for col, clr in zip(point_frame.columns, colorway, strict=True):
|
@@ -600,7 +603,7 @@ def sharpeplot( # noqa: C901
|
|
600
603
|
auto_open=auto_open,
|
601
604
|
auto_play=False,
|
602
605
|
link_text="",
|
603
|
-
include_plotlyjs=cast(bool, include_plotlyjs),
|
606
|
+
include_plotlyjs=cast("bool", include_plotlyjs),
|
604
607
|
config=fig["config"],
|
605
608
|
output_type=output_type,
|
606
609
|
)
|
@@ -611,7 +614,7 @@ def sharpeplot( # noqa: C901
|
|
611
614
|
fig=figure,
|
612
615
|
config=fig["config"],
|
613
616
|
auto_play=False,
|
614
|
-
include_plotlyjs=cast(bool, include_plotlyjs),
|
617
|
+
include_plotlyjs=cast("bool", include_plotlyjs),
|
615
618
|
full_html=False,
|
616
619
|
div_id=div_id,
|
617
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,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
|
|
@@ -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
|
|
@@ -624,26 +633,26 @@ class OpenTimeSeries(_CommonModel):
|
|
624
633
|
time_factor = float(periods_in_a_year_fixed)
|
625
634
|
else:
|
626
635
|
how_many = self.tsdf.loc[
|
627
|
-
cast(int, earlier) : cast(int, later),
|
636
|
+
cast("int", earlier) : cast("int", later),
|
628
637
|
self.tsdf.columns.to_numpy()[0],
|
629
638
|
].count()
|
630
639
|
fraction = (later - earlier).days / 365.25
|
631
640
|
time_factor = how_many / fraction
|
632
641
|
|
633
|
-
data = self.tsdf.loc[cast(int, earlier) : cast(int, later)].copy()
|
642
|
+
data = self.tsdf.loc[cast("int", earlier) : cast("int", later)].copy()
|
634
643
|
|
635
644
|
data[self.label, ValueType.RTRN] = (
|
636
645
|
data.loc[:, self.tsdf.columns.to_numpy()[0]].apply(log).diff()
|
637
646
|
)
|
638
647
|
|
639
648
|
rawdata = [
|
640
|
-
data.loc[:, cast(int, (self.label, ValueType.RTRN))]
|
649
|
+
data.loc[:, cast("int", (self.label, ValueType.RTRN))]
|
641
650
|
.iloc[1:day_chunk]
|
642
651
|
.std(ddof=dlta_degr_freedms)
|
643
652
|
* sqrt(time_factor),
|
644
653
|
]
|
645
654
|
|
646
|
-
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:]:
|
647
656
|
prev = rawdata[-1]
|
648
657
|
rawdata.append(
|
649
658
|
sqrt(
|
@@ -684,7 +693,7 @@ class OpenTimeSeries(_CommonModel):
|
|
684
693
|
values = [1.0]
|
685
694
|
returns_input = True
|
686
695
|
else:
|
687
|
-
values = [cast(float, self.tsdf.iloc[0, 0])]
|
696
|
+
values = [cast("float", self.tsdf.iloc[0, 0])]
|
688
697
|
ra_df = self.tsdf.ffill().pct_change()
|
689
698
|
returns_input = False
|
690
699
|
ra_df = ra_df.dropna()
|
@@ -693,16 +702,16 @@ class OpenTimeSeries(_CommonModel):
|
|
693
702
|
dates: list[dt.date] = [prev]
|
694
703
|
|
695
704
|
for idx, row in ra_df.iterrows():
|
696
|
-
dates.append(cast(dt.date, idx))
|
705
|
+
dates.append(cast("dt.date", idx))
|
697
706
|
values.append(
|
698
707
|
values[-1]
|
699
708
|
* (
|
700
709
|
1
|
701
710
|
+ row.iloc[0]
|
702
|
-
+ adjustment * (cast(dt.date, idx) - prev).days / days_in_year
|
711
|
+
+ adjustment * (cast("dt.date", idx) - prev).days / days_in_year
|
703
712
|
),
|
704
713
|
)
|
705
|
-
prev = cast(dt.date, idx)
|
714
|
+
prev = cast("dt.date", idx)
|
706
715
|
self.tsdf = DataFrame(data=values, index=dates)
|
707
716
|
self.valuetype = ValueType.PRICE
|
708
717
|
self.tsdf.columns = MultiIndex.from_arrays(
|
@@ -752,7 +761,7 @@ class OpenTimeSeries(_CommonModel):
|
|
752
761
|
self.valuetype = lvl_one
|
753
762
|
else:
|
754
763
|
self.tsdf.columns = MultiIndex.from_arrays([[lvl_zero], [lvl_one]])
|
755
|
-
self.label, self.valuetype = lvl_zero, cast(ValueType, lvl_one)
|
764
|
+
self.label, self.valuetype = lvl_zero, cast("ValueType", lvl_one)
|
756
765
|
if delete_lvl_one:
|
757
766
|
self.tsdf.columns = self.tsdf.columns.droplevel(level=1)
|
758
767
|
return self
|
@@ -762,7 +771,7 @@ def timeseries_chain(
|
|
762
771
|
front: TypeOpenTimeSeries,
|
763
772
|
back: TypeOpenTimeSeries,
|
764
773
|
old_fee: float = 0.0,
|
765
|
-
) -> TypeOpenTimeSeries
|
774
|
+
) -> TypeOpenTimeSeries:
|
766
775
|
"""Chain two timeseries together.
|
767
776
|
|
768
777
|
The function assumes that the two series have at least one date in common.
|
@@ -778,7 +787,7 @@ def timeseries_chain(
|
|
778
787
|
|
779
788
|
Returns
|
780
789
|
-------
|
781
|
-
TypeOpenTimeSeries
|
790
|
+
TypeOpenTimeSeries
|
782
791
|
An OpenTimeSeries object or a subclass thereof
|
783
792
|
|
784
793
|
"""
|
@@ -790,14 +799,14 @@ def timeseries_chain(
|
|
790
799
|
|
791
800
|
if old.last_idx < first:
|
792
801
|
msg = "Timeseries dates must overlap to allow them to be chained."
|
793
|
-
raise
|
802
|
+
raise DateAlignmentError(msg)
|
794
803
|
|
795
804
|
while first not in old.tsdf.index:
|
796
805
|
idx += 1
|
797
806
|
first = new.tsdf.index[idx]
|
798
807
|
if first > old.tsdf.index[-1]:
|
799
808
|
msg = "Failed to find a matching date between series"
|
800
|
-
raise
|
809
|
+
raise DateAlignmentError(msg)
|
801
810
|
|
802
811
|
dates: list[str] = [x.strftime("%Y-%m-%d") for x in old.tsdf.index if x < first]
|
803
812
|
|
@@ -809,27 +818,6 @@ def timeseries_chain(
|
|
809
818
|
|
810
819
|
dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
|
811
820
|
|
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
821
|
return back.__class__(
|
834
822
|
timeseries_id=new.timeseries_id,
|
835
823
|
instrument_id=new.instrument_id,
|
@@ -864,7 +852,7 @@ def _check_if_none(item: Any) -> bool: # noqa: ANN401
|
|
864
852
|
|
865
853
|
"""
|
866
854
|
try:
|
867
|
-
return cast(bool, isnan(item))
|
855
|
+
return cast("bool", isnan(item))
|
868
856
|
except TypeError:
|
869
857
|
if item is None:
|
870
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
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.
|
@@ -1,8 +1,34 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: openseries
|
3
|
-
Version: 1.8.
|
3
|
+
Version: 1.8.1
|
4
4
|
Summary: Tools for analyzing financial timeseries.
|
5
|
-
License: BSD
|
5
|
+
License: # BSD 3-Clause License
|
6
|
+
|
7
|
+
## Copyright (c) Captor Fund Management AB
|
8
|
+
|
9
|
+
Redistribution and use in source and binary forms, with or without modification, are
|
10
|
+
permitted provided that the following conditions are met:
|
11
|
+
|
12
|
+
1. Redistributions of source code must retain the above copyright notice, this list of
|
13
|
+
conditions and the following disclaimer.
|
14
|
+
|
15
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
16
|
+
of conditions and the following disclaimer in the documentation and/or other
|
17
|
+
materials provided with the distribution.
|
18
|
+
|
19
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be
|
20
|
+
used to endorse or promote products derived from this software without specific prior
|
21
|
+
written permission.
|
22
|
+
|
23
|
+
This software is provided by the copyright holders and contributors “as is” and any
|
24
|
+
express or implied warranties, including, but not limited to, the implied warranties of
|
25
|
+
merchantability and fitness for a particular purpose, are disclaimed. In no event shall
|
26
|
+
the copyright holder or contributors be liable for any direct, indirect, incidental,
|
27
|
+
special, exemplary, or consequential damages (including, but not limited to, procurement
|
28
|
+
of substitute goods or services; loss of use, data, or profits; or business interruption)
|
29
|
+
however caused and on any theory of liability, whether in contract, strict liability,
|
30
|
+
or tort (including negligence or otherwise) arising in any way out of the use of this
|
31
|
+
software, even if advised of the possibility of such damage.
|
6
32
|
Keywords: python,finance,fintech,data-science,timeseries,timeseries-data,timeseries-analysis,investment,investment-analysis,investing
|
7
33
|
Author: Martin Karrin
|
8
34
|
Author-email: martin.karrin@captor.se
|
@@ -22,18 +48,19 @@ Classifier: Operating System :: OS Independent
|
|
22
48
|
Classifier: Framework :: Pydantic
|
23
49
|
Requires-Dist: holidays (>=0.30,<1.0)
|
24
50
|
Requires-Dist: numpy (>=1.23.2,<3.0.0)
|
25
|
-
Requires-Dist: openpyxl (>=3.1.2,<
|
51
|
+
Requires-Dist: openpyxl (>=3.1.2,<5.0.0)
|
26
52
|
Requires-Dist: pandas (>=2.1.2,<3.0.0)
|
27
|
-
Requires-Dist: plotly (>=5.18.0,<
|
28
|
-
Requires-Dist: pyarrow (>=14.0.2,<
|
53
|
+
Requires-Dist: plotly (>=5.18.0,<7.0.0)
|
54
|
+
Requires-Dist: pyarrow (>=14.0.2,<21.0.0)
|
29
55
|
Requires-Dist: pydantic (>=2.5.2,<3.0.0)
|
30
|
-
Requires-Dist: python-dateutil (>=2.8.2,<
|
56
|
+
Requires-Dist: python-dateutil (>=2.8.2,<4.0.0)
|
31
57
|
Requires-Dist: requests (>=2.20.0,<3.0.0)
|
32
58
|
Requires-Dist: scipy (>=1.11.4,<2.0.0)
|
33
59
|
Requires-Dist: statsmodels (>=0.14.0,!=0.14.2,<1.0.0)
|
34
|
-
Project-URL: Documentation, https://github.com/CaptorAB/openseries
|
35
60
|
Project-URL: Homepage, https://github.com/CaptorAB/openseries
|
36
|
-
Project-URL:
|
61
|
+
Project-URL: Issue Tracker, https://github.com/CaptorAB/openseries/issues
|
62
|
+
Project-URL: Release Notes, https://github.com/CaptorAB/openseries/releases
|
63
|
+
Project-URL: Source, https://github.com/CaptorAB/openseries
|
37
64
|
Description-Content-Type: text/markdown
|
38
65
|
|
39
66
|
<a href="https://captor.se/"><img src="https://sales.captor.se/captor_logo_sv_1600_icketransparent.png" alt="Captor Fund Management AB" width="81" height="100" align="left" float="right"/></a><br/>
|
@@ -194,11 +221,12 @@ make lint
|
|
194
221
|
|
195
222
|
### On some files in the project
|
196
223
|
|
197
|
-
| File
|
198
|
-
|
199
|
-
| [series.py](https://github.com/CaptorAB/openseries/blob/master/openseries/series.py)
|
200
|
-
| [frame.py](https://github.com/CaptorAB/openseries/blob/master/openseries/frame.py)
|
201
|
-
| [
|
224
|
+
| File | Description |
|
225
|
+
|:-----------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
226
|
+
| [series.py](https://github.com/CaptorAB/openseries/blob/master/openseries/series.py) | Defines the class _OpenTimeSeries_ for managing and analyzing a single timeseries. The module also defines a function `timeseries_chain` that can be used to chain two timeseries objects together. |
|
227
|
+
| [frame.py](https://github.com/CaptorAB/openseries/blob/master/openseries/frame.py) | Defines the class _OpenFrame_ for managing a group of timeseries, and e.g. calculate a portfolio timeseries from a rebalancing strategy between timeseries. |
|
228
|
+
| [portfoliotools.py](https://github.com/CaptorAB/openseries/blob/master/openseries/portfoliotools.py) | Defines functions to simulate, optimize, and plot portfolios. |
|
229
|
+
| [simulation.py](https://github.com/CaptorAB/openseries/blob/master/openseries/simulation.py) | Defines the class _ReturnSimulation_ to create simulated financial timeseries. Used in the project's test suite |
|
202
230
|
|
203
231
|
### Class methods used to construct objects.
|
204
232
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
openseries/__init__.py,sha256=dKw_wEfgrCwwV1IRljesrtxjE9AVFwTyhE8k4CFIck8,1053
|
2
|
+
openseries/_common_model.py,sha256=x1CqaMjWo-nG-sRYxhKaIT_UBPGhfsNTGFF2x4tf3Gk,74451
|
3
|
+
openseries/_risk.py,sha256=5Lu4vK2oQlDdtv5WKD2ZTGrMIlsa0TiPxk6opgtnVlA,2084
|
4
|
+
openseries/datefixer.py,sha256=w_3lH9cmAm-Xl8t8-X3FdW22AA3zb5OqS0A4FrgS9vc,12479
|
5
|
+
openseries/frame.py,sha256=j5kD6s6kXLDjlSQEpKaFzT1bDN9Pmb2PbAF-hSf-NGc,55894
|
6
|
+
openseries/load_plotly.py,sha256=CzD-ZJNk6WGzIkzIHso4aULDpoWXsjH3xL7p7MP0lqY,1998
|
7
|
+
openseries/owntypes.py,sha256=aqidJ09w3autlgLD6fVEtANbgzh28K53gWDB6j_P4T4,9047
|
8
|
+
openseries/plotly_captor_logo.json,sha256=F5nhMzEyxKywtjvQqMTKgKRCJQYMDIiBgDSxdte8Clo,178
|
9
|
+
openseries/plotly_layouts.json,sha256=im8QZPRB--zV_rOu1OiSbvJPA52oJ6QZRFzkwB1C708,1430
|
10
|
+
openseries/portfoliotools.py,sha256=8XTjPMPHuazh20LeYj-y4hTZnGX8hrArLwS9Ot_MufM,19261
|
11
|
+
openseries/series.py,sha256=rwd-J_cD1lElC7W65kt2xGLPHFRfmWAwxe_KEJvi3Pc,27099
|
12
|
+
openseries/simulation.py,sha256=TLEsIZq8SzQ5BRQqjQMIeCY8ibPX5tkftnx4R-zLspQ,13905
|
13
|
+
openseries-1.8.1.dist-info/LICENSE.md,sha256=wNupG-KLsG0aTncb_SMNDh1ExtrKXlpxSJ6RC-g-SWs,1516
|
14
|
+
openseries-1.8.1.dist-info/METADATA,sha256=JcL-WQlrWAvJu2cfcxNwqDKsIrSgFrK6Uad2jqC-Xzs,45658
|
15
|
+
openseries-1.8.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
16
|
+
openseries-1.8.1.dist-info/RECORD,,
|
@@ -1,27 +0,0 @@
|
|
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
|
25
|
-
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
26
|
-
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|