openseries 1.8.4__tar.gz → 1.9.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openseries
3
- Version: 1.8.4
3
+ Version: 1.9.1
4
4
  Summary: Tools for analyzing financial timeseries.
5
5
  License: # BSD 3-Clause License
6
6
 
@@ -46,13 +46,12 @@ Classifier: Natural Language :: English
46
46
  Classifier: Development Status :: 5 - Production/Stable
47
47
  Classifier: Operating System :: OS Independent
48
48
  Classifier: Framework :: Pydantic
49
+ Requires-Dist: exchange-calendars (>=4.8,<6.0)
49
50
  Requires-Dist: holidays (>=0.30,<1.0)
50
- Requires-Dist: numpy (>=1.23.2,<3.0.0)
51
+ Requires-Dist: numpy (>=1.23.2,!=2.3.0,<3.0.0)
51
52
  Requires-Dist: openpyxl (>=3.1.2,<5.0.0)
52
53
  Requires-Dist: pandas (>=2.1.2,<3.0.0)
53
- Requires-Dist: pandas-market-calendars (>=5.1.0,<7.0.0)
54
54
  Requires-Dist: plotly (>=5.18.0,<7.0.0)
55
- Requires-Dist: pyarrow (>=14.0.2,<21.0.0)
56
55
  Requires-Dist: pydantic (>=2.5.2,<3.0.0)
57
56
  Requires-Dist: python-dateutil (>=2.8.2,<4.0.0)
58
57
  Requires-Dist: requests (>=2.20.0,<3.0.0)
