openseries 1.8.0__py3-none-any.whl → 1.8.2__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/load_plotly.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from json import load
6
- from logging import warning
6
+ from logging import getLogger
7
7
  from pathlib import Path
8
8
  from typing import TYPE_CHECKING
9
9
 
@@ -13,6 +13,8 @@ from requests.exceptions import ConnectionError as RequestsConnectionError
13
13
  if TYPE_CHECKING:
14
14
  from .owntypes import CaptorLogoType, PlotlyLayoutType # pragma: no cover
15
15
 
16
+ logger = getLogger(__name__)
17
+
16
18
  __all__ = ["load_plotly_dict"]
17
19
 
18
20
 
@@ -24,7 +26,7 @@ def _check_remote_file_existence(url: str) -> bool:
24
26
  url: str
25
27
  Path to remote file
26
28
 
27
- Returns
29
+ Returns:
28
30
  -------
29
31
  bool
30
32
  True if url is valid and False otherwise
@@ -52,7 +54,7 @@ def load_plotly_dict(
52
54
  responsive : bool
53
55
  Flag whether to load as responsive
54
56
 
55
- Returns
57
+ Returns:
56
58
  -------
57
59
  tuple[PlotlyLayoutType, CaptorLogoType]
58
60
  A dictionary with the Plotly config and layout template
@@ -69,7 +71,7 @@ def load_plotly_dict(
69
71
 
70
72
  if _check_remote_file_existence(url=logo["source"]) is False:
71
73
  msg = f"Failed to add logo image from URL {logo['source']}"
72
- warning(msg)
74
+ logger.warning(msg)
73
75
  logo = {}
74
76
 
75
77
  fig["config"].update({"responsive": responsive})
openseries/owntypes.py CHANGED
@@ -10,9 +10,14 @@ from typing import Annotated, ClassVar, Literal, Union
10
10
  from numpy import datetime64
11
11
  from pandas import Timestamp
12
12
  from pydantic import BaseModel, Field, StringConstraints, conlist, conset
13
- from typing_extensions import Self
14
13
 
15
- __all__ = ["ValueType"]
14
+ try:
15
+ from typing import Self # type: ignore[attr-defined,unused-ignore]
16
+ except ImportError: # pragma: no cover
17
+ from typing_extensions import Self
18
+
19
+
20
+ __all__ = ["Self", "ValueType"]
16
21
 
17
22
 
18
23
  CountryStringType = Annotated[
@@ -33,7 +38,7 @@ CountryListType = conset(
33
38
  CountriesType = Union[CountryListType, CountryStringType] # type: ignore[valid-type] # noqa: UP007
34
39
 
35
40
 
36
- class Countries(BaseModel):
41
+ class Countries(BaseModel): # type: ignore[misc]
37
42
  """Declare Countries."""
38
43
 
39
44
  countryinput: CountriesType
@@ -52,7 +57,7 @@ CurrencyStringType = Annotated[
52
57
  ]
53
58
 
54
59
 
55
- class Currency(BaseModel):
60
+ class Currency(BaseModel): # type: ignore[misc]
56
61
  """Declare Currency."""
57
62
 
58
63
  ccy: CurrencyStringType
@@ -262,7 +267,7 @@ class PropertiesList(list[str]):
262
267
  if len(duplicates) != 0:
263
268
  msg += f"Duplicate string(s): {list(duplicates)}."
264
269
  if len(msg) != 0:
265
- raise ValueError(msg)
270
+ raise PropertiesInputValidationError(msg)
266
271
 
267
272
 
268
273
  class OpenTimeSeriesPropertiesList(PropertiesList):
@@ -316,3 +321,59 @@ class ValueType(str, Enum):
316
321
  ROLLRTRN = "Rolling returns"
317
322
  ROLLVAR = "Rolling VaR"
318
323
  ROLLVOL = "Rolling volatility"
324
+
325
+
326
+ class MixedValuetypesError(Exception):
327
+ """Raised when provided timeseries valuetypes are not the same."""
328
+
329
+
330
+ class AtLeastOneFrameError(Exception):
331
+ """Raised when none of the possible frame inputs is provided."""
332
+
333
+
334
+ class DateAlignmentError(Exception):
335
+ """Raised when date input is not aligned with existing range."""
336
+
337
+
338
+ class NumberOfItemsAndLabelsNotSameError(Exception):
339
+ """Raised when number of labels is not matching the number of timeseries."""
340
+
341
+
342
+ class InitialValueZeroError(Exception):
343
+ """Raised when a calculation cannot be performed due to initial value(s) zero."""
344
+
345
+
346
+ class CountriesNotStringNorListStrError(Exception):
347
+ """Raised when countries argument is not provided in correct format."""
348
+
349
+
350
+ class TradingDaysNotAboveZeroError(Exception):
351
+ """Raised when trading days argument is not above zero."""
352
+
353
+
354
+ class BothStartAndEndError(Exception):
355
+ """Raised when both start and end dates are provided."""
356
+
357
+
358
+ class NoWeightsError(Exception):
359
+ """Raised when no weights are provided to function where necessary."""
360
+
361
+
362
+ class LabelsNotUniqueError(Exception):
363
+ """Raised when provided label names are not unique."""
364
+
365
+
366
+ class RatioInputError(Exception):
367
+ """Raised when ratio keyword not provided correctly."""
368
+
369
+
370
+ class MergingResultedInEmptyError(Exception):
371
+ """Raised when a merge resulted in an empty DataFrame."""
372
+
373
+
374
+ class IncorrectArgumentComboError(Exception):
375
+ """Raised when correct combination of arguments is not provided."""
376
+
377
+
378
+ class PropertiesInputValidationError(Exception):
379
+ """Raised when duplicate strings are provided."""
@@ -43,25 +43,25 @@
43
43
  "size": 14
44
44
  },
45
45
  "legend": {
46
- "bgcolor": "rgba(0, 0, 0, 0)",
46
+ "bgcolor": "rgba(255,255,255,1)",
47
47
  "orientation": "h",
48
48
  "x": 0.98,
49
49
  "xanchor": "right",
50
50
  "y": -0.15,
51
51
  "yanchor": "bottom"
52
52
  },
53
- "paper_bgcolor": "rgba(0, 0, 0, 0)",
54
- "plot_bgcolor": "rgba(0, 0, 0, 0)",
53
+ "paper_bgcolor": "rgba(255,255,255,1)",
54
+ "plot_bgcolor": "rgba(255,255,255,1)",
55
55
  "showlegend": true,
56
56
  "title": {
57
+ "font": {
58
+ "size": 32
59
+ },
57
60
  "x": 0.5,
58
61
  "xanchor": "center",
59
62
  "y": 0.95,
60
63
  "yanchor": "top"
61
64
  },
62
- "titlefont": {
63
- "size": 24
64
- },
65
65
  "xaxis": {
66
66
  "gridcolor": "#EEEEEE",
67
67
  "tickangle": -45,
@@ -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,23 +21,24 @@ 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,
29
27
  concat,
30
28
  )
31
- from plotly.graph_objs import Figure # type: ignore[import-untyped,unused-ignore]
32
- from plotly.io import to_html # type: ignore[import-untyped,unused-ignore]
33
- from plotly.offline import plot # type: ignore[import-untyped,unused-ignore]
34
- from scipy.optimize import minimize # type: ignore[import-untyped,unused-ignore]
29
+ from plotly.graph_objs import Figure # type: ignore[import-untyped]
30
+ from plotly.io import to_html # type: ignore[import-untyped]
31
+ from plotly.offline import plot # type: ignore[import-untyped]
32
+ from scipy.optimize import minimize # type: ignore[import-untyped]
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,10 @@ from .series import OpenTimeSeries
47
47
  from .simulation import _random_generator
48
48
 
49
49
  if TYPE_CHECKING: # pragma: no cover
50
+ # noinspection PyUnresolvedReferences
51
+ from collections.abc import Callable
52
+
53
+ from numpy.typing import NDArray
50
54
  from pydantic import DirectoryPath
51
55
 
52
56
  from .frame import OpenFrame
@@ -76,7 +80,7 @@ def simulate_portfolios(
76
80
  seed: int
77
81
  The seed for the random process
78
82
 
79
- Returns
83
+ Returns:
80
84
  -------
81
85
  pandas.DataFrame
82
86
  The resulting data
@@ -92,7 +96,7 @@ def simulate_portfolios(
92
96
  log_ret = copi.tsdf.copy()
93
97
  else:
94
98
  msg = "Mix of series types will give inconsistent results"
95
- raise ValueError(msg)
99
+ raise MixedValuetypesError(msg)
96
100
 
97
101
  log_ret.columns = log_ret.columns.droplevel(level=1)
98
102
 
@@ -128,7 +132,7 @@ def simulate_portfolios(
128
132
 
129
133
 
130
134
  # noinspection PyUnusedLocal
131
- def efficient_frontier( # noqa: C901
135
+ def efficient_frontier(
132
136
  eframe: OpenFrame,
133
137
  num_ports: int = 5000,
134
138
  seed: int = 71,
@@ -157,7 +161,7 @@ def efficient_frontier( # noqa: C901
157
161
  tweak: bool, default: True
158
162
  cutting the frontier to exclude multiple points with almost the same risk
159
163
 
160
- Returns
164
+ Returns:
161
165
  -------
162
166
  tuple[DataFrame, DataFrame, NDArray[float]]
163
167
  The efficient frontier data, simulation data and optimal portfolio
@@ -176,7 +180,7 @@ def efficient_frontier( # noqa: C901
176
180
  log_ret = copi.tsdf.copy()
177
181
  else:
178
182
  msg = "Mix of series types will give inconsistent results"
179
- raise ValueError(msg)
183
+ raise MixedValuetypesError(msg)
180
184
 
181
185
  log_ret.columns = log_ret.columns.droplevel(level=1)
182
186
 
@@ -189,8 +193,8 @@ def efficient_frontier( # noqa: C901
189
193
 
190
194
  frontier_max = cleaned_arithmetic_means.max()
191
195
 
192
- def _check_sum(weights: NDArray[float64]) -> float64:
193
- return npsum(weights) - 1
196
+ def _check_sum(weights: NDArray[float64]) -> float:
197
+ return cast("float", npsum(weights) - 1)
194
198
 
195
199
  def _get_ret_vol_sr(
196
200
  lg_ret: DataFrame,
@@ -200,7 +204,7 @@ def efficient_frontier( # noqa: C901
200
204
  ret = npsum(lg_ret.mean() * weights) * per_in_yr
201
205
  volatility = sqrt(weights.T @ (lg_ret.cov() * per_in_yr @ weights))
202
206
  sr = ret / volatility
203
- return cast(NDArray[float64], array([ret, volatility, sr]))
207
+ return cast("NDArray[float64]", array([ret, volatility, sr]))
204
208
 
205
209
  def _diff_return(
206
210
  lg_ret: DataFrame,
@@ -209,14 +213,14 @@ def efficient_frontier( # noqa: C901
209
213
  poss_return: float,
210
214
  ) -> float64:
211
215
  return cast(
212
- float64,
216
+ "float64",
213
217
  _get_ret_vol_sr(lg_ret=lg_ret, weights=weights, per_in_yr=per_in_yr)[0]
214
218
  - poss_return,
215
219
  )
216
220
 
217
221
  def _neg_sharpe(weights: NDArray[float64]) -> float64:
218
222
  return cast(
219
- float64,
223
+ "float64",
220
224
  _get_ret_vol_sr(
221
225
  lg_ret=log_ret,
222
226
  weights=weights,
@@ -229,7 +233,7 @@ def efficient_frontier( # noqa: C901
229
233
  weights: NDArray[float64],
230
234
  ) -> float64:
231
235
  return cast(
232
- float64,
236
+ "float64",
233
237
  _get_ret_vol_sr(
234
238
  lg_ret=log_ret,
235
239
  weights=weights,
@@ -262,7 +266,7 @@ def efficient_frontier( # noqa: C901
262
266
 
263
267
  for possible_return in frontier_y:
264
268
  cons = cast(
265
- dict[str, str | Callable[[float, NDArray[float64]], float64]],
269
+ "dict[str, str | Callable[[float, NDArray[float64]], float64]]",
266
270
  (
267
271
  {"type": "eq", "fun": _check_sum},
268
272
  {
@@ -344,7 +348,7 @@ def constrain_optimized_portfolios(
344
348
  minimize_method: LiteralMinimizeMethods, default: SLSQP
345
349
  The method passed into the scipy.minimize function
346
350
 
347
- Returns
351
+ Returns:
348
352
  -------
349
353
  tuple[OpenFrame, OpenTimeSeries, OpenFrame, OpenTimeSeries]
350
354
  The constrained optimal portfolio data
@@ -404,7 +408,7 @@ def prepare_plot_data(
404
408
  optimized: DataFrame
405
409
  Data optimized with the efficient_frontier method
406
410
 
407
- Returns
411
+ Returns:
408
412
  -------
409
413
  DataFrame
410
414
  The data prepared with mean returns, volatility and weights
@@ -414,7 +418,7 @@ def prepare_plot_data(
414
418
  [
415
419
  f"{wgt:.1%} {nm}"
416
420
  for wgt, nm in zip(
417
- cast(list[float], assets.weights),
421
+ cast("list[float]", assets.weights),
418
422
  assets.columns_lvl_zero,
419
423
  strict=True,
420
424
  )
@@ -445,7 +449,7 @@ def prepare_plot_data(
445
449
  return plotframe
446
450
 
447
451
 
448
- def sharpeplot( # noqa: C901
452
+ def sharpeplot(
449
453
  sim_frame: DataFrame | None = None,
450
454
  line_frame: DataFrame | None = None,
451
455
  point_frame: DataFrame | None = None,
@@ -489,7 +493,7 @@ def sharpeplot( # noqa: C901
489
493
  auto_open: bool, default: True
490
494
  Determines whether to open a browser window with the plot
491
495
 
492
- Returns
496
+ Returns:
493
497
  -------
494
498
  Figure
495
499
  The scatter plot with simulated and optimized results
@@ -514,7 +518,7 @@ def sharpeplot( # noqa: C901
514
518
 
515
519
  if sim_frame is None and line_frame is None and point_frame is None:
516
520
  msg = "One of sim_frame, line_frame or point_frame must be provided."
517
- raise ValueError(msg)
521
+ raise AtLeastOneFrameError(msg)
518
522
 
519
523
  if sim_frame is not None:
520
524
  returns.extend(list(sim_frame.loc[:, "ret"]))
@@ -552,12 +556,12 @@ def sharpeplot( # noqa: C901
552
556
 
553
557
  if point_frame is not None:
554
558
  colorway = cast(
555
- dict[str, str | int | float | bool | list[str]],
559
+ "dict[str, str | int | float | bool | list[str]]",
556
560
  fig["layout"],
557
561
  ).get("colorway")[: len(point_frame.columns)]
558
562
  for col, clr in zip(point_frame.columns, colorway, strict=True):
559
- returns.extend([point_frame.loc["ret", col]])
560
- risk.extend([point_frame.loc["stdev", col]])
563
+ returns.extend([cast("float", point_frame.loc["ret", col])])
564
+ risk.extend([cast("float", point_frame.loc["stdev", col])])
561
565
  figure.add_scatter(
562
566
  x=[point_frame.loc["stdev", col]],
563
567
  y=[point_frame.loc["ret", col]],
@@ -600,7 +604,7 @@ def sharpeplot( # noqa: C901
600
604
  auto_open=auto_open,
601
605
  auto_play=False,
602
606
  link_text="",
603
- include_plotlyjs=cast(bool, include_plotlyjs),
607
+ include_plotlyjs=cast("bool", include_plotlyjs),
604
608
  config=fig["config"],
605
609
  output_type=output_type,
606
610
  )
@@ -611,7 +615,7 @@ def sharpeplot( # noqa: C901
611
615
  fig=figure,
612
616
  config=fig["config"],
613
617
  auto_play=False,
614
- include_plotlyjs=cast(bool, include_plotlyjs),
618
+ include_plotlyjs=cast("bool", include_plotlyjs),
615
619
  full_html=False,
616
620
  div_id=div_id,
617
621
  )