openbb-charting 2.0.0__tar.gz → 2.0.2__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.
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/PKG-INFO +2 -2
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/__init__.py +33 -20
- openbb_charting-2.0.2/openbb_charting/charting_router.py +538 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/backend.py +7 -1
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/openbb_figure.py +1 -5
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/base.py +16 -8
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/data_classes.py +33 -23
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/ta_class.py +12 -6
- openbb_charting-2.0.2/openbb_charting/query_params.py +105 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/pyproject.toml +2 -2
- openbb_charting-2.0.0/openbb_charting/charting_router.py +0 -176
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/README.md +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/builder.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/__init__.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/assets/Terminal_icon.png +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/assets/plotly-2.24.2.min.js +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/chart_style.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/config/__init__.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/config/openbb_styles.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/dummy_backend.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly.html +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/__init__.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/plugins/custom_indicators_plugin.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/plugins/momentum_plugin.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/plugins/overlap_plugin.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/plugins/trend_indicators_plugin.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/plugins/volatility_plugin.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/plugins/volume_plugin.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/ta_helpers.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/table.html +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/to_chart.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/styles/default/dark.pltstyle.json +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/styles/default/light.pltstyle.json +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/styles/default/tables.pltstyle.json +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/utils/__init__.py +0 -0
- {openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/utils/helpers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: openbb-charting
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Charting extension for OpenBB
|
|
5
5
|
Author: OpenBB Team
|
|
6
6
|
Author-email: hello@openbb.co
|
|
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Requires-Dist: nbformat (>=5.9.2,<6.0.0)
|
|
14
|
-
Requires-Dist: openbb-core (>=1.1.
|
|
14
|
+
Requires-Dist: openbb-core (>=1.1.5,<2.0.0)
|
|
15
15
|
Requires-Dist: pandas-ta (>=0.3.14b,<0.4.0)
|
|
16
16
|
Requires-Dist: plotly (>=5.17.0,<6.0.0)
|
|
17
17
|
Requires-Dist: pywry (>=0.6.1,<0.7.0)
|
|
@@ -6,7 +6,6 @@ from typing import (
|
|
|
6
6
|
Dict,
|
|
7
7
|
List,
|
|
8
8
|
Optional,
|
|
9
|
-
Tuple,
|
|
10
9
|
Union,
|
|
11
10
|
)
|
|
12
11
|
|
|
@@ -18,7 +17,7 @@ from openbb_core.app.utils import basemodel_to_df, convert_to_basemodel
|
|
|
18
17
|
from openbb_core.provider.abstract.data import Data
|
|
19
18
|
|
|
20
19
|
from openbb_charting import charting_router
|
|
21
|
-
from openbb_charting.core.to_chart import ChartIndicators,
|
|
20
|
+
from openbb_charting.core.to_chart import ChartIndicators, to_chart
|
|
22
21
|
from openbb_charting.utils.helpers import get_charting_functions
|
|
23
22
|
|
|
24
23
|
ext = Extension(name="charting")
|
|
@@ -49,8 +48,8 @@ class Charting:
|
|
|
49
48
|
|
|
50
49
|
self._obbject: OBBject = obbject
|
|
51
50
|
self._charting_settings = ChartingSettings(
|
|
52
|
-
user_settings=self._obbject._user_settings,
|
|
53
|
-
system_settings=self._obbject._system_settings,
|
|
51
|
+
user_settings=self._obbject._user_settings, # type: ignore
|
|
52
|
+
system_settings=self._obbject._system_settings, # type: ignore
|
|
54
53
|
)
|
|
55
54
|
self._handle_backend()
|
|
56
55
|
|
|
@@ -91,7 +90,9 @@ class Charting:
|
|
|
91
90
|
kwargs["standard_params"] = (
|
|
92
91
|
self._obbject._standard_params.__dict__ # pylint: disable=protected-access
|
|
93
92
|
)
|
|
94
|
-
|
|
93
|
+
kwargs["provider"] = self._obbject.provider # pylint: disable=protected-access
|
|
94
|
+
kwargs["extra"] = self._obbject.extra # pylint: disable=protected-access
|
|
95
|
+
kwargs["warnings"] = self._obbject.warnings # pylint: disable=protected-access
|
|
95
96
|
fig, content = charting_function(**kwargs)
|
|
96
97
|
self._obbject.chart = Chart(
|
|
97
98
|
fig=fig, content=content, format=charting_router.CHART_FORMAT
|
|
@@ -178,24 +179,36 @@ class Charting:
|
|
|
178
179
|
index = (
|
|
179
180
|
data.index.name
|
|
180
181
|
if has_data and isinstance(data, (pd.DataFrame, pd.Series))
|
|
181
|
-
else
|
|
182
|
+
else None
|
|
182
183
|
)
|
|
183
184
|
data_as_df: pd.DataFrame = (
|
|
184
185
|
basemodel_to_df(convert_to_basemodel(data), index=index)
|
|
185
186
|
if has_data
|
|
186
187
|
else self._obbject.to_dataframe()
|
|
187
188
|
)
|
|
188
|
-
|
|
189
|
-
data_as_df
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
189
|
+
if "date" in data_as_df.columns:
|
|
190
|
+
data_as_df = data_as_df.set_index("date")
|
|
191
|
+
try:
|
|
192
|
+
fig, content = to_chart(
|
|
193
|
+
data_as_df,
|
|
194
|
+
indicators=indicators,
|
|
195
|
+
symbol=symbol,
|
|
196
|
+
candles=candles,
|
|
197
|
+
volume=volume,
|
|
198
|
+
prepost=prepost,
|
|
199
|
+
volume_ticks_x=volume_ticks_x,
|
|
200
|
+
)
|
|
201
|
+
self._obbject.chart = Chart(
|
|
202
|
+
fig=fig, content=content, format=charting_router.CHART_FORMAT
|
|
203
|
+
)
|
|
204
|
+
if render:
|
|
205
|
+
fig.show(**kwargs)
|
|
206
|
+
|
|
207
|
+
except Exception:
|
|
208
|
+
try:
|
|
209
|
+
if has_data:
|
|
210
|
+
self.show(data=data_as_df, symbol=symbol, render=render, **kwargs)
|
|
211
|
+
else:
|
|
212
|
+
self.show(**kwargs)
|
|
213
|
+
except Exception as e:
|
|
214
|
+
raise RuntimeError("Could not create chart from the OBBject.") from e
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
"""Charting router."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from openbb_core.app.model.charts.chart import ChartFormat
|
|
8
|
+
from openbb_core.app.utils import basemodel_to_df
|
|
9
|
+
|
|
10
|
+
from openbb_charting.core.chart_style import ChartStyle
|
|
11
|
+
from openbb_charting.core.openbb_figure import OpenBBFigure
|
|
12
|
+
from openbb_charting.core.plotly_ta.ta_class import PlotlyTA
|
|
13
|
+
from openbb_charting.query_params import (
|
|
14
|
+
FredSeriesChartQueryParams,
|
|
15
|
+
TechnicalConesChartQueryParams,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
CHART_FORMAT = ChartFormat.plotly
|
|
19
|
+
|
|
20
|
+
# if TYPE_CHECKING:
|
|
21
|
+
|
|
22
|
+
# from .core.openbb_figure_table import OpenBBFigureTable
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def equity_price_historical(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
26
|
+
"""Equity price chart."""
|
|
27
|
+
|
|
28
|
+
def handle_indicators(ma):
|
|
29
|
+
"""Handle indicators."""
|
|
30
|
+
k = {}
|
|
31
|
+
if ma:
|
|
32
|
+
k["rma"] = dict(length=ma)
|
|
33
|
+
return k
|
|
34
|
+
|
|
35
|
+
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
36
|
+
standard_params = kwargs["standard_params"]
|
|
37
|
+
ma = standard_params.get("ma", None)
|
|
38
|
+
prepost = standard_params.get("prepost", False)
|
|
39
|
+
symbol = standard_params.get("symbol", "")
|
|
40
|
+
|
|
41
|
+
ta = PlotlyTA()
|
|
42
|
+
fig = ta.plot(
|
|
43
|
+
data,
|
|
44
|
+
indicators=dict(**handle_indicators(ma)),
|
|
45
|
+
symbol=f"{symbol} historical data",
|
|
46
|
+
prepost=prepost,
|
|
47
|
+
)
|
|
48
|
+
content = fig.show(external=True).to_plotly_json()
|
|
49
|
+
|
|
50
|
+
return fig, content
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _ta_ma(ma_type: str, **kwargs):
|
|
54
|
+
"""Plot moving average helper."""
|
|
55
|
+
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
56
|
+
window = kwargs.get("window", 50)
|
|
57
|
+
offset = kwargs.get("offset", 0)
|
|
58
|
+
symbol = kwargs.get("symbol", "")
|
|
59
|
+
|
|
60
|
+
ta = PlotlyTA()
|
|
61
|
+
fig = ta.plot(
|
|
62
|
+
data,
|
|
63
|
+
{f"{ma_type.lower()}": dict(length=window, offset=offset)},
|
|
64
|
+
f"{symbol.upper()} {ma_type.upper()}",
|
|
65
|
+
False,
|
|
66
|
+
volume=False,
|
|
67
|
+
)
|
|
68
|
+
fig.update_layout(ChartStyle().plotly_template.get("layout", {}))
|
|
69
|
+
content = fig.show(external=True).to_plotly_json()
|
|
70
|
+
|
|
71
|
+
return fig, content
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def technical_zlma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
75
|
+
"""Zero lag moving average chart."""
|
|
76
|
+
ma_type = "zlma"
|
|
77
|
+
return _ta_ma(ma_type, **kwargs)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def technical_aroon(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
81
|
+
"""Aroon chart."""
|
|
82
|
+
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
83
|
+
length = kwargs.get("length", 25)
|
|
84
|
+
scalar = kwargs.get("scalar", 100)
|
|
85
|
+
symbol = kwargs.get("symbol", "")
|
|
86
|
+
|
|
87
|
+
ta = PlotlyTA()
|
|
88
|
+
fig = ta.plot(
|
|
89
|
+
data,
|
|
90
|
+
dict(aroon=dict(length=length, scalar=scalar)),
|
|
91
|
+
f"Aroon on {symbol}",
|
|
92
|
+
False,
|
|
93
|
+
volume=False,
|
|
94
|
+
)
|
|
95
|
+
content = fig.show(external=True).to_plotly_json()
|
|
96
|
+
|
|
97
|
+
return fig, content
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def technical_sma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
101
|
+
"""Plot simple moving average chart."""
|
|
102
|
+
ma_type = "sma"
|
|
103
|
+
return _ta_ma(ma_type, **kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def technical_macd(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
107
|
+
"""Plot moving average convergence divergence chart."""
|
|
108
|
+
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
109
|
+
fast = kwargs.get("fast", 12)
|
|
110
|
+
slow = kwargs.get("slow", 26)
|
|
111
|
+
signal = kwargs.get("signal", 9)
|
|
112
|
+
symbol = kwargs.get("symbol", "")
|
|
113
|
+
|
|
114
|
+
ta = PlotlyTA()
|
|
115
|
+
fig = ta.plot(
|
|
116
|
+
data,
|
|
117
|
+
dict(macd=dict(fast=fast, slow=slow, signal=signal)),
|
|
118
|
+
f"{symbol.upper()} MACD",
|
|
119
|
+
False,
|
|
120
|
+
volume=False,
|
|
121
|
+
)
|
|
122
|
+
content = fig.show(external=True).to_plotly_json()
|
|
123
|
+
|
|
124
|
+
return fig, content
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def technical_hma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
128
|
+
"""Hull moving average chart."""
|
|
129
|
+
ma_type = "hma"
|
|
130
|
+
return _ta_ma(ma_type, **kwargs)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def technical_adx(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
134
|
+
"""Average directional movement index chart."""
|
|
135
|
+
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
136
|
+
length = kwargs.get("length", 14)
|
|
137
|
+
scalar = kwargs.get("scalar", 100.0)
|
|
138
|
+
drift = kwargs.get("drift", 1)
|
|
139
|
+
symbol = kwargs.get("symbol", "")
|
|
140
|
+
|
|
141
|
+
ta = PlotlyTA()
|
|
142
|
+
fig = ta.plot(
|
|
143
|
+
data,
|
|
144
|
+
dict(adx=dict(length=length, scalar=scalar, drift=drift)),
|
|
145
|
+
f"Average Directional Movement Index (ADX) {symbol}",
|
|
146
|
+
False,
|
|
147
|
+
volume=False,
|
|
148
|
+
)
|
|
149
|
+
content = fig.show(external=True).to_plotly_json()
|
|
150
|
+
|
|
151
|
+
return fig, content
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def technical_wma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
155
|
+
"""Weighted moving average chart."""
|
|
156
|
+
ma_type = "wma"
|
|
157
|
+
return _ta_ma(ma_type, **kwargs)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def technical_rsi(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
161
|
+
"""Relative strength index chart."""
|
|
162
|
+
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
163
|
+
window = kwargs.get("window", 14)
|
|
164
|
+
scalar = kwargs.get("scalar", 100.0)
|
|
165
|
+
drift = kwargs.get("drift", 1)
|
|
166
|
+
symbol = kwargs.get("symbol", "")
|
|
167
|
+
|
|
168
|
+
ta = PlotlyTA()
|
|
169
|
+
fig = ta.plot(
|
|
170
|
+
data,
|
|
171
|
+
dict(rsi=dict(length=window, scalar=scalar, drift=drift)),
|
|
172
|
+
f"{symbol.upper()} RSI {window}",
|
|
173
|
+
False,
|
|
174
|
+
volume=False,
|
|
175
|
+
)
|
|
176
|
+
content = fig.show(external=True).to_plotly_json()
|
|
177
|
+
|
|
178
|
+
return fig, content
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def technical_ema(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
182
|
+
"""Exponential moving average chart."""
|
|
183
|
+
ma_type = "ema"
|
|
184
|
+
return _ta_ma(ma_type, **kwargs)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def technical_cones(
|
|
188
|
+
**kwargs: TechnicalConesChartQueryParams,
|
|
189
|
+
) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
190
|
+
"""Volatility Cones Chart."""
|
|
191
|
+
data = kwargs.get("data")
|
|
192
|
+
|
|
193
|
+
if isinstance(data, pd.DataFrame) and not data.empty and "window" in data.columns:
|
|
194
|
+
df_ta = data.set_index("window")
|
|
195
|
+
else:
|
|
196
|
+
df_ta = basemodel_to_df(kwargs["obbject_item"], index="window") # type: ignore
|
|
197
|
+
|
|
198
|
+
df_ta.columns = [col.title().replace("_", " ") for col in df_ta.columns]
|
|
199
|
+
|
|
200
|
+
# Check if the data is formatted as expected.
|
|
201
|
+
if not all(col in df_ta.columns for col in ["Realized", "Min", "Median", "Max"]):
|
|
202
|
+
raise ValueError("Data supplied does not match the expected format.")
|
|
203
|
+
|
|
204
|
+
model = (
|
|
205
|
+
str(kwargs.get("model"))
|
|
206
|
+
.replace("std", "Standard Deviation")
|
|
207
|
+
.replace("_", "-")
|
|
208
|
+
.title()
|
|
209
|
+
if kwargs.get("model")
|
|
210
|
+
else "Standard Deviation"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
symbol = str(kwargs.get("symbol")) + " - " if kwargs.get("symbol") else ""
|
|
214
|
+
|
|
215
|
+
title = (
|
|
216
|
+
str(kwargs.get("title"))
|
|
217
|
+
if kwargs.get("title")
|
|
218
|
+
else f"{symbol}Realized Volatility Cones - {model} Model"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
colors = [
|
|
222
|
+
"green",
|
|
223
|
+
"red",
|
|
224
|
+
"burlywood",
|
|
225
|
+
"grey",
|
|
226
|
+
"orange",
|
|
227
|
+
"blue",
|
|
228
|
+
]
|
|
229
|
+
color = 0
|
|
230
|
+
|
|
231
|
+
fig = OpenBBFigure()
|
|
232
|
+
|
|
233
|
+
fig.update_layout(ChartStyle().plotly_template.get("layout", {}))
|
|
234
|
+
|
|
235
|
+
text_color = "black" if ChartStyle().plt_style == "light" else "white"
|
|
236
|
+
|
|
237
|
+
for col in df_ta.columns:
|
|
238
|
+
fig.add_scatter(
|
|
239
|
+
x=df_ta.index,
|
|
240
|
+
y=df_ta[col],
|
|
241
|
+
name=col,
|
|
242
|
+
mode="lines+markers",
|
|
243
|
+
hovertemplate=f"{col}: %{{y}}<extra></extra>",
|
|
244
|
+
marker=dict(
|
|
245
|
+
color=colors[color],
|
|
246
|
+
size=11,
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
color += 1
|
|
250
|
+
|
|
251
|
+
fig.set_title(title)
|
|
252
|
+
|
|
253
|
+
fig.update_layout(
|
|
254
|
+
paper_bgcolor="rgba(0,0,0,0)",
|
|
255
|
+
plot_bgcolor="rgba(0,0,0,0)",
|
|
256
|
+
font=dict(color=text_color),
|
|
257
|
+
legend=dict(
|
|
258
|
+
orientation="h",
|
|
259
|
+
yanchor="bottom",
|
|
260
|
+
xanchor="right",
|
|
261
|
+
y=1.02,
|
|
262
|
+
x=1,
|
|
263
|
+
bgcolor="rgba(0,0,0,0)",
|
|
264
|
+
),
|
|
265
|
+
yaxis=dict(
|
|
266
|
+
ticklen=0,
|
|
267
|
+
),
|
|
268
|
+
xaxis=dict(
|
|
269
|
+
type="category",
|
|
270
|
+
tickmode="array",
|
|
271
|
+
ticklen=0,
|
|
272
|
+
tickvals=df_ta.index,
|
|
273
|
+
ticktext=df_ta.index,
|
|
274
|
+
title_text="Period",
|
|
275
|
+
showgrid=False,
|
|
276
|
+
zeroline=False,
|
|
277
|
+
),
|
|
278
|
+
margin=dict(l=20, r=20, b=20),
|
|
279
|
+
dragmode="pan",
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
content = fig.to_plotly_json()
|
|
283
|
+
|
|
284
|
+
return fig, content
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def economy_fred_series(
|
|
288
|
+
**kwargs: Union[Any, FredSeriesChartQueryParams],
|
|
289
|
+
) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
290
|
+
"""FRED Series Chart."""
|
|
291
|
+
ytitle_dict = {
|
|
292
|
+
"chg": "Change",
|
|
293
|
+
"ch1": "Change From Year Ago",
|
|
294
|
+
"pch": "Percent Change",
|
|
295
|
+
"pc1": "Percent Change From Year Ago",
|
|
296
|
+
"pca": "Compounded Annual Rate Of Change",
|
|
297
|
+
"cch": "Continuously Compounded Rate Of Change",
|
|
298
|
+
"cca": "Continuously Compounded Annual Rate Of Change",
|
|
299
|
+
"log": "Natural Log",
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
colors = [
|
|
303
|
+
"#1f77b4",
|
|
304
|
+
"#7f7f7f",
|
|
305
|
+
"#ff7f0e",
|
|
306
|
+
"#2ca02c",
|
|
307
|
+
"#d62728",
|
|
308
|
+
"#9467bd",
|
|
309
|
+
"#8c564b",
|
|
310
|
+
"#e377c2",
|
|
311
|
+
"#7f7f7f",
|
|
312
|
+
"#bcbd22",
|
|
313
|
+
"#17becf",
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
provider = kwargs.get("provider")
|
|
317
|
+
|
|
318
|
+
if provider != "fred":
|
|
319
|
+
raise RuntimeError(
|
|
320
|
+
f"This charting method does not support {provider}. Supported providers: fred."
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
columns = basemodel_to_df(kwargs["obbject_item"], index=None).columns.to_list() # type: ignore
|
|
324
|
+
|
|
325
|
+
allow_unsafe = kwargs.get("allow_unsafe", False)
|
|
326
|
+
dropnan = kwargs.get("dropna", True)
|
|
327
|
+
normalize = kwargs.get("normalize", False)
|
|
328
|
+
|
|
329
|
+
data_cols = []
|
|
330
|
+
data = kwargs.get("data")
|
|
331
|
+
|
|
332
|
+
if isinstance(data, pd.DataFrame) and not data.empty:
|
|
333
|
+
data_cols = data.columns.to_list()
|
|
334
|
+
df_ta = data
|
|
335
|
+
|
|
336
|
+
else:
|
|
337
|
+
df_ta = basemodel_to_df(kwargs["obbject_item"], index="date") # type: ignore
|
|
338
|
+
|
|
339
|
+
# Check for unsupported external data injection.
|
|
340
|
+
if allow_unsafe is False and data_cols:
|
|
341
|
+
for data_col in data_cols:
|
|
342
|
+
if data_col not in columns:
|
|
343
|
+
raise RuntimeError(
|
|
344
|
+
f"Column '{data_col}' was not found in the original data."
|
|
345
|
+
+ " External data injection is not supported unless `allow_unsafe = True`."
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Align the data so each column has the same index and length.
|
|
349
|
+
if dropnan:
|
|
350
|
+
df_ta = df_ta.dropna(how="any")
|
|
351
|
+
|
|
352
|
+
if df_ta.empty or len(df_ta) < 2:
|
|
353
|
+
raise ValueError(
|
|
354
|
+
"No data is left after dropping NaN values. Try setting `dropnan = False`,"
|
|
355
|
+
+ " or use the `frequency` parameter on request ."
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
columns = df_ta.columns.to_list()
|
|
359
|
+
|
|
360
|
+
def z_score_standardization(data: pd.Series) -> pd.Series:
|
|
361
|
+
"""Z-Score Standardization Method."""
|
|
362
|
+
return (data - data.mean()) / data.std()
|
|
363
|
+
|
|
364
|
+
if normalize:
|
|
365
|
+
df_ta = df_ta.apply(z_score_standardization)
|
|
366
|
+
|
|
367
|
+
# Extract the metadata from the warnings.
|
|
368
|
+
warnings = kwargs.get("warnings")
|
|
369
|
+
metadata = json.loads(warnings[0].message) if warnings else {} # type: ignore
|
|
370
|
+
|
|
371
|
+
# Check if the request was transformed by the FRED API.
|
|
372
|
+
params = kwargs["extra_params"] if kwargs.get("extra_params") else {}
|
|
373
|
+
has_params = hasattr(params, "transform") and params.transform is not None # type: ignore
|
|
374
|
+
|
|
375
|
+
# Get a unique list of all units of measurement in the DataFrame.
|
|
376
|
+
y_units = list({metadata.get(col).get("units") for col in columns if col in metadata}) # type: ignore
|
|
377
|
+
|
|
378
|
+
if len(y_units) > 2 and has_params is False and allow_unsafe is True:
|
|
379
|
+
raise RuntimeError(
|
|
380
|
+
"This method supports up to 2 y-axis units."
|
|
381
|
+
+ " Please use the 'transform' parameter, in the data request,"
|
|
382
|
+
+ " to compare all series on the same scale, or set `normalize = True`."
|
|
383
|
+
+ " Override this error by setting `allow_unsafe = True`."
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
y1_units = y_units[0] if y_units else None
|
|
387
|
+
y1title = y1_units
|
|
388
|
+
y2title = y_units[1] if len(y_units) > 1 else None
|
|
389
|
+
xtitle = ""
|
|
390
|
+
|
|
391
|
+
# If the request was transformed, the y-axis will be shared under these conditions.
|
|
392
|
+
if has_params and any(
|
|
393
|
+
i in params.transform for i in ["pc1", "pch", "pca", "cch", "cca", "log"] # type: ignore
|
|
394
|
+
):
|
|
395
|
+
y1title = "Log" if params.transform == "Log" else "Percent" # type: ignore
|
|
396
|
+
y2title = None
|
|
397
|
+
|
|
398
|
+
# Set the title for the chart.
|
|
399
|
+
title: str = ""
|
|
400
|
+
if isinstance(kwargs, dict) and title in kwargs:
|
|
401
|
+
title = kwargs["title"]
|
|
402
|
+
else:
|
|
403
|
+
if metadata.get(columns[0]):
|
|
404
|
+
title = metadata.get(columns[0]).get("title") if len(columns) == 1 else "FRED Series" # type: ignore
|
|
405
|
+
else:
|
|
406
|
+
title = "FRED Series"
|
|
407
|
+
transform_title = ytitle_dict.get(params.transform) if has_params is True else "" # type: ignore
|
|
408
|
+
title = f"{title} - {transform_title}" if transform_title else title
|
|
409
|
+
|
|
410
|
+
# Define this to use as a check.
|
|
411
|
+
y3title: Optional[str] = ""
|
|
412
|
+
|
|
413
|
+
# Create the figure object with subplots.
|
|
414
|
+
fig = OpenBBFigure().create_subplots(
|
|
415
|
+
rows=1, cols=1, shared_xaxes=True, shared_yaxes=False
|
|
416
|
+
)
|
|
417
|
+
fig.update_layout(ChartStyle().plotly_template.get("layout", {}))
|
|
418
|
+
text_color = "black" if ChartStyle().plt_style == "light" else "white"
|
|
419
|
+
|
|
420
|
+
# For each series in the DataFrame, add a scatter plot.
|
|
421
|
+
for i, col in enumerate(df_ta.columns):
|
|
422
|
+
|
|
423
|
+
# Check if the y-axis should be shared for this series.
|
|
424
|
+
on_y1 = (
|
|
425
|
+
(
|
|
426
|
+
metadata.get(col).get("units") == y1_units
|
|
427
|
+
or y2title is None # type: ignore
|
|
428
|
+
)
|
|
429
|
+
if metadata.get(col)
|
|
430
|
+
else False
|
|
431
|
+
)
|
|
432
|
+
if normalize:
|
|
433
|
+
on_y1 = True
|
|
434
|
+
yaxes = "y2" if not on_y1 else "y1"
|
|
435
|
+
on_y3 = not metadata.get(col) and normalize is False
|
|
436
|
+
if on_y3:
|
|
437
|
+
yaxes = "y3"
|
|
438
|
+
y3title = df_ta[col].name
|
|
439
|
+
fig.add_scatter(
|
|
440
|
+
x=df_ta.index,
|
|
441
|
+
y=df_ta[col],
|
|
442
|
+
name=df_ta[col].name,
|
|
443
|
+
mode="lines",
|
|
444
|
+
hovertemplate=f"{df_ta[col].name}: %{{y}}<extra></extra>",
|
|
445
|
+
line=dict(width=1, color=colors[i % len(colors)]),
|
|
446
|
+
yaxis=yaxes,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Set the y-axis titles, if supplied.
|
|
450
|
+
if kwargs.get("y1title"):
|
|
451
|
+
y1title = kwargs.get("y1title")
|
|
452
|
+
if kwargs.get("y2title") and y2title is not None:
|
|
453
|
+
y2title = kwargs.get("y2title")
|
|
454
|
+
# Set the x-axis title, if suppiled.
|
|
455
|
+
if isinstance(kwargs, dict) and "xtitle" in kwargs:
|
|
456
|
+
xtitle = kwargs["xtitle"]
|
|
457
|
+
# If the data was normalized, set the title to reflect this.
|
|
458
|
+
if normalize:
|
|
459
|
+
y1title = None
|
|
460
|
+
y2title = None
|
|
461
|
+
y3title = None
|
|
462
|
+
title = f"{title} - Normalized" if title else "Normalized"
|
|
463
|
+
|
|
464
|
+
# Now update the layout of the complete figure.
|
|
465
|
+
fig.update_layout(
|
|
466
|
+
title=dict(text=title, x=0.5, font=dict(size=16)),
|
|
467
|
+
paper_bgcolor="rgba(0,0,0,0)",
|
|
468
|
+
plot_bgcolor="rgba(0,0,0,0)",
|
|
469
|
+
font=dict(color=text_color),
|
|
470
|
+
legend=dict(
|
|
471
|
+
orientation="h",
|
|
472
|
+
yanchor="bottom",
|
|
473
|
+
xanchor="right",
|
|
474
|
+
y=1.02,
|
|
475
|
+
x=1,
|
|
476
|
+
bgcolor="rgba(0,0,0,0)",
|
|
477
|
+
),
|
|
478
|
+
yaxis=(
|
|
479
|
+
dict(
|
|
480
|
+
ticklen=0,
|
|
481
|
+
side="right",
|
|
482
|
+
title=dict(text=y1title, standoff=30, font=dict(size=18)),
|
|
483
|
+
tickfont=dict(size=14),
|
|
484
|
+
anchor="x",
|
|
485
|
+
)
|
|
486
|
+
if y1title
|
|
487
|
+
else None
|
|
488
|
+
),
|
|
489
|
+
yaxis2=(
|
|
490
|
+
dict(
|
|
491
|
+
overlaying="y",
|
|
492
|
+
side="left",
|
|
493
|
+
ticklen=0,
|
|
494
|
+
showgrid=False,
|
|
495
|
+
title=dict(
|
|
496
|
+
text=y2title if y2title else None, standoff=10, font=dict(size=18)
|
|
497
|
+
),
|
|
498
|
+
tickfont=dict(size=14),
|
|
499
|
+
anchor="x",
|
|
500
|
+
)
|
|
501
|
+
if y2title
|
|
502
|
+
else None
|
|
503
|
+
),
|
|
504
|
+
yaxis3=(
|
|
505
|
+
dict(
|
|
506
|
+
overlaying="y",
|
|
507
|
+
side="left",
|
|
508
|
+
ticklen=0,
|
|
509
|
+
position=0,
|
|
510
|
+
showgrid=False,
|
|
511
|
+
showticklabels=True,
|
|
512
|
+
title=(
|
|
513
|
+
dict(text=y3title, standoff=10, font=dict(size=16))
|
|
514
|
+
if y3title
|
|
515
|
+
else None
|
|
516
|
+
),
|
|
517
|
+
tickfont=dict(size=12, color="rgba(128,128,128,0.75)"),
|
|
518
|
+
anchor="free",
|
|
519
|
+
)
|
|
520
|
+
if y3title
|
|
521
|
+
else None
|
|
522
|
+
),
|
|
523
|
+
xaxis=dict(
|
|
524
|
+
ticklen=0,
|
|
525
|
+
showgrid=False,
|
|
526
|
+
title=(
|
|
527
|
+
dict(text=xtitle, standoff=30, font=dict(size=18)) if xtitle else None
|
|
528
|
+
),
|
|
529
|
+
domain=[0.095, 0.95] if y3title else None,
|
|
530
|
+
),
|
|
531
|
+
margin=dict(r=25, l=25) if normalize is False else None,
|
|
532
|
+
autosize=True,
|
|
533
|
+
dragmode="pan",
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
content = fig.to_plotly_json()
|
|
537
|
+
|
|
538
|
+
return fig, content
|
|
@@ -152,7 +152,6 @@ class Backend(PyWry):
|
|
|
152
152
|
theme: Optional[str] = None,
|
|
153
153
|
) -> dict:
|
|
154
154
|
"""Get the json update for the backend."""
|
|
155
|
-
|
|
156
155
|
posthog: Dict[str, Any] = dict(collect_logs=self.charting_settings.log_collect)
|
|
157
156
|
if (
|
|
158
157
|
self.charting_settings.log_collect
|
|
@@ -222,6 +221,11 @@ class Backend(PyWry):
|
|
|
222
221
|
self.send_outgoing(outgoing)
|
|
223
222
|
|
|
224
223
|
if export_image and isinstance(export_image, Path):
|
|
224
|
+
if self.loop.is_closed(): # type: ignore[has-type]
|
|
225
|
+
# Create a new event loop
|
|
226
|
+
self.loop = asyncio.new_event_loop()
|
|
227
|
+
asyncio.set_event_loop(self.loop)
|
|
228
|
+
|
|
225
229
|
self.loop.run_until_complete(self.process_image(export_image))
|
|
226
230
|
|
|
227
231
|
async def process_image(self, export_image: Path):
|
|
@@ -507,6 +511,7 @@ if not PLOTLYJS_PATH.exists() and not JUPYTER_NOTEBOOK:
|
|
|
507
511
|
|
|
508
512
|
|
|
509
513
|
def create_backend(charting_settings: Optional["ChartingSettings"] = None):
|
|
514
|
+
"""Create the backend."""
|
|
510
515
|
# # pylint: disable=import-outside-toplevel
|
|
511
516
|
from openbb_core.app.model.charts.charting_settings import ChartingSettings
|
|
512
517
|
|
|
@@ -517,6 +522,7 @@ def create_backend(charting_settings: Optional["ChartingSettings"] = None):
|
|
|
517
522
|
|
|
518
523
|
|
|
519
524
|
def get_backend() -> Backend:
|
|
525
|
+
"""Get the backend instance."""
|
|
520
526
|
if BACKEND is None:
|
|
521
527
|
raise ValueError("Backend not created")
|
|
522
528
|
return BACKEND
|
|
@@ -923,11 +923,7 @@ class OpenBBFigure(go.Figure):
|
|
|
923
923
|
# If the backend fails, we just show the figure normally
|
|
924
924
|
# This is a very rare case, but it's better to have a fallback
|
|
925
925
|
|
|
926
|
-
|
|
927
|
-
warn(f"Failed to show figure with backend: {e}")
|
|
928
|
-
warn(
|
|
929
|
-
f"Failed to show figure with backend: {e}"
|
|
930
|
-
) # remove this line when the above lines are figured out
|
|
926
|
+
warn(f"Failed to show figure with backend. {e}")
|
|
931
927
|
|
|
932
928
|
# We check if any figures were initialized before the backend failed
|
|
933
929
|
# If so, we show them with the default plotly backend
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
"""Base class for charting plugins."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Iterator, List, Optional, Type, Union
|
|
2
4
|
|
|
3
5
|
import pandas as pd
|
|
4
6
|
|
|
@@ -6,7 +8,7 @@ from .data_classes import ChartIndicators, TAIndicator
|
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
def columns_regex(df_ta: pd.DataFrame, name: str) -> List[str]:
|
|
9
|
-
"""Return columns that match regex name"""
|
|
11
|
+
"""Return columns that match regex name."""
|
|
10
12
|
column_name = df_ta.filter(regex=rf"{name}(?=[^\d]|$)").columns.tolist()
|
|
11
13
|
|
|
12
14
|
return column_name
|
|
@@ -26,6 +28,7 @@ class Indicator:
|
|
|
26
28
|
self.attrs = attrs
|
|
27
29
|
|
|
28
30
|
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
31
|
+
"""Call the indicator function."""
|
|
29
32
|
return self.func(*args, **kwargs)
|
|
30
33
|
|
|
31
34
|
|
|
@@ -39,6 +42,7 @@ class PluginMeta(type):
|
|
|
39
42
|
__subplots__: List[str] = []
|
|
40
43
|
|
|
41
44
|
def __new__(mcs: Type["PluginMeta"], *args: Any, **kwargs: Any) -> "PluginMeta":
|
|
45
|
+
"""Create a new instance of the class."""
|
|
42
46
|
name, bases, attrs = args
|
|
43
47
|
indicators: Dict[str, Indicator] = {}
|
|
44
48
|
cls_attrs: Dict[str, list] = {
|
|
@@ -76,6 +80,7 @@ class PluginMeta(type):
|
|
|
76
80
|
return new_cls
|
|
77
81
|
|
|
78
82
|
def __iter__(cls: Type["PluginMeta"]) -> Iterator[Indicator]: # type: ignore
|
|
83
|
+
"""Iterate over the indicators."""
|
|
79
84
|
return iter(cls.__indicators__)
|
|
80
85
|
|
|
81
86
|
# pylint: disable=unused-argument
|
|
@@ -88,11 +93,11 @@ class PltTA(metaclass=PluginMeta):
|
|
|
88
93
|
|
|
89
94
|
indicators: ChartIndicators
|
|
90
95
|
intraday: bool = False
|
|
91
|
-
df_stock: pd.DataFrame
|
|
92
|
-
df_ta: pd.DataFrame
|
|
96
|
+
df_stock: Union[pd.DataFrame, pd.Series]
|
|
97
|
+
df_ta: Optional[pd.DataFrame] = None
|
|
93
98
|
df_fib: pd.DataFrame
|
|
94
99
|
close_column: Optional[str] = "close"
|
|
95
|
-
params: Dict[str, TAIndicator] = {}
|
|
100
|
+
params: Optional[Dict[str, TAIndicator]] = {}
|
|
96
101
|
inchart_colors: List[str] = []
|
|
97
102
|
show_volume: bool = True
|
|
98
103
|
|
|
@@ -104,6 +109,7 @@ class PltTA(metaclass=PluginMeta):
|
|
|
104
109
|
|
|
105
110
|
# pylint: disable=unused-argument
|
|
106
111
|
def __new__(cls, *args: Any, **kwargs: Any) -> "PltTA":
|
|
112
|
+
"""Create a new instance of the class."""
|
|
107
113
|
if cls is PltTA:
|
|
108
114
|
raise TypeError("Can't instantiate abstract class Plugin directly")
|
|
109
115
|
self = super().__new__(cls)
|
|
@@ -132,6 +138,7 @@ class PltTA(metaclass=PluginMeta):
|
|
|
132
138
|
|
|
133
139
|
@property
|
|
134
140
|
def ma_mode(self) -> List[str]:
|
|
141
|
+
"""Moving average mode."""
|
|
135
142
|
return list(set(self.__ma_mode__))
|
|
136
143
|
|
|
137
144
|
@ma_mode.setter
|
|
@@ -139,7 +146,7 @@ class PltTA(metaclass=PluginMeta):
|
|
|
139
146
|
self.__ma_mode__ = value
|
|
140
147
|
|
|
141
148
|
def add_plugins(self, plugins: List["PltTA"]) -> None:
|
|
142
|
-
"""Add plugins to current instance"""
|
|
149
|
+
"""Add plugins to current instance."""
|
|
143
150
|
for plugin in plugins:
|
|
144
151
|
for item in plugin.__indicators__:
|
|
145
152
|
# pylint: disable=unnecessary-dunder-call
|
|
@@ -161,7 +168,7 @@ class PltTA(metaclass=PluginMeta):
|
|
|
161
168
|
getattr(self, attr).extend(value)
|
|
162
169
|
|
|
163
170
|
def remove_plugins(self, plugins: List["PltTA"]) -> None:
|
|
164
|
-
"""Remove plugins from current instance"""
|
|
171
|
+
"""Remove plugins from current instance."""
|
|
165
172
|
for plugin in plugins:
|
|
166
173
|
for item in plugin.__indicators__:
|
|
167
174
|
delattr(self, item.name)
|
|
@@ -171,10 +178,11 @@ class PltTA(metaclass=PluginMeta):
|
|
|
171
178
|
delattr(self, static_method)
|
|
172
179
|
|
|
173
180
|
def __iter__(self) -> Iterator[Indicator]:
|
|
181
|
+
"""Iterate over the indicators."""
|
|
174
182
|
return iter(self.__indicators__)
|
|
175
183
|
|
|
176
184
|
def get_float_precision(self) -> str:
|
|
177
|
-
"""Returns f-string precision format"""
|
|
185
|
+
"""Returns f-string precision format."""
|
|
178
186
|
price = self.df_stock[self.close_column].tail(1).values[0]
|
|
179
187
|
float_precision = (
|
|
180
188
|
",.2f" if price > 1.10 else "" if len(str(price)) < 8 else ".6f"
|
{openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/data_classes.py
RENAMED
|
@@ -67,10 +67,6 @@ class TAIndicator:
|
|
|
67
67
|
]
|
|
68
68
|
args: List[Arguments]
|
|
69
69
|
|
|
70
|
-
def __post_init__(self):
|
|
71
|
-
"""Post init."""
|
|
72
|
-
self.args = [Arguments(**arg) for arg in self.args]
|
|
73
|
-
|
|
74
70
|
def __iter__(self):
|
|
75
71
|
"""Return iterator."""
|
|
76
72
|
return iter(self.args)
|
|
@@ -98,14 +94,6 @@ class ChartIndicators:
|
|
|
98
94
|
|
|
99
95
|
indicators: Optional[List[TAIndicator]] = None
|
|
100
96
|
|
|
101
|
-
def __post_init__(self):
|
|
102
|
-
"""Post init."""
|
|
103
|
-
self.indicators = (
|
|
104
|
-
[TAIndicator(**indicator) for indicator in self.indicators]
|
|
105
|
-
if self.indicators
|
|
106
|
-
else []
|
|
107
|
-
)
|
|
108
|
-
|
|
109
97
|
def get_indicator(self, name: str) -> Union[TAIndicator, None]:
|
|
110
98
|
"""Return indicator with given name."""
|
|
111
99
|
output = None
|
|
@@ -165,21 +153,43 @@ class ChartIndicators:
|
|
|
165
153
|
@staticmethod
|
|
166
154
|
def get_available_indicators() -> Tuple[str, ...]:
|
|
167
155
|
"""Return tuple of available indicators."""
|
|
168
|
-
return
|
|
156
|
+
return tuple(
|
|
169
157
|
TAIndicator.__annotations__["name"].__args__ # pylint: disable=E1101
|
|
170
158
|
)
|
|
171
159
|
|
|
172
160
|
@classmethod
|
|
173
|
-
def from_dict(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
161
|
+
def from_dict(
|
|
162
|
+
cls, indicators: Dict[str, Dict[str, List[Dict[str, Any]]]]
|
|
163
|
+
) -> "ChartIndicators":
|
|
164
|
+
"""Return ChartIndicators from dictionary.
|
|
165
|
+
|
|
166
|
+
Example
|
|
167
|
+
-------
|
|
168
|
+
ChartIndicators.from_dict(
|
|
169
|
+
{
|
|
170
|
+
"ad": {
|
|
171
|
+
"args": [
|
|
172
|
+
{
|
|
173
|
+
"label": "AD_LABEL",
|
|
174
|
+
"values": [1, 2, 3],
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
"""
|
|
181
|
+
return cls(
|
|
182
|
+
indicators=[
|
|
183
|
+
TAIndicator(
|
|
184
|
+
name=name, # type: ignore[arg-type]
|
|
185
|
+
args=[
|
|
186
|
+
Arguments(label=label, values=values)
|
|
187
|
+
for label, values in args.items()
|
|
188
|
+
],
|
|
189
|
+
)
|
|
190
|
+
for name, args in indicators.items()
|
|
191
|
+
]
|
|
192
|
+
)
|
|
183
193
|
|
|
184
194
|
def to_dataframe(
|
|
185
195
|
self, df_ta: pd.DataFrame, ma_mode: Optional[List[str]] = None
|
|
@@ -88,7 +88,7 @@ class PlotlyTA(PltTA):
|
|
|
88
88
|
|
|
89
89
|
inchart_colors: List[str] = []
|
|
90
90
|
plugins: List[Type[PltTA]] = []
|
|
91
|
-
df_ta: pd.DataFrame = None
|
|
91
|
+
df_ta: Optional[pd.DataFrame] = None
|
|
92
92
|
close_column: Optional[str] = "close"
|
|
93
93
|
has_volume: bool = True
|
|
94
94
|
show_volume: bool = True
|
|
@@ -112,11 +112,11 @@ class PlotlyTA(PltTA):
|
|
|
112
112
|
# Creates the instance of the class and loads the plugins
|
|
113
113
|
# We set the global variable to the instance of the class so that
|
|
114
114
|
# the plugins are only loaded once
|
|
115
|
-
PLOTLY_TA = super().__new__(cls)
|
|
116
|
-
PLOTLY_TA._locate_plugins(
|
|
115
|
+
PLOTLY_TA = super().__new__(cls) # type: ignore[attr-defined, assignment]
|
|
116
|
+
PLOTLY_TA._locate_plugins( # type: ignore[attr-defined]
|
|
117
117
|
getattr(cls.charting_settings, "debug_mode", False)
|
|
118
118
|
)
|
|
119
|
-
PLOTLY_TA.add_plugins(PLOTLY_TA.plugins)
|
|
119
|
+
PLOTLY_TA.add_plugins(PLOTLY_TA.plugins) # type: ignore[attr-defined, assignment]
|
|
120
120
|
|
|
121
121
|
return PLOTLY_TA
|
|
122
122
|
|
|
@@ -180,7 +180,13 @@ class PlotlyTA(PltTA):
|
|
|
180
180
|
df_stock = df_stock.to_frame()
|
|
181
181
|
|
|
182
182
|
if not isinstance(indicators, ChartIndicators):
|
|
183
|
-
indicators = ChartIndicators.from_dict(indicators or
|
|
183
|
+
indicators = ChartIndicators.from_dict(indicators or {})
|
|
184
|
+
|
|
185
|
+
# Apply to_datetime to the index in a way that handles daylight savings.
|
|
186
|
+
df_stock.loc[:, "date"] = df_stock.index # type: ignore
|
|
187
|
+
df_stock["date"] = df_stock["date"].apply(pd.to_datetime)
|
|
188
|
+
df_stock.index = df_stock["date"] # type: ignore
|
|
189
|
+
df_stock.drop(columns=["date"], inplace=True)
|
|
184
190
|
|
|
185
191
|
self.indicators = indicators
|
|
186
192
|
self.intraday = df_stock.index[-2].time() != df_stock.index[-1].time()
|
|
@@ -283,7 +289,7 @@ class PlotlyTA(PltTA):
|
|
|
283
289
|
def _clear_data(self):
|
|
284
290
|
"""Clear and reset all data to default values."""
|
|
285
291
|
self.df_stock = None
|
|
286
|
-
self.indicators = {}
|
|
292
|
+
self.indicators = ChartIndicators.from_dict({})
|
|
287
293
|
self.params = None
|
|
288
294
|
self.intraday = False
|
|
289
295
|
self.show_volume = True
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Charting Extension Query Params."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from openbb_core.provider.abstract.data import Data
|
|
6
|
+
from openbb_core.provider.abstract.query_params import QueryParams
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FredSeriesChartQueryParams(QueryParams):
|
|
11
|
+
"""
|
|
12
|
+
FRED Series Chart Query Params.
|
|
13
|
+
|
|
14
|
+
kwargs
|
|
15
|
+
------
|
|
16
|
+
|
|
17
|
+
data : List[Data], optional
|
|
18
|
+
Filtered versions of the data contained in the original results.
|
|
19
|
+
Example use is to reduce the number of columns or the length of data to plot.
|
|
20
|
+
To supply additional columns, set `allow_unsafe = True`.
|
|
21
|
+
title : str, optional
|
|
22
|
+
Title of the chart.
|
|
23
|
+
y1title : str, optional
|
|
24
|
+
Right Y-axis title.
|
|
25
|
+
y2title : str, optional
|
|
26
|
+
Left Y-axis title.
|
|
27
|
+
xtitle : str, optional
|
|
28
|
+
X-axis title.
|
|
29
|
+
dropnan: bool, optional (default: True)
|
|
30
|
+
If True, rows containing NaN will be dropped.
|
|
31
|
+
normalize: bool, optional (default: False)
|
|
32
|
+
If True, the data will be normalized and placed on the same axis.
|
|
33
|
+
allow_unsafe: bool, optional (default: False)
|
|
34
|
+
If True, the method will attempt to pass all supplied data to the chart constructor.
|
|
35
|
+
This can result in unexpected behavior.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
data: Optional[Union[Data, List[Data]]] = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
description="Filtered versions of the data contained in the original `self.results`."
|
|
41
|
+
+ " Columns should be the same as the original data."
|
|
42
|
+
+ " Example use is to reduce the number of columns or the length of data to plot."
|
|
43
|
+
+ " To supply additional columns, set `allow_unsafe = True`.",
|
|
44
|
+
)
|
|
45
|
+
title: Optional[str] = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
description="Title of the chart.",
|
|
48
|
+
)
|
|
49
|
+
y1title: Optional[str] = Field(
|
|
50
|
+
default=None,
|
|
51
|
+
description="Right Y-axis title.",
|
|
52
|
+
)
|
|
53
|
+
y2title: Optional[str] = Field(
|
|
54
|
+
default=None,
|
|
55
|
+
description="Left Y-axis title.",
|
|
56
|
+
)
|
|
57
|
+
xtitle: Optional[str] = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
description="X-axis title.",
|
|
60
|
+
)
|
|
61
|
+
dropnan: bool = Field(
|
|
62
|
+
default=True,
|
|
63
|
+
description="If True, rows containing NaN will be dropped.",
|
|
64
|
+
)
|
|
65
|
+
normalize: bool = Field(
|
|
66
|
+
default=False,
|
|
67
|
+
description="If True, the data will be normalized and placed on the same axis.",
|
|
68
|
+
)
|
|
69
|
+
allow_unsafe: bool = Field(
|
|
70
|
+
default=False,
|
|
71
|
+
description="If True, the method will attempt to pass all supplied data to the chart constructor."
|
|
72
|
+
+ " This can result in unexpected behavior.",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TechnicalConesChartQueryParams(QueryParams):
|
|
77
|
+
"""
|
|
78
|
+
Technical Cones Chart Query Params.
|
|
79
|
+
|
|
80
|
+
kwargs
|
|
81
|
+
------
|
|
82
|
+
|
|
83
|
+
data : List[Data], optional
|
|
84
|
+
Filtered versions of the data contained in the original results.
|
|
85
|
+
Example use is to reduce the number of windows to plot.
|
|
86
|
+
title : str, optional
|
|
87
|
+
Title of the chart.
|
|
88
|
+
symbol: str, optional
|
|
89
|
+
Symbol represented by the data. Used to label the chart.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
data: Optional[Union[Data, List[Data]]] = Field(
|
|
93
|
+
default=None,
|
|
94
|
+
description="Filtered versions of the data contained in the original results."
|
|
95
|
+
+ " Columns should be the same as the original data."
|
|
96
|
+
+ " Example use is to reduce the number of columns or the length of data to plot.",
|
|
97
|
+
)
|
|
98
|
+
title: Optional[str] = Field(
|
|
99
|
+
default=None,
|
|
100
|
+
description="Title of the chart.",
|
|
101
|
+
)
|
|
102
|
+
symbol: Optional[str] = Field(
|
|
103
|
+
default=None,
|
|
104
|
+
description="Symbol represented by the data. Used to label the chart.",
|
|
105
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "openbb-charting"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.2"
|
|
4
4
|
description = "Charting extension for OpenBB"
|
|
5
5
|
authors = ["OpenBB Team <hello@openbb.co>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -16,7 +16,7 @@ pywry = "^0.6.1"
|
|
|
16
16
|
svglib = "^1.5.1"
|
|
17
17
|
nbformat = "^5.9.2"
|
|
18
18
|
pandas-ta = "^0.3.14b"
|
|
19
|
-
openbb-core = "^1.1.
|
|
19
|
+
openbb-core = "^1.1.5"
|
|
20
20
|
|
|
21
21
|
[build-system]
|
|
22
22
|
requires = ["poetry-core"]
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
"""Charting router."""
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Dict, Tuple
|
|
4
|
-
|
|
5
|
-
from openbb_core.app.model.charts.chart import ChartFormat
|
|
6
|
-
from openbb_core.app.utils import basemodel_to_df
|
|
7
|
-
|
|
8
|
-
from .core.plotly_ta.ta_class import PlotlyTA
|
|
9
|
-
|
|
10
|
-
CHART_FORMAT = ChartFormat.plotly
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from .core.openbb_figure import OpenBBFigure
|
|
14
|
-
|
|
15
|
-
# from .core.openbb_figure_table import OpenBBFigureTable
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def equity_price_historical(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
19
|
-
"""Equity price chart."""
|
|
20
|
-
|
|
21
|
-
def handle_indicators(ma):
|
|
22
|
-
"""Handle indicators."""
|
|
23
|
-
k = {}
|
|
24
|
-
if ma:
|
|
25
|
-
k["rma"] = dict(length=ma)
|
|
26
|
-
return k
|
|
27
|
-
|
|
28
|
-
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
29
|
-
standard_params = kwargs["standard_params"]
|
|
30
|
-
ma = standard_params.get("ma", None)
|
|
31
|
-
prepost = standard_params.get("prepost", False)
|
|
32
|
-
symbol = standard_params.get("symbol", "")
|
|
33
|
-
|
|
34
|
-
ta = PlotlyTA()
|
|
35
|
-
fig = ta.plot(
|
|
36
|
-
data,
|
|
37
|
-
indicators=dict(**handle_indicators(ma)),
|
|
38
|
-
symbol=f"{symbol} historical data",
|
|
39
|
-
prepost=prepost,
|
|
40
|
-
)
|
|
41
|
-
content = fig.show(external=True).to_plotly_json()
|
|
42
|
-
|
|
43
|
-
return fig, content
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def _ta_ma(ma_type: str, **kwargs):
|
|
47
|
-
"""Plot moving average helper."""
|
|
48
|
-
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
49
|
-
window = kwargs.get("window", 50)
|
|
50
|
-
offset = kwargs.get("offset", 0)
|
|
51
|
-
symbol = kwargs.get("symbol", "")
|
|
52
|
-
|
|
53
|
-
ta = PlotlyTA()
|
|
54
|
-
fig = ta.plot(
|
|
55
|
-
data,
|
|
56
|
-
{f"{ma_type.lower()}": dict(length=window, offset=offset)},
|
|
57
|
-
f"{symbol.upper()} {ma_type.upper()}",
|
|
58
|
-
False,
|
|
59
|
-
volume=False,
|
|
60
|
-
)
|
|
61
|
-
content = fig.show(external=True).to_plotly_json()
|
|
62
|
-
|
|
63
|
-
return fig, content
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def technical_zlma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
67
|
-
"""Zero lag moving average chart."""
|
|
68
|
-
ma_type = "zlma"
|
|
69
|
-
return _ta_ma(ma_type, **kwargs)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def technical_aroon(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
73
|
-
"""Aroon chart."""
|
|
74
|
-
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
75
|
-
length = kwargs.get("length", 25)
|
|
76
|
-
scalar = kwargs.get("scalar", 100)
|
|
77
|
-
symbol = kwargs.get("symbol", "")
|
|
78
|
-
|
|
79
|
-
ta = PlotlyTA()
|
|
80
|
-
fig = ta.plot(
|
|
81
|
-
data,
|
|
82
|
-
dict(aroon=dict(length=length, scalar=scalar)),
|
|
83
|
-
f"Aroon on {symbol}",
|
|
84
|
-
False,
|
|
85
|
-
volume=False,
|
|
86
|
-
)
|
|
87
|
-
content = fig.show(external=True).to_plotly_json()
|
|
88
|
-
|
|
89
|
-
return fig, content
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def technical_sma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
93
|
-
"""Plot simple moving average chart."""
|
|
94
|
-
ma_type = "sma"
|
|
95
|
-
return _ta_ma(ma_type, **kwargs)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def technical_macd(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
99
|
-
"""Plot moving average convergence divergence chart."""
|
|
100
|
-
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
101
|
-
fast = kwargs.get("fast", 12)
|
|
102
|
-
slow = kwargs.get("slow", 26)
|
|
103
|
-
signal = kwargs.get("signal", 9)
|
|
104
|
-
symbol = kwargs.get("symbol", "")
|
|
105
|
-
|
|
106
|
-
ta = PlotlyTA()
|
|
107
|
-
fig = ta.plot(
|
|
108
|
-
data,
|
|
109
|
-
dict(macd=dict(fast=fast, slow=slow, signal=signal)),
|
|
110
|
-
f"{symbol.upper()} MACD",
|
|
111
|
-
False,
|
|
112
|
-
volume=False,
|
|
113
|
-
)
|
|
114
|
-
content = fig.show(external=True).to_plotly_json()
|
|
115
|
-
|
|
116
|
-
return fig, content
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def technical_hma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
120
|
-
"""Hull moving average chart."""
|
|
121
|
-
ma_type = "hma"
|
|
122
|
-
return _ta_ma(ma_type, **kwargs)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def technical_adx(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
126
|
-
"""Average directional movement index chart."""
|
|
127
|
-
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
128
|
-
length = kwargs.get("length", 14)
|
|
129
|
-
scalar = kwargs.get("scalar", 100.0)
|
|
130
|
-
drift = kwargs.get("drift", 1)
|
|
131
|
-
symbol = kwargs.get("symbol", "")
|
|
132
|
-
|
|
133
|
-
ta = PlotlyTA()
|
|
134
|
-
fig = ta.plot(
|
|
135
|
-
data,
|
|
136
|
-
dict(adx=dict(length=length, scalar=scalar, drift=drift)),
|
|
137
|
-
f"Average Directional Movement Index (ADX) {symbol}",
|
|
138
|
-
False,
|
|
139
|
-
volume=False,
|
|
140
|
-
)
|
|
141
|
-
content = fig.show(external=True).to_plotly_json()
|
|
142
|
-
|
|
143
|
-
return fig, content
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def technical_wma(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
147
|
-
"""Weighted moving average chart."""
|
|
148
|
-
ma_type = "wma"
|
|
149
|
-
return _ta_ma(ma_type, **kwargs)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def technical_rsi(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
153
|
-
"""Relative strength index chart."""
|
|
154
|
-
data = basemodel_to_df(kwargs["obbject_item"], index=kwargs.get("index", "date"))
|
|
155
|
-
window = kwargs.get("window", 14)
|
|
156
|
-
scalar = kwargs.get("scalar", 100.0)
|
|
157
|
-
drift = kwargs.get("drift", 1)
|
|
158
|
-
symbol = kwargs.get("symbol", "")
|
|
159
|
-
|
|
160
|
-
ta = PlotlyTA()
|
|
161
|
-
fig = ta.plot(
|
|
162
|
-
data,
|
|
163
|
-
dict(rsi=dict(length=window, scalar=scalar, drift=drift)),
|
|
164
|
-
f"{symbol.upper()} RSI {window}",
|
|
165
|
-
False,
|
|
166
|
-
volume=False,
|
|
167
|
-
)
|
|
168
|
-
content = fig.show(external=True).to_plotly_json()
|
|
169
|
-
|
|
170
|
-
return fig, content
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def technical_ema(**kwargs) -> Tuple["OpenBBFigure", Dict[str, Any]]:
|
|
174
|
-
"""Exponential moving average chart."""
|
|
175
|
-
ma_type = "ema"
|
|
176
|
-
return _ta_ma(ma_type, **kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/assets/Terminal_icon.png
RENAMED
|
File without changes
|
{openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/assets/plotly-2.24.2.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/config/openbb_styles.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/core/plotly_ta/ta_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/styles/default/dark.pltstyle.json
RENAMED
|
File without changes
|
{openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/styles/default/light.pltstyle.json
RENAMED
|
File without changes
|
{openbb_charting-2.0.0 → openbb_charting-2.0.2}/openbb_charting/styles/default/tables.pltstyle.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|