@@ -75,7 +74,7 @@ Description-Content-Type: text/markdown
75
74
  ![Platform](https://img.shields.io/badge/platforms-Windows%20%7C%20macOS%20%7C%20Linux-blue)
76
75
  [![Python version](https://img.shields.io/pypi/pyversions/openseries.svg)](https://www.python.org/)
77
76
  [![GitHub Action Test Suite](https://github.com/CaptorAB/openseries/actions/workflows/test.yml/badge.svg)](https://github.com/CaptorAB/openseries/actions/workflows/test.yml)
78
- [![Coverage](https://cdn.jsdelivr.net/gh/CaptorAB/openseries@master/coverage.svg)](https://github.com/CaptorAB/openseries/actions/workflows/test.yml)
77
+ [![codecov](https://img.shields.io/codecov/c/github/CaptorAB/openseries/master)](https://codecov.io/gh/CaptorAB/openseries/branch/master)
79
78
  [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
80
79
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://beta.ruff.rs/docs/)
81
80
  [![GitHub License](https://img.shields.io/github/license/CaptorAB/openseries)](https://github.com/CaptorAB/openseries/blob/master/LICENSE.md)
@@ -118,35 +117,9 @@ _,_=series.plot_series()
118
117
 
119
118
  ```
120
119
 
121
- ### Sample output using the OpenFrame.all_properties() method:
120
+ ### Sample output using the report_html() function:
122
121
 
123
- ```
124
- Scilla Global Equity C (simulation+fund) Global Low Volatility index, SEK
125
- ValueType.PRICE ValueType.PRICE
126
- Total return 3.641282 1.946319
127
- Arithmetic return 0.096271 0.069636
128
- Geometric return 0.093057 0.06464
129
- Volatility 0.120279 0.117866
130
- Return vol ratio 0.800396 0.59081
131
- Downside deviation 0.085956 0.086723
132
- Sortino ratio 1.119993 0.802975
133
- Positive share 0.541783 0.551996
134
- Worst -0.071616 -0.089415
135
- Worst month -0.122503 -0.154485
136
- Max drawdown -0.309849 -0.435444
137
- Max drawdown in cal yr -0.309849 -0.348681
138
- Max drawdown dates 2020-03-23 2009-03-09
139
- CVaR 95.0% -0.01793 -0.018429
140
- VaR 95.0% -0.011365 -0.010807
141
- Imp vol from VaR 95% 0.109204 0.103834
142
- Z-score 0.587905 0.103241
143
- Skew -0.650782 -0.888109
144
- Kurtosis 8.511166 17.527367
145
- observations 4309 4309
146
- span of days 6301 6301
147
- first indices 2006-01-03 2006-01-03
148
- last indices 2023-04-05 2023-04-05
149
- ```
122
+ <img src="./captor_plot_image.png" alt="Two Assets Compared" width="1000" />
150
123
 
151
124
  ## Development Instructions
152
125
 
@@ -171,9 +144,8 @@ cd openseries
171
144
  ```bash
172
145
  git clone https://github.com/CaptorAB/openseries.git
173
146
  cd openseries
174
- make
175
- source source_me
176
147
  make install
148
+ source source_me
177
149
 
178
150
  ```
179
151
 
@@ -220,12 +192,13 @@ make lint
220
192
 
221
193
  ### On some files in the project
222
194
 
223
- | File | Description |
224
- |:-----------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
225
- | [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. |
226
- | [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. |
227
- | [portfoliotools.py](https://github.com/CaptorAB/openseries/blob/master/openseries/portfoliotools.py) | Defines functions to simulate, optimize, and plot portfolios. |
228
- | [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 |
195
+ | File | Description |
196
+ |:-----------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
197
+ | [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. |
198
+ | [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. |
199
+ | [portfoliotools.py](https://github.com/CaptorAB/openseries/blob/master/openseries/portfoliotools.py) | Defines functions to simulate, optimize, and plot portfolios. |
200
+ | [report.py](https://github.com/CaptorAB/openseries/blob/master/openseries/report.py) | Defines the _report_html_ function that is used to create a landscape orientation report on at least two assets. All preceding assets will be measured against the last asset in the input OpenFrame. |
201
+ | [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 |
229
202
 
230
203
  ### Class methods used to construct objects.
231
204
 
@@ -9,7 +9,7 @@
9
9
  ![Platform](https://img.shields.io/badge/platforms-Windows%20%7C%20macOS%20%7C%20Linux-blue)
10
10
  [![Python version](https://img.shields.io/pypi/pyversions/openseries.svg)](https://www.python.org/)
11
11
  [![GitHub Action Test Suite](https://github.com/CaptorAB/openseries/actions/workflows/test.yml/badge.svg)](https://github.com/CaptorAB/openseries/actions/workflows/test.yml)
12
- [![Coverage](https://cdn.jsdelivr.net/gh/CaptorAB/openseries@master/coverage.svg)](https://github.com/CaptorAB/openseries/actions/workflows/test.yml)
12
+ [![codecov](https://img.shields.io/codecov/c/github/CaptorAB/openseries/master)](https://codecov.io/gh/CaptorAB/openseries/branch/master)
13
13
  [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
14
14
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://beta.ruff.rs/docs/)
15
15
  [![GitHub License](https://img.shields.io/github/license/CaptorAB/openseries)](https://github.com/CaptorAB/openseries/blob/master/LICENSE.md)
@@ -52,35 +52,9 @@ _,_=series.plot_series()
52
52
 
53
53
  ```
54
54
 
55
- ### Sample output using the OpenFrame.all_properties() method:
55
+ ### Sample output using the report_html() function:
56
56
 
57
- ```
58
- Scilla Global Equity C (simulation+fund) Global Low Volatility index, SEK
59
- ValueType.PRICE ValueType.PRICE
60
- Total return 3.641282 1.946319
61
- Arithmetic return 0.096271 0.069636
62
- Geometric return 0.093057 0.06464
63
- Volatility 0.120279 0.117866
64
- Return vol ratio 0.800396 0.59081
65
- Downside deviation 0.085956 0.086723
66
- Sortino ratio 1.119993 0.802975
67
- Positive share 0.541783 0.551996
68
- Worst -0.071616 -0.089415
69
- Worst month -0.122503 -0.154485
70
- Max drawdown -0.309849 -0.435444
71
- Max drawdown in cal yr -0.309849 -0.348681
72
- Max drawdown dates 2020-03-23 2009-03-09
73
- CVaR 95.0% -0.01793 -0.018429
74
- VaR 95.0% -0.011365 -0.010807
75
- Imp vol from VaR 95% 0.109204 0.103834
76
- Z-score 0.587905 0.103241
77
- Skew -0.650782 -0.888109
78
- Kurtosis 8.511166 17.527367
79
- observations 4309 4309
80
- span of days 6301 6301
81
- first indices 2006-01-03 2006-01-03
82
- last indices 2023-04-05 2023-04-05
83
- ```
57
+ <img src="./captor_plot_image.png" alt="Two Assets Compared" width="1000" />
84
58
 
85
59
  ## Development Instructions
86
60
 
@@ -105,9 +79,8 @@ cd openseries
105
79
  ```bash
106
80
  git clone https://github.com/CaptorAB/openseries.git
107
81
  cd openseries
108
- make
109
- source source_me
110
82
  make install
83
+ source source_me
111
84
 
112
85
  ```
113
86
 
@@ -154,12 +127,13 @@ make lint
154
127
 
155
128
  ### On some files in the project
156
129
 
157
- | File | Description |
158
- |:-----------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
159
- | [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. |
160
- | [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. |
161
- | [portfoliotools.py](https://github.com/CaptorAB/openseries/blob/master/openseries/portfoliotools.py) | Defines functions to simulate, optimize, and plot portfolios. |
162
- | [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 |
130
+ | File | Description |
131
+ |:-----------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
132
+ | [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. |
133
+ | [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. |
134
+ | [portfoliotools.py](https://github.com/CaptorAB/openseries/blob/master/openseries/portfoliotools.py) | Defines functions to simulate, optimize, and plot portfolios. |
135
+ | [report.py](https://github.com/CaptorAB/openseries/blob/master/openseries/report.py) | Defines the _report_html_ function that is used to create a landscape orientation report on at least two assets. All preceding assets will be measured against the last asset in the input OpenFrame. |
136
+ | [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 |
163
137
 
164
138
  ### Class methods used to construct objects.
165
139
 
@@ -1,4 +1,11 @@
1
- """openseries.openseries.__init__.py."""
1
+ """openseries.openseries.__init__.py.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from .datefixer import (
4
11
  date_fix,
@@ -18,6 +25,7 @@ from .portfoliotools import (
18
25
  sharpeplot,
19
26
  simulate_portfolios,
20
27
  )
28
+ from .report import report_html
21
29
  from .series import OpenTimeSeries, timeseries_chain
22
30
  from .simulation import ReturnSimulation
23
31
 
@@ -37,6 +45,7 @@ __all__ = [
37
45
  "load_plotly_dict",
38
46
  "offset_business_days",
39
47
  "prepare_plot_data",
48
+ "report_html",
40
49
  "sharpeplot",
41
50
  "simulate_portfolios",
42
51
  "timeseries_chain",
@@ -1,4 +1,11 @@
1
- """Defining the _CommonModel class."""
1
+ """Defining the _CommonModel class.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  # mypy: disable-error-code="no-any-return"
4
11
  from __future__ import annotations
@@ -1,4 +1,11 @@
1
- """Various risk related functions."""
1
+ """Various risk related functions.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
@@ -1,11 +1,18 @@
1
- """Date related utilities."""
1
+ """Date related utilities.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
5
12
  import datetime as dt
6
13
  from typing import TYPE_CHECKING, cast
7
14
 
8
- import pandas_market_calendars as mcal
15
+ import exchange_calendars as exchcal
9
16
  from dateutil.relativedelta import relativedelta
10
17
  from holidays import (
11
18
  country_holidays,
@@ -69,7 +76,7 @@ def market_holidays(
69
76
  """
70
77
  market_list = [markets] if isinstance(markets, str) else list(markets)
71
78
 
72
- supported = mcal.get_calendar_names()
79
+ supported = exchcal.get_calendar_names()
73
80
 
74
81
  if not all(m in supported for m in market_list):
75
82
  msg = (
@@ -80,13 +87,12 @@ def market_holidays(
80
87
 
81
88
  holidays: list[str] = []
82
89
  for m in market_list:
83
- cal = mcal.get_calendar(m)
84
- # noinspection PyUnresolvedReferences
85
- cal_hols = cal.holidays().calendar.holidays
90
+ cal = exchcal.get_calendar(m)
91
+ cal_hols = cal.regular_holidays.holidays()
86
92
  my_hols: list[str] = [
87
- str(date)
93
+ date.date().strftime("%Y-%m-%d")
88
94
  for date in cal_hols
89
- if (startyear <= int(str(date)[:4]) <= endyear)
95
+ if (startyear <= date.date().year <= endyear)
90
96
  ]
91
97
  holidays.extend(my_hols)
92
98
 
@@ -1,4 +1,11 @@
1
- """Defining the OpenFrame class."""
1
+ """Defining the OpenFrame class.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  # mypy: disable-error-code="index,assignment,arg-type,no-any-return"
4
11
  from __future__ import annotations
@@ -1,4 +1,11 @@
1
- """Function to load plotly layout and configuration from local json file."""
1
+ """Function to load plotly layout and configuration from local json file.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
@@ -1,4 +1,11 @@
1
- """Declaring types used throughout the project."""
1
+ """Declaring types used throughout the project.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
@@ -1,4 +1,11 @@
1
- """Defining the portfolio tools for the OpenFrame class."""
1
+ """Defining the portfolio tools for the OpenFrame class.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  # mypy: disable-error-code="index,assignment"
4
11
  from __future__ import annotations
@@ -0,0 +1,479 @@
1
+ """Functions related to HTML reports.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
9
+
10
+ # mypy: disable-error-code="assignment,index,arg-type"
11
+ from __future__ import annotations
12
+
13
+ from inspect import stack
14
+ from logging import getLogger
15
+ from pathlib import Path
16
+ from secrets import choice
17
+ from string import ascii_letters
18
+ from typing import TYPE_CHECKING, cast
19
+ from warnings import catch_warnings, simplefilter
20
+
21
+ if TYPE_CHECKING: # pragma: no cover
22
+ from pandas import Series
23
+ from plotly.graph_objs import Figure
24
+
25
+ from .frame import OpenFrame
26
+ from .owntypes import LiteralPlotlyJSlib, LiteralPlotlyOutput
27
+
28
+
29
+ from pandas import DataFrame, Series, concat
30
+ from plotly.io import to_html
31
+ from plotly.offline import plot
32
+ from plotly.subplots import make_subplots
33
+
34
+ from .load_plotly import load_plotly_dict
35
+ from .owntypes import (
36
+ LiteralBizDayFreq,
37
+ ValueType,
38
+ )
39
+
40
+ logger = getLogger(__name__)
41
+
42
+
43
+ __all__ = ["report_html"]
44
+
45
+
46
+ def calendar_period_returns(
47
+ data: OpenFrame,
48
+ freq: LiteralBizDayFreq = "BYE",
49
+ *,
50
+ relabel: bool = True,
51
+ ) -> DataFrame:
52
+ """Generate a table of returns with appropriate table labels.
53
+
54
+ Parameters
55
+ ----------
56
+ data: OpenFrame
57
+ The timeseries data
58
+ freq: LiteralBizDayFreq
59
+ The date offset string that sets the resampled frequency
60
+ relabel: bool, default: True
61
+ Whether to set new appropriate labels
62
+
63
+ Returns:
64
+ -------
65
+ pandas.DataFrame
66
+ The resulting data
67
+
68
+ """
69
+ copied = data.from_deepcopy()
70
+ copied.resample_to_business_period_ends(freq=freq)
71
+ vtypes = [x == ValueType.RTRN for x in copied.tsdf.columns.get_level_values(1)]
72
+ if not any(vtypes):
73
+ copied.value_to_ret()
74
+ cldr = copied.tsdf.iloc[1:].copy()
75
+ if relabel:
76
+ if freq == "BYE":
77
+ cldr.index = [d.year for d in cldr.index]
78
+ else:
79
+ cldr.index = [d.strftime("%b %y") for d in cldr.index]
80
+
81
+ return cldr # type: ignore[no-any-return]
82
+
83
+
84
+ def report_html(
85
+ data: OpenFrame,
86
+ bar_freq: LiteralBizDayFreq = "BYE",
87
+ filename: str | None = None,
88
+ title: str | None = None,
89
+ directory: Path | None = None,
90
+ output_type: LiteralPlotlyOutput = "file",
91
+ include_plotlyjs: LiteralPlotlyJSlib = "cdn",
92
+ *,
93
+ auto_open: bool = False,
94
+ add_logo: bool = True,
95
+ vertical_legend: bool = True,
96
+ ) -> tuple[Figure, str]:
97
+ """Generate a HTML report page with line and bar plots and a table.
98
+
99
+ Parameters
100
+ ----------
101
+ data: OpenFrame
102
+ The timeseries data
103
+ bar_freq: LiteralBizDayFreq
104
+ The date offset string that sets the bar plot frequency
105
+ filename: str, optional
106
+ Name of the Plotly html file
107
+ title: str, optional
108
+ The report page title
109
+ directory: DirectoryPath, optional
110
+ Directory where Plotly html file is saved
111
+ output_type: LiteralPlotlyOutput, default: "file"
112
+ Determines output type
113
+ include_plotlyjs: LiteralPlotlyJSlib, default: "cdn"
114
+ Determines how the plotly.js library is included in the output
115
+ auto_open: bool, default: True
116
+ Determines whether to open a browser window with the plot
117
+ add_logo: bool, default: True
118
+ If True a Captor logo is added to the plot
119
+ vertical_legend: bool, default: True
120
+ Determines whether to vertically align the legend's labels
121
+
122
+ Returns:
123
+ -------
124
+ tuple[plotly.go.Figure, str]
125
+ Plotly Figure and a div section or a html filename with location
126
+
127
+ """
128
+ data.trunc_frame().value_nan_handle().to_cumret()
129
+
130
+ if data.yearfrac > 1.0:
131
+ properties = [
132
+ "geo_ret",
133
+ "vol",
134
+ "ret_vol_ratio",
135
+ "sortino_ratio",
136
+ "worst_month",
137
+ "first_indices",
138
+ "last_indices",
139
+ ]
140
+ labels_init = [
141
+ "Return (CAGR)",
142
+ "Volatility",
143
+ "Sharpe Ratio",
144
+ "Sortino Ratio",
145
+ "Worst Month",
146
+ "Comparison Start",
147
+ "Comparison End",
148
+ "Jensen's Alpha",
149
+ "Information Ratio",
150
+ "Tracking Error (weekly)",
151
+ "Capture Ratio (monthly)",
152
+ "Index Beta (weekly)",
153
+ ]
154
+ labels_final = [
155
+ "Return (CAGR)",
156
+ "Year-to-Date",
157
+ "Month-to-Date",
158
+ "Volatility",
159
+ "Sharpe Ratio",
160
+ "Sortino Ratio",
161
+ "Jensen's Alpha",
162
+ "Information Ratio",
163
+ "Tracking Error (weekly)",
164
+ "Index Beta (weekly)",
165
+ "Capture Ratio (monthly)",
166
+ "Worst Month",
167
+ "Comparison Start",
168
+ "Comparison End",
169
+ ]
170
+ else:
171
+ properties = [
172
+ "value_ret",
173
+ "vol",
174
+ "ret_vol_ratio",
175
+ "sortino_ratio",
176
+ "worst",
177
+ "first_indices",
178
+ "last_indices",
179
+ ]
180
+ labels_init = [
181
+ "Return (simple)",
182
+ "Volatility",
183
+ "Sharpe Ratio",
184
+ "Sortino Ratio",
185
+ "Worst Day",
186
+ "Comparison Start",
187
+ "Comparison End",
188
+ "Jensen's Alpha",
189
+ "Information Ratio",
190
+ "Tracking Error (weekly)",
191
+ "Index Beta (weekly)",
192
+ ]
193
+ labels_final = [
194
+ "Return (simple)",
195
+ "Year-to-Date",
196
+ "Month-to-Date",
197
+ "Volatility",
198
+ "Sharpe Ratio",
199
+ "Sortino Ratio",
200
+ "Jensen's Alpha",
201
+ "Information Ratio",
202
+ "Tracking Error (weekly)",
203
+ "Index Beta (weekly)",
204
+ "Worst Day",
205
+ "Comparison Start",
206
+ "Comparison End",
207
+ ]
208
+
209
+ figure = make_subplots(
210
+ rows=2,
211
+ cols=2,
212
+ specs=[
213
+ [{"type": "xy"}, {"rowspan": 2, "type": "table"}],
214
+ [{"type": "xy"}, None],
215
+ ],
216
+ )
217
+
218
+ for item, lbl in enumerate(data.columns_lvl_zero):
219
+ figure.add_scatter(
220
+ x=data.tsdf.index,
221
+ y=data.tsdf.iloc[:, item],
222
+ hovertemplate="%{y:.2%}<br>%{x|%Y-%m-%d}",
223
+ line={"width": 2.5, "dash": "solid"},
224
+ mode="lines",
225
+ name=lbl,
226
+ showlegend=True,
227
+ row=1,
228
+ col=1,
229
+ )
230
+
231
+ quarter_of_year = 0.25
232
+ if data.yearfrac < quarter_of_year:
233
+ tmp = data.from_deepcopy()
234
+ bdf = tmp.value_to_ret().tsdf.iloc[1:]
235
+ else:
236
+ bdf = calendar_period_returns(data, freq=bar_freq)
237
+
238
+ for item in range(data.item_count):
239
+ figure.add_bar(
240
+ x=bdf.index,
241
+ y=bdf.iloc[:, item],
242
+ hovertemplate="%{y:.2%}<br>%{x}",
243
+ name=bdf.iloc[:, item].name[0],
244
+ showlegend=False,
245
+ row=2,
246
+ col=1,
247
+ )
248
+
249
+ formats = [
250
+ "{:.2%}",
251
+ "{:.2%}",
252
+ "{:.2f}",
253
+ "{:.2f}",
254
+ "{:.2%}",
255
+ "{:%Y-%m-%d}",
256
+ "{:%Y-%m-%d}",
257
+ "{:.2%}",
258
+ "{:.2f}",
259
+ "{:.2%}",
260
+ "{:.2f}",
261
+ ]
262
+
263
+ # noinspection PyTypeChecker
264
+ rpt_df = data.all_properties(properties=properties)
265
+ alpha_frame = data.from_deepcopy()
266
+ alpha_frame.to_cumret()
267
+ with catch_warnings():
268
+ simplefilter("ignore")
269
+ alphas: list[str | float] = [
270
+ alpha_frame.jensen_alpha(
271
+ asset=(aname, ValueType.PRICE),
272
+ market=(alpha_frame.columns_lvl_zero[-1], ValueType.PRICE),
273
+ riskfree_rate=0.0,
274
+ )
275
+ for aname in alpha_frame.columns_lvl_zero[:-1]
276
+ ]
277
+ alphas.append("")
278
+ ar = DataFrame(data=alphas, index=data.tsdf.columns, columns=["Jensen's Alpha"]).T
279
+ rpt_df = concat([rpt_df, ar])
280
+ ir = data.info_ratio_func()
281
+ ir.name = "Information Ratio"
282
+ ir.iloc[-1] = None
283
+ ir = ir.to_frame().T
284
+ rpt_df = concat([rpt_df, ir])
285
+ te_frame = data.from_deepcopy()
286
+ te_frame.resample("7D")
287
+ with catch_warnings():
288
+ simplefilter("ignore")
289
+ te = te_frame.tracking_error_func()
290
+ if te.hasnans:
291
+ te = Series(
292
+ data=[""] * te_frame.item_count,
293
+ index=te_frame.tsdf.columns,
294
+ name="Tracking Error (weekly)",
295
+ )
296
+ else:
297
+ te.iloc[-1] = None
298
+ te.name = "Tracking Error (weekly)"
299
+ te = te.to_frame().T
300
+ rpt_df = concat([rpt_df, te])
301
+
302
+ if data.yearfrac > 1.0:
303
+ crm = data.from_deepcopy()
304
+ crm.resample("ME")
305
+ cru_save = Series(
306
+ data=[""] * crm.item_count,
307
+ index=crm.tsdf.columns,
308
+ name="Capture Ratio (monthly)",
309
+ )
310
+ with catch_warnings():
311
+ simplefilter("ignore")
312
+ try:
313
+ cru = crm.capture_ratio_func(ratio="both")
314
+ except ZeroDivisionError as exc: # pragma: no cover
315
+ msg = f"Capture ratio calculation error: {exc!s}" # pragma: no cover
316
+ logger.warning(msg=msg) # pragma: no cover
317
+ cru = cru_save # pragma: no cover
318
+ if cru.hasnans:
319
+ cru = cru_save
320
+ else:
321
+ cru.iloc[-1] = None
322
+ cru.name = "Capture Ratio (monthly)"
323
+ cru = cru.to_frame().T
324
+ rpt_df = concat([rpt_df, cru])
325
+ formats.append("{:.2f}")
326
+ beta_frame = data.from_deepcopy()
327
+ beta_frame.resample("7D").value_nan_handle("drop")
328
+ beta_frame.to_cumret()
329
+ betas: list[str | float] = [
330
+ beta_frame.beta(
331
+ asset=(bname, ValueType.PRICE),
332
+ market=(beta_frame.columns_lvl_zero[-1], ValueType.PRICE),
333
+ )
334
+ for bname in beta_frame.columns_lvl_zero[:-1]
335
+ ]
336
+ # noinspection PyTypeChecker
337
+ betas.append("")
338
+ br = DataFrame(
339
+ data=betas,
340
+ index=data.tsdf.columns,
341
+ columns=["Index Beta (weekly)"],
342
+ ).T
343
+ rpt_df = concat([rpt_df, br])
344
+
345
+ for item, f in zip(rpt_df.index, formats, strict=False):
346
+ rpt_df.loc[item] = rpt_df.loc[item].apply(
347
+ lambda x, fmt=f: x if (isinstance(x, str) or x is None) else fmt.format(x), # type: ignore[return-value]
348
+ )
349
+
350
+ rpt_df.index = labels_init
351
+
352
+ this_year = data.last_idx.year
353
+ this_month = data.last_idx.month
354
+ ytd = cast("Series[float]", data.value_ret_calendar_period(year=this_year)).map(
355
+ "{:.2%}".format
356
+ )
357
+ ytd.name = "Year-to-Date"
358
+ mtd = cast(
359
+ "Series[float]",
360
+ data.value_ret_calendar_period(year=this_year, month=this_month),
361
+ ).map(
362
+ "{:.2%}".format,
363
+ )
364
+ mtd.name = "Month-to-Date"
365
+ ytd = ytd.to_frame().T
366
+ mtd = mtd.to_frame().T
367
+ rpt_df = concat([rpt_df, ytd])
368
+ rpt_df = concat([rpt_df, mtd])
369
+ rpt_df = rpt_df.reindex(labels_final)
370
+
371
+ rpt_df.index = [f"<b>{x}</b>" for x in rpt_df.index]
372
+ rpt_df = rpt_df.reset_index()
373
+
374
+ colmns = ["", *data.columns_lvl_zero]
375
+ columns = [f"<b>{x}</b>" for x in colmns]
376
+ aligning = ["left"] + ["center"] * (len(columns) - 1)
377
+
378
+ col_even_color = "lightgrey"
379
+ col_odd_color = "white"
380
+ color_lst = ["grey"] + [col_odd_color] * (data.item_count - 1) + [col_even_color]
381
+
382
+ tablevalues = rpt_df.transpose().to_numpy().tolist()
383
+ cleanedtablevalues = list(tablevalues)[:-1]
384
+ cleanedcol = [
385
+ valu if valu not in ["nan", "nan%"] else "" for valu in tablevalues[-1]
386
+ ]
387
+ cleanedtablevalues.append(cleanedcol)
388
+
389
+ figure.add_table(
390
+ header={
391
+ "values": columns,
392
+ "align": "center",
393
+ "fill_color": "grey",
394
+ "font": {"color": "white"},
395
+ },
396
+ cells={
397
+ "values": cleanedtablevalues,
398
+ "align": aligning,
399
+ "height": 25,
400
+ "fill_color": color_lst,
401
+ "font": {"color": ["white"] + ["black"] * len(columns)},
402
+ },
403
+ row=1,
404
+ col=2,
405
+ )
406
+
407
+ if directory:
408
+ dirpath = Path(directory).resolve()
409
+ elif Path.home().joinpath("Documents").exists():
410
+ dirpath = Path.home() / "Documents"
411
+ else:
412
+ dirpath = Path(stack()[1].filename).parent
413
+
414
+ if not filename:
415
+ filename = "".join(choice(ascii_letters) for _ in range(6)) + ".html"
416
+
417
+ plotfile = dirpath / filename
418
+
419
+ fig, logo = load_plotly_dict()
420
+
421
+ if add_logo:
422
+ figure.add_layout_image(logo)
423
+
424
+ figure.update_layout(fig.get("layout"))
425
+ colorway: list[str] = cast("dict[str, list[str]]", fig["layout"]).get("colorway")
426
+
427
+ if vertical_legend:
428
+ legend = {
429
+ "yanchor": "bottom",
430
+ "y": -0.04,
431
+ "xanchor": "right",
432
+ "x": 0.98,
433
+ "orientation": "v",
434
+ }
435
+ else:
436
+ legend = {
437
+ "yanchor": "bottom",
438
+ "y": -0.2,
439
+ "xanchor": "right",
440
+ "x": 0.98,
441
+ "orientation": "h",
442
+ }
443
+
444
+ figure.update_layout(
445
+ legend=legend,
446
+ colorway=colorway[: data.item_count],
447
+ )
448
+ figure.update_xaxes(gridcolor="#EEEEEE", automargin=True, tickangle=-45)
449
+ figure.update_yaxes(tickformat=".2%", gridcolor="#EEEEEE", automargin=True)
450
+
451
+ if isinstance(title, str):
452
+ figure.update_layout(
453
+ {"title": {"text": f"<b>{title}</b><br>", "font": {"size": 36}}},
454
+ )
455
+
456
+ if output_type == "file":
457
+ plot(
458
+ figure_or_data=figure,
459
+ filename=str(plotfile),
460
+ auto_open=auto_open,
461
+ auto_play=False,
462
+ link_text="",
463
+ include_plotlyjs=cast("bool", include_plotlyjs),
464
+ output_type=output_type,
465
+ config=fig["config"],
466
+ )
467
+ string_output = str(plotfile)
468
+ else:
469
+ div_id = filename.split(sep=".")[0]
470
+ string_output = to_html(
471
+ fig=figure,
472
+ div_id=div_id,
473
+ auto_play=False,
474
+ full_html=False,
475
+ include_plotlyjs=cast("bool", include_plotlyjs),
476
+ config=fig["config"],
477
+ )
478
+
479
+ return figure, string_output
@@ -1,4 +1,11 @@
1
- """Defining the OpenTimeSeries class."""
1
+ """Defining the OpenTimeSeries class.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  # mypy: disable-error-code="no-any-return"
4
11
  from __future__ import annotations
@@ -1,4 +1,11 @@
1
- """Defining the ReturnSimulation class."""
1
+ """Defining the ReturnSimulation class.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  # mypy: disable-error-code="no-any-return"
4
11
  from __future__ import annotations
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openseries"
3
- version = "1.8.4"
3
+ version = "1.9.1"
4
4
  description = "Tools for analyzing financial timeseries."
5
5
  authors = [
6
6
  { name = "Martin Karrin", email = "martin.karrin@captor.se" },
@@ -40,18 +40,17 @@ keywords = [
40
40
  ]
41
41
 
42
42
  dependencies = [
43
+ "exchange-calendars (>=4.8,<6.0)",
43
44
  "holidays (>=0.30,<1.0)",
44
- "numpy (>=1.23.2,<3.0.0)",
45
+ "numpy (>=1.23.2,!=2.3.0,<3.0.0)",
45
46
  "openpyxl (>=3.1.2,<5.0.0)",
46
47
  "pandas (>=2.1.2,<3.0.0)",
47
48
  "plotly (>=5.18.0,<7.0.0)",
48
- "pyarrow (>=14.0.2,<21.0.0)",
49
49
  "pydantic (>=2.5.2,<3.0.0)",
50
50
  "python-dateutil (>=2.8.2,<4.0.0)",
51
51
  "requests (>=2.20.0,<3.0.0)",
52
52
  "scipy (>=1.11.4,<2.0.0)",
53
- "statsmodels (>=0.14.0,!=0.14.2,<1.0.0)",
54
- "pandas-market-calendars (>=5.1.0,<7.0.0)"
53
+ "statsmodels (>=0.14.0,!=0.14.2,<1.0.0)"
55
54
  ]
56
55
 
57
56
  [project.urls]
@@ -63,12 +62,11 @@ dependencies = [
63
62
  [tool.poetry.group.dev.dependencies]
64
63
  black = ">=24.4.2,<27.0.0"
65
64
  coverage = ">=7.2.7,<9.0.0"
66
- genbadge = { version = ">=1.1.1,<2.0.0", extras = ["coverage"] }
67
65
  mypy = "1.16.0"
68
66
  pandas-stubs = ">=2.1.2,<3.0.0"
69
67
  pre-commit = ">=3.7.1,<6.0.0"
70
68
  pytest = ">=8.2.2,<9.0.0"
71
- ruff = "0.11.12"
69
+ ruff = "0.11.13"
72
70
  types-openpyxl = ">=3.1.2,<5.0.0"
73
71
  types-python-dateutil = ">=2.8.2,<4.0.0"
74
72
  types-requests = ">=2.20.0,<3.0.0"
@@ -109,10 +107,7 @@ disallow_any_generics = true
109
107
  check_untyped_defs = true
110
108
  no_implicit_reexport = true
111
109
  disallow_untyped_defs = true
112
- plugins = [
113
- "pydantic.mypy",
114
- "numpy.typing.mypy_plugin"
115
- ]
110
+ plugins = ["pydantic.mypy"]
116
111
 
117
112
  [tool.pydantic-mypy]
118
113
  init_forbid_extra = true
@@ -130,9 +125,9 @@ line-length = 87
130
125
  select = ["ALL"]
131
126
  ignore = ["COM812", "D203", "D213"]
132
127
  fixable = ["ALL"]
133
- mccabe = { max-complexity = 15 }
128
+ mccabe = { max-complexity = 18 }
134
129
  pydocstyle = { convention = "google" }
135
- pylint = { max-args = 19, max-branches = 23, max-statements = 59 }
130
+ pylint = { max-args = 19, max-branches = 24, max-statements = 128 }
136
131
 
137
132
  [tool.pytest.ini_options]
138
133
  testpaths = "tests"
File without changes