openseries 1.9.5__py3-none-any.whl → 1.9.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openseries/_common_model.py +371 -442
- openseries/datefixer.py +4 -2
- openseries/frame.py +103 -113
- openseries/owntypes.py +48 -47
- openseries/portfoliotools.py +3 -4
- openseries/report.py +12 -14
- openseries/series.py +33 -19
- openseries/simulation.py +1 -1
- {openseries-1.9.5.dist-info → openseries-1.9.6.dist-info}/METADATA +4 -3
- openseries-1.9.6.dist-info/RECORD +17 -0
- {openseries-1.9.5.dist-info → openseries-1.9.6.dist-info}/WHEEL +1 -1
- openseries-1.9.5.dist-info/RECORD +0 -17
- {openseries-1.9.5.dist-info → openseries-1.9.6.dist-info/licenses}/LICENSE.md +0 -0
openseries/report.py
CHANGED
@@ -75,7 +75,7 @@ def calendar_period_returns(
|
|
75
75
|
cldr.index = Index([d.year for d in cldr.index])
|
76
76
|
elif freq.upper() == "BQE":
|
77
77
|
cldr.index = Index(
|
78
|
-
[Timestamp(d).to_period("Q").strftime("Q%q %Y") for d in cldr.index]
|
78
|
+
[Timestamp(d).to_period("Q").strftime("Q%q %Y") for d in cldr.index],
|
79
79
|
)
|
80
80
|
else:
|
81
81
|
cldr.index = Index([d.strftime("%b %y") for d in cldr.index])
|
@@ -266,7 +266,7 @@ def report_html(
|
|
266
266
|
|
267
267
|
# noinspection PyTypeChecker
|
268
268
|
rpt_df = copied.all_properties(
|
269
|
-
properties=cast("list[LiteralFrameProps]", properties)
|
269
|
+
properties=cast("list[LiteralFrameProps]", properties),
|
270
270
|
)
|
271
271
|
alpha_frame = copied.from_deepcopy()
|
272
272
|
alpha_frame.to_cumret()
|
@@ -282,7 +282,9 @@ def report_html(
|
|
282
282
|
]
|
283
283
|
alphas.append("")
|
284
284
|
ar = DataFrame(
|
285
|
-
data=alphas,
|
285
|
+
data=alphas,
|
286
|
+
index=copied.tsdf.columns,
|
287
|
+
columns=["Jensen's Alpha"],
|
286
288
|
).T
|
287
289
|
rpt_df = concat([rpt_df, ar])
|
288
290
|
ir = copied.info_ratio_func()
|
@@ -294,7 +296,7 @@ def report_html(
|
|
294
296
|
te_frame.resample("7D")
|
295
297
|
with catch_warnings():
|
296
298
|
simplefilter("ignore")
|
297
|
-
te = te_frame.tracking_error_func()
|
299
|
+
te: Series[float] | Series[str] = te_frame.tracking_error_func()
|
298
300
|
if te.hasnans:
|
299
301
|
te = Series(
|
300
302
|
data=[""] * te_frame.item_count,
|
@@ -318,7 +320,7 @@ def report_html(
|
|
318
320
|
with catch_warnings():
|
319
321
|
simplefilter("ignore")
|
320
322
|
try:
|
321
|
-
cru = crm.capture_ratio_func(ratio="both")
|
323
|
+
cru: Series[float] | Series[str] = crm.capture_ratio_func(ratio="both")
|
322
324
|
except ZeroDivisionError as exc: # pragma: no cover
|
323
325
|
msg = f"Capture ratio calculation error: {exc!s}" # pragma: no cover
|
324
326
|
logger.warning(msg=msg) # pragma: no cover
|
@@ -358,14 +360,9 @@ def report_html(
|
|
358
360
|
|
359
361
|
this_year = copied.last_idx.year
|
360
362
|
this_month = copied.last_idx.month
|
361
|
-
ytd =
|
362
|
-
"{:.2%}".format
|
363
|
-
)
|
363
|
+
ytd = copied.value_ret_calendar_period(year=this_year).map("{:.2%}".format)
|
364
364
|
ytd.name = "Year-to-Date"
|
365
|
-
mtd =
|
366
|
-
"Series[float]",
|
367
|
-
copied.value_ret_calendar_period(year=this_year, month=this_month),
|
368
|
-
).map(
|
365
|
+
mtd = copied.value_ret_calendar_period(year=this_year, month=this_month).map(
|
369
366
|
"{:.2%}".format,
|
370
367
|
)
|
371
368
|
mtd.name = "Month-to-Date"
|
@@ -430,7 +427,8 @@ def report_html(
|
|
430
427
|
|
431
428
|
figure.update_layout(fig.get("layout"))
|
432
429
|
colorway: list[str] = cast("dict[str, list[str]]", fig["layout"]).get(
|
433
|
-
"colorway",
|
430
|
+
"colorway",
|
431
|
+
[],
|
434
432
|
)
|
435
433
|
|
436
434
|
if vertical_legend:
|
@@ -457,7 +455,7 @@ def report_html(
|
|
457
455
|
figure.update_xaxes(gridcolor="#EEEEEE", automargin=True, tickangle=-45)
|
458
456
|
figure.update_yaxes(tickformat=".2%", gridcolor="#EEEEEE", automargin=True)
|
459
457
|
|
460
|
-
if
|
458
|
+
if title:
|
461
459
|
figure.update_layout(
|
462
460
|
{"title": {"text": f"<b>{title}</b><br>", "font": {"size": 36}}},
|
463
461
|
)
|
openseries/series.py
CHANGED
@@ -17,10 +17,14 @@ if TYPE_CHECKING: # pragma: no cover
|
|
17
17
|
import datetime as dt
|
18
18
|
from collections.abc import Callable
|
19
19
|
|
20
|
+
from numpy.typing import NDArray
|
21
|
+
from pandas import Timestamp
|
22
|
+
|
20
23
|
from numpy import (
|
21
24
|
append,
|
22
25
|
array,
|
23
26
|
cumprod,
|
27
|
+
float64,
|
24
28
|
insert,
|
25
29
|
isnan,
|
26
30
|
log,
|
@@ -70,7 +74,7 @@ TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
|
|
70
74
|
|
71
75
|
|
72
76
|
# noinspection PyUnresolvedReferences,PyNestedDecorators
|
73
|
-
class OpenTimeSeries(_CommonModel):
|
77
|
+
class OpenTimeSeries(_CommonModel[float]):
|
74
78
|
"""OpenTimeSeries objects are at the core of the openseries package.
|
75
79
|
|
76
80
|
The intended use is to allow analyses of financial timeseries.
|
@@ -143,7 +147,8 @@ class OpenTimeSeries(_CommonModel):
|
|
143
147
|
@FieldValidator("markets", mode="before")
|
144
148
|
@classmethod
|
145
149
|
def _validate_markets(
|
146
|
-
cls,
|
150
|
+
cls,
|
151
|
+
value: list[str] | str | None,
|
147
152
|
) -> list[str] | str | None:
|
148
153
|
"""Pydantic validator to ensure markets field is validated."""
|
149
154
|
msg = (
|
@@ -168,9 +173,6 @@ class OpenTimeSeries(_CommonModel):
|
|
168
173
|
if dates_list_length != dates_set_length:
|
169
174
|
msg = "Dates are not unique"
|
170
175
|
raise ValueError(msg)
|
171
|
-
if values_list_length < 1:
|
172
|
-
msg = "There must be at least 1 value"
|
173
|
-
raise ValueError(msg)
|
174
176
|
if (
|
175
177
|
(dates_list_length != values_list_length)
|
176
178
|
or (len(self.tsdf.index) != self.tsdf.shape[0])
|
@@ -470,12 +472,13 @@ class OpenTimeSeries(_CommonModel):
|
|
470
472
|
The returns of the values in the series
|
471
473
|
|
472
474
|
"""
|
475
|
+
# noinspection PyCallingNonCallable
|
473
476
|
returns = self.tsdf.ffill().pct_change()
|
474
477
|
returns.iloc[0] = 0
|
475
478
|
self.valuetype = ValueType.RTRN
|
476
479
|
arrays = [[self.label], [self.valuetype]]
|
477
480
|
returns.columns = MultiIndex.from_arrays(
|
478
|
-
arrays=arrays # type: ignore[arg-type]
|
481
|
+
arrays=arrays, # type: ignore[arg-type]
|
479
482
|
)
|
480
483
|
self.tsdf = returns.copy()
|
481
484
|
return self
|
@@ -550,11 +553,16 @@ class OpenTimeSeries(_CommonModel):
|
|
550
553
|
An OpenTimeSeries object
|
551
554
|
|
552
555
|
"""
|
553
|
-
arr = array(self.values) / divider
|
556
|
+
arr: NDArray[float64] = array(self.values) / divider
|
554
557
|
|
555
558
|
deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]])
|
556
|
-
arr =
|
557
|
-
|
559
|
+
arr = cast(
|
560
|
+
"NDArray[float64]",
|
561
|
+
cumprod(
|
562
|
+
a=insert(
|
563
|
+
arr=1.0 + deltas * arr[:-1] / days_in_year, obj=0, values=1.0
|
564
|
+
),
|
565
|
+
),
|
558
566
|
)
|
559
567
|
|
560
568
|
self.dates = [d.strftime("%Y-%m-%d") for d in self.tsdf.index]
|
@@ -672,32 +680,37 @@ class OpenTimeSeries(_CommonModel):
|
|
672
680
|
|
673
681
|
"""
|
674
682
|
earlier, later = self.calc_range(
|
675
|
-
months_offset=months_from_last,
|
683
|
+
months_offset=months_from_last,
|
684
|
+
from_dt=from_date,
|
685
|
+
to_dt=to_date,
|
676
686
|
)
|
677
687
|
if periods_in_a_year_fixed:
|
678
688
|
time_factor = float(periods_in_a_year_fixed)
|
679
689
|
else:
|
680
|
-
how_many =
|
681
|
-
cast("
|
682
|
-
|
683
|
-
|
690
|
+
how_many = (
|
691
|
+
self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)]
|
692
|
+
.count()
|
693
|
+
.iloc[0]
|
694
|
+
)
|
684
695
|
fraction = (later - earlier).days / 365.25
|
685
|
-
time_factor =
|
696
|
+
time_factor = how_many / fraction
|
686
697
|
|
687
|
-
data = self.tsdf.loc[
|
698
|
+
data = self.tsdf.loc[
|
699
|
+
cast("Timestamp", earlier) : cast("Timestamp", later)
|
700
|
+
].copy()
|
688
701
|
|
689
702
|
data[self.label, ValueType.RTRN] = log(
|
690
|
-
data.loc[:, self.tsdf.columns.to_numpy()[0]]
|
703
|
+
data.loc[:, self.tsdf.columns.to_numpy()[0]],
|
691
704
|
).diff()
|
692
705
|
|
693
706
|
rawdata = [
|
694
|
-
data
|
707
|
+
data[(self.label, ValueType.RTRN)]
|
695
708
|
.iloc[1:day_chunk]
|
696
709
|
.std(ddof=dlta_degr_freedms)
|
697
710
|
* sqrt(time_factor),
|
698
711
|
]
|
699
712
|
|
700
|
-
for item in data
|
713
|
+
for item in data[(self.label, ValueType.RTRN)].iloc[1:]:
|
701
714
|
prev = rawdata[-1]
|
702
715
|
rawdata.append(
|
703
716
|
sqrt(
|
@@ -865,6 +878,7 @@ def timeseries_chain(
|
|
865
878
|
|
866
879
|
dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
|
867
880
|
|
881
|
+
# noinspection PyTypeChecker
|
868
882
|
return back.__class__(
|
869
883
|
timeseries_id=new.timeseries_id,
|
870
884
|
instrument_id=new.instrument_id,
|
openseries/simulation.py
CHANGED
@@ -60,7 +60,7 @@ def _random_generator(seed: int | None) -> Generator:
|
|
60
60
|
return Generator(bit_generator=bg)
|
61
61
|
|
62
62
|
|
63
|
-
class ReturnSimulation(BaseModel):
|
63
|
+
class ReturnSimulation(BaseModel):
|
64
64
|
"""The class ReturnSimulation allows for simulating financial timeseries.
|
65
65
|
|
66
66
|
Parameters
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: openseries
|
3
|
-
Version: 1.9.
|
3
|
+
Version: 1.9.6
|
4
4
|
Summary: Tools for analyzing financial timeseries.
|
5
5
|
License: # BSD 3-Clause License
|
6
6
|
|
@@ -29,6 +29,7 @@ License: # BSD 3-Clause License
|
|
29
29
|
however caused and on any theory of liability, whether in contract, strict liability,
|
30
30
|
or tort (including negligence or otherwise) arising in any way out of the use of this
|
31
31
|
software, even if advised of the possibility of such damage.
|
32
|
+
License-File: LICENSE.md
|
32
33
|
Keywords: python,finance,fintech,data-science,timeseries,timeseries-data,timeseries-analysis,investment,investment-analysis,investing
|
33
34
|
Author: Martin Karrin
|
34
35
|
Author-email: martin.karrin@captor.se
|
@@ -119,7 +120,7 @@ _,_=series.plot_series()
|
|
119
120
|
|
120
121
|
### Sample output using the report_html() function:
|
121
122
|
|
122
|
-
<img src="https://raw.githubusercontent.com/CaptorAB/openseries/master/
|
123
|
+
<img src="https://raw.githubusercontent.com/CaptorAB/openseries/master/openseries_plot.png" alt="Two Assets Compared" width="1000" />
|
123
124
|
|
124
125
|
## Development Instructions
|
125
126
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
openseries/__init__.py,sha256=WAh79oE-ceGG_yl4nBukkp3UPvmLk4u_GySL2xOKbxE,1375
|
2
|
+
openseries/_common_model.py,sha256=oZDTfUiQ4OG0T0RsBwTwRPHQcFpdOv1X7447hssoma4,82768
|
3
|
+
openseries/_risk.py,sha256=8XKZWWXrECo0Vd9r2kbcn4dzyPuo93DAEO8eSkv4w20,2357
|
4
|
+
openseries/datefixer.py,sha256=eVhxaFj_la_XZQuPQHvinTWEzCCn8ct_AnZEYPOpY6U,15775
|
5
|
+
openseries/frame.py,sha256=BG_Qnp0PMIZ7ZiShqTjO3Koj7Gs04n4WyzApCvtcUQY,57953
|
6
|
+
openseries/load_plotly.py,sha256=C6iQyabfi5ubSONuis3yRHb3bUktBtTDlovsDIaeHNQ,2266
|
7
|
+
openseries/owntypes.py,sha256=4IZvwl_YtoUZKlmVX65j5fp1zmfmOJLvaX8vxEIzIAY,9665
|
8
|
+
openseries/plotly_captor_logo.json,sha256=F5nhMzEyxKywtjvQqMTKgKRCJQYMDIiBgDSxdte8Clo,178
|
9
|
+
openseries/plotly_layouts.json,sha256=MvDEQuiqIhMBXBelXb1sedTOlTPheizv6NZRLeE9YS4,1431
|
10
|
+
openseries/portfoliotools.py,sha256=NqSTMlVv9Szu2usXtYzt__661VJoLsAf059ThfBr99Q,19677
|
11
|
+
openseries/report.py,sha256=pnxiEfbbkDmzj-lPcWgmYao1vtv1DUG8b73QDQKJa8o,14379
|
12
|
+
openseries/series.py,sha256=NgEZ2YPiLG55Bba48XS2zSh5dRLqz8hyGm-CGG1jNmY,29122
|
13
|
+
openseries/simulation.py,sha256=8J_iDlKjDVDiHMTWQAzHdXWxNR81pOz2RfdfUMHKSqQ,14337
|
14
|
+
openseries-1.9.6.dist-info/METADATA,sha256=GrNCO5aUnS5cuboqOsyU8sTN1YyHARF3RhFT1keNM3s,48324
|
15
|
+
openseries-1.9.6.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
|
16
|
+
openseries-1.9.6.dist-info/licenses/LICENSE.md,sha256=wNupG-KLsG0aTncb_SMNDh1ExtrKXlpxSJ6RC-g-SWs,1516
|
17
|
+
openseries-1.9.6.dist-info/RECORD,,
|
@@ -1,17 +0,0 @@
|
|
1
|
-
openseries/__init__.py,sha256=WAh79oE-ceGG_yl4nBukkp3UPvmLk4u_GySL2xOKbxE,1375
|
2
|
-
openseries/_common_model.py,sha256=Nug9DIp54q7tt0yHFEQKAZPrG9c1Oy6VpyqoRWKOC4I,85499
|
3
|
-
openseries/_risk.py,sha256=8XKZWWXrECo0Vd9r2kbcn4dzyPuo93DAEO8eSkv4w20,2357
|
4
|
-
openseries/datefixer.py,sha256=Z3AKLvULzy9MPQOndKhay0nGx2EgYcjVFNjT9qReoHk,15727
|
5
|
-
openseries/frame.py,sha256=a3TPLdvapnvHU_wbhPO0G95UHaHlJLXnsSmm-Ti_2sw,58579
|
6
|
-
openseries/load_plotly.py,sha256=C6iQyabfi5ubSONuis3yRHb3bUktBtTDlovsDIaeHNQ,2266
|
7
|
-
openseries/owntypes.py,sha256=P9CKoLtjUaFiktLb_axihrlVR5bJfdDbSFJC72kQG2o,9584
|
8
|
-
openseries/plotly_captor_logo.json,sha256=F5nhMzEyxKywtjvQqMTKgKRCJQYMDIiBgDSxdte8Clo,178
|
9
|
-
openseries/plotly_layouts.json,sha256=MvDEQuiqIhMBXBelXb1sedTOlTPheizv6NZRLeE9YS4,1431
|
10
|
-
openseries/portfoliotools.py,sha256=NMSp-dYjPRjBJpZ9W20IKDrQmBDk7e4qkTPT4QFx6io,19721
|
11
|
-
openseries/report.py,sha256=iWe68o883EIU9B_t-61fl41wzTY2e6p_ZHgST2uoH3g,14393
|
12
|
-
openseries/series.py,sha256=HT2U_gUhiZMhvt7hzpUPakKBEXI64Ur2WqoaUhXV11k,28925
|
13
|
-
openseries/simulation.py,sha256=t2LFlAT9lcfPqqGEXOUoEgIG2gDEuGps3Qd3IgN_GLk,14359
|
14
|
-
openseries-1.9.5.dist-info/LICENSE.md,sha256=wNupG-KLsG0aTncb_SMNDh1ExtrKXlpxSJ6RC-g-SWs,1516
|
15
|
-
openseries-1.9.5.dist-info/METADATA,sha256=XYz8k9ujPY_MuwGjyHEyxyCgjKppszQJfmFq3tZ67U4,48301
|
16
|
-
openseries-1.9.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
17
|
-
openseries-1.9.5.dist-info/RECORD,,
|
File without changes
|