openseries 1.8.2__tar.gz → 1.8.3__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.2
3
+ Version: 1.8.3
4
4
  Summary: Tools for analyzing financial timeseries.
5
5
  License: # BSD 3-Clause License
6
6
 
@@ -330,8 +330,9 @@ make lint
330
330
  | `value_to_diff` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a series of differences. |
331
331
  | `value_to_log` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a logarithmic return series. |
332
332
  | `value_ret_calendar_period` | `OpenTimeSeries`, `OpenFrame` | Returns the series simple return for a specific calendar period. |
333
- | `plot_series` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Scatter](https://plotly.com/python/line-and-scatter/) plot of the series in a browser window. |
334
- | `plot_bars` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Bar](https://plotly.com/python/bar-charts/) plot of the series in a browser window. |
333
+ | `plot_series` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Scatter](https://plotly.com/python/line-and-scatter/) plot of the serie(s) in a browser window. |
334
+ | `plot_bars` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Bar](https://plotly.com/python/bar-charts/) plot of the serie(s) in a browser window. |
335
+ | `plot_histogram` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Histogram](https://plotly.com/python/histograms/) plot of the serie(s) in a browser window. |
335
336
  | `to_drawdown_series` | `OpenTimeSeries`, `OpenFrame` | Converts the series into drawdown series. |
336
337
  | `rolling_return` | `OpenTimeSeries`, `OpenFrame` | Returns a pandas.DataFrame with rolling returns. |
337
338
  | `rolling_vol` | `OpenTimeSeries`, `OpenFrame` | Returns a pandas.DataFrame with rolling volatilities. |
@@ -265,8 +265,9 @@ make lint
265
265
  | `value_to_diff` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a series of differences. |
266
266
  | `value_to_log` | `OpenTimeSeries`, `OpenFrame` | Converts a value series into a logarithmic return series. |
267
267
  | `value_ret_calendar_period` | `OpenTimeSeries`, `OpenFrame` | Returns the series simple return for a specific calendar period. |
268
- | `plot_series` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Scatter](https://plotly.com/python/line-and-scatter/) plot of the series in a browser window. |
269
- | `plot_bars` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Bar](https://plotly.com/python/bar-charts/) plot of the series in a browser window. |
268
+ | `plot_series` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Scatter](https://plotly.com/python/line-and-scatter/) plot of the serie(s) in a browser window. |
269
+ | `plot_bars` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Bar](https://plotly.com/python/bar-charts/) plot of the serie(s) in a browser window. |
270
+ | `plot_histogram` | `OpenTimeSeries`, `OpenFrame` | Opens a HTML [Plotly Histogram](https://plotly.com/python/histograms/) plot of the serie(s) in a browser window. |
270
271
  | `to_drawdown_series` | `OpenTimeSeries`, `OpenFrame` | Converts the series into drawdown series. |
271
272
  | `rolling_return` | `OpenTimeSeries`, `OpenFrame` | Returns a pandas.DataFrame with rolling returns. |
272
273
  | `rolling_vol` | `OpenTimeSeries`, `OpenFrame` | Returns a pandas.DataFrame with rolling volatilities. |
@@ -33,6 +33,10 @@ if TYPE_CHECKING: # pragma: no cover
33
33
  LiteralLinePlotMode,
34
34
  LiteralNanMethod,
35
35
  LiteralPandasReindexMethod,
36
+ LiteralPlotlyHistogramBarMode,
37
+ LiteralPlotlyHistogramCurveType,
38
+ LiteralPlotlyHistogramHistNorm,
39
+ LiteralPlotlyHistogramPlotType,
36
40
  LiteralPlotlyJSlib,
37
41
  LiteralPlotlyOutput,
38
42
  LiteralQuantileInterp,
@@ -50,6 +54,7 @@ from pandas import (
50
54
  to_datetime,
51
55
  )
52
56
  from pandas.tseries.offsets import CustomBusinessDay
57
+ from plotly.figure_factory import create_distplot # type: ignore[import-untyped]
53
58
  from plotly.graph_objs import Figure # type: ignore[import-untyped]
54
59
  from plotly.io import to_html # type: ignore[import-untyped]
55
60
  from plotly.offline import plot # type: ignore[import-untyped]
@@ -1005,6 +1010,174 @@ class _CommonModel(BaseModel): # type: ignore[misc]
1005
1010
 
1006
1011
  return figure, string_output
1007
1012
 
1013
+ def plot_histogram(
1014
+ self: Self,
1015
+ plot_type: LiteralPlotlyHistogramPlotType = "bars",
1016
+ histnorm: LiteralPlotlyHistogramHistNorm = "percent",
1017
+ barmode: LiteralPlotlyHistogramBarMode = "overlay",
1018
+ xbins_size: float | None = None,
1019
+ opacity: float = 0.75,
1020
+ bargap: float = 0.0,
1021
+ bargroupgap: float = 0.0,
1022
+ curve_type: LiteralPlotlyHistogramCurveType = "kde",
1023
+ x_fmt: str | None = None,
1024
+ y_fmt: str | None = None,
1025
+ filename: str | None = None,
1026
+ directory: DirectoryPath | None = None,
1027
+ labels: list[str] | None = None,
1028
+ output_type: LiteralPlotlyOutput = "file",
1029
+ include_plotlyjs: LiteralPlotlyJSlib = "cdn",
1030
+ *,
1031
+ cumulative: bool = False,
1032
+ show_rug: bool = False,
1033
+ auto_open: bool = True,
1034
+ add_logo: bool = True,
1035
+ ) -> tuple[Figure, str]:
1036
+ """Create a Plotly Histogram Figure.
1037
+
1038
+ Parameters
1039
+ ----------
1040
+ plot_type: LiteralPlotlyHistogramPlotType, default: bars
1041
+ Type of plot
1042
+ histnorm: LiteralPlotlyHistogramHistNorm, default: percent
1043
+ Sets the normalization mode
1044
+ barmode: LiteralPlotlyHistogramBarMode, default: overlay
1045
+ Specifies how bar traces are displayed relative to one another
1046
+ xbins_size: float, optional
1047
+ Explicitly sets the width of each bin along the x-axis in data units
1048
+ opacity: float, default: 0.75
1049
+ Sets the trace opacity, must be between 0 (fully transparent) and 1
1050
+ bargap: float, default: 0.0
1051
+ Sets the gap between bars of adjacent location coordinates
1052
+ bargroupgap: float, default: 0.0
1053
+ Sets the gap between bar “groups” at the same location coordinate
1054
+ curve_type: LiteralPlotlyHistogramCurveType, default: kde
1055
+ Specifies the type of distribution curve to overlay on the histogram
1056
+ y_fmt: str, optional
1057
+ None, '%', '.1%' depending on number of decimals to show on the y-axis
1058
+ x_fmt: str, optional
1059
+ None, '%', '.1%' depending on number of decimals to show on the x-axis
1060
+ filename: str, optional
1061
+ Name of the Plotly html file
1062
+ directory: DirectoryPath, optional
1063
+ Directory where Plotly html file is saved
1064
+ labels: list[str], optional
1065
+ A list of labels to manually override using the names of
1066
+ the input self.tsdf
1067
+ output_type: LiteralPlotlyOutput, default: "file"
1068
+ Determines output type
1069
+ include_plotlyjs: LiteralPlotlyJSlib, default: "cdn"
1070
+ Determines how the plotly.js library is included in the output
1071
+ cumulative: bool, default: False
1072
+ Determines whether to compute a cumulative histogram
1073
+ show_rug: bool, default: False
1074
+ Determines whether to draw a rug plot alongside the distribution
1075
+ auto_open: bool, default: True
1076
+ Determines whether to open a browser window with the plot
1077
+ add_logo: bool, default: True
1078
+ If True a Captor logo is added to the plot
1079
+
1080
+ Returns:
1081
+ -------
1082
+ tuple[plotly.go.Figure, str]
1083
+ Plotly Figure and a div section or a html filename with location
1084
+
1085
+ """
1086
+ if labels:
1087
+ if len(labels) != self.tsdf.shape[1]:
1088
+ msg = "Must provide same number of labels as items in frame."
1089
+ raise NumberOfItemsAndLabelsNotSameError(msg)
1090
+ else:
1091
+ labels = list(self.tsdf.columns.get_level_values(0))
1092
+
1093
+ if directory:
1094
+ dirpath = Path(directory).resolve()
1095
+ elif Path.home().joinpath("Documents").exists():
1096
+ dirpath = Path.home().joinpath("Documents")
1097
+ else:
1098
+ dirpath = Path(stack()[1].filename).parent
1099
+
1100
+ if not filename:
1101
+ filename = "".join(choice(ascii_letters) for _ in range(6)) + ".html"
1102
+ plotfile = dirpath.joinpath(filename)
1103
+
1104
+ fig_dict, logo = load_plotly_dict()
1105
+
1106
+ hovertemplate = f"Count: %{{y:{y_fmt}}}" if y_fmt else "Count: %{y}"
1107
+
1108
+ if x_fmt:
1109
+ hovertemplate += f"<br>%{{x:{x_fmt}}}"
1110
+ else:
1111
+ hovertemplate += "<br>%{x}"
1112
+
1113
+ msg = "plot_type must be 'bars' or 'lines'."
1114
+ if plot_type == "bars":
1115
+ figure = Figure(fig_dict)
1116
+ for item in range(self.tsdf.shape[1]):
1117
+ figure.add_histogram(
1118
+ x=self.tsdf.iloc[:, item],
1119
+ cumulative={"enabled": cumulative},
1120
+ histnorm=histnorm,
1121
+ name=labels[item],
1122
+ xbins={"size": xbins_size},
1123
+ opacity=opacity,
1124
+ hovertemplate=hovertemplate,
1125
+ )
1126
+ figure.update_layout(
1127
+ barmode=barmode,
1128
+ bargap=bargap,
1129
+ bargroupgap=bargroupgap,
1130
+ )
1131
+ elif plot_type == "lines":
1132
+ hist_data = [
1133
+ cast("Series[float]", self.tsdf.loc[:, ds]).dropna().tolist()
1134
+ for ds in self.tsdf
1135
+ ]
1136
+ figure = create_distplot(
1137
+ hist_data=hist_data,
1138
+ curve_type=curve_type,
1139
+ group_labels=labels,
1140
+ show_hist=False,
1141
+ show_rug=show_rug,
1142
+ histnorm=histnorm,
1143
+ )
1144
+ figure.update_layout(dict1=fig_dict["layout"])
1145
+ else:
1146
+ raise TypeError(msg)
1147
+
1148
+ figure.update_layout(xaxis={"tickformat": x_fmt}, yaxis={"tickformat": y_fmt})
1149
+
1150
+ figure.update_xaxes(zeroline=True, zerolinewidth=2, zerolinecolor="lightgrey")
1151
+ figure.update_yaxes(zeroline=True, zerolinewidth=2, zerolinecolor="lightgrey")
1152
+
1153
+ if add_logo:
1154
+ figure.add_layout_image(logo)
1155
+
1156
+ if output_type == "file":
1157
+ plot(
1158
+ figure_or_data=figure,
1159
+ filename=str(plotfile),
1160
+ auto_open=auto_open,
1161
+ auto_play=False,
1162
+ link_text="",
1163
+ include_plotlyjs=cast("bool", include_plotlyjs),
1164
+ config=fig_dict["config"],
1165
+ output_type=output_type,
1166
+ )
1167
+ string_output = str(plotfile)
1168
+ else:
1169
+ div_id = filename.rsplit(".", 1)[0]
1170
+ string_output = to_html(
1171
+ fig=figure,
1172
+ config=fig_dict["config"],
1173
+ auto_play=False,
1174
+ include_plotlyjs=cast("bool", include_plotlyjs),
1175
+ full_html=False,
1176
+ div_id=div_id,
1177
+ )
1178
+
1179
+ return figure, string_output
1180
+
1008
1181
  def arithmetic_ret_func(
1009
1182
  self: Self,
1010
1183
  months_from_last: int | None = None,
@@ -135,6 +135,15 @@ LiteralCaptureRatio = Literal["up", "down", "both"]
135
135
  LiteralBarPlotMode = Literal["stack", "group", "overlay", "relative"]
136
136
  LiteralPlotlyOutput = Literal["file", "div"]
137
137
  LiteralPlotlyJSlib = Literal[True, False, "cdn"]
138
+ LiteralPlotlyHistogramPlotType = Literal["bars", "lines"]
139
+ LiteralPlotlyHistogramBarMode = Literal["stack", "group", "overlay", "relative"]
140
+ LiteralPlotlyHistogramCurveType = Literal["normal", "kde"]
141
+ LiteralPlotlyHistogramHistNorm = Literal[
142
+ "percent",
143
+ "probability",
144
+ "density",
145
+ "probability density",
146
+ ]
138
147
  LiteralOlsFitMethod = Literal["pinv", "qr"]
139
148
  LiteralPortfolioWeightings = Literal["eq_weights", "inv_vol"]
140
149
  LiteralOlsFitCovType = Literal[
@@ -43,7 +43,7 @@
43
43
  "size": 14
44
44
  },
45
45
  "legend": {
46
- "bgcolor": "rgba(255,255,255,1)",
46
+ "bgcolor": "rgba(0,0,0,0)",
47
47
  "orientation": "h",
48
48
  "x": 0.98,
49
49
  "xanchor": "right",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openseries"
3
- version = "1.8.2"
3
+ version = "1.8.3"
4
4
  description = "Tools for analyzing financial timeseries."
5
5
  authors = [
6
6
  { name = "Martin Karrin", email = "martin.karrin@captor.se" },
@@ -67,13 +67,13 @@ mypy = "1.15.0"
67
67
  pandas-stubs = ">=2.1.2,<3.0.0"
68
68
  pre-commit = ">=3.7.1,<6.0.0"
69
69
  pytest = ">=8.2.2,<9.0.0"
70
- ruff = "0.11.8"
70
+ ruff = "0.11.9"
71
71
  types-openpyxl = ">=3.1.2,<5.0.0"
72
72
  types-python-dateutil = ">=2.8.2,<4.0.0"
73
73
  types-requests = ">=2.20.0,<3.0.0"
74
74
 
75
75
  [build-system]
76
- requires = ["poetry-core>=2.1.2"]
76
+ requires = ["poetry-core>=2.1.3"]
77
77
  build-backend = "poetry.core.masonry.api"
78
78
 
79
79
  [tool.coverage.run]
@@ -131,7 +131,7 @@ ignore = ["COM812", "D203", "D213"]
131
131
  fixable = ["ALL"]
132
132
  mccabe = { max-complexity = 15 }
133
133
  pydocstyle = { convention = "google" }
134
- pylint = { max-args = 12, max-branches = 23, max-statements = 59 }
134
+ pylint = { max-args = 19, max-branches = 23, max-statements = 59 }
135
135
 
136
136
  [tool.pytest.ini_options]
137
137
  testpaths = "tests"
File without changes