Skip to content

Commit

Permalink
Hotfix/ta srlines (#4630)
Browse files Browse the repository at this point in the history
* init

* Update style.css

* oops was stashed

* Update popups.js

* bump plotly.js version

* fix disappearing modebar

* updates

* Update plotly.html

* bump pywry

* Update ta_class.py

* Update main.js

* bump, pdf support

* Update plotly_helper.py

* tests
  • Loading branch information
tehcoderer authored and jmaslek committed Apr 12, 2023
1 parent b01dbf1 commit 2c5ea5a
Show file tree
Hide file tree
Showing 15 changed files with 3,502 additions and 3,359 deletions.
14 changes: 7 additions & 7 deletions openbb_terminal/core/plots/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def send_figure(
{
"html_path": self.get_plotly_html(),
"json_data": json.loads(fig.to_json()),
"export_image": str(export_image).replace(".pdf", ".svg"),
"export_image": str(export_image),
**self.get_kwargs(title),
}
)
Expand All @@ -198,16 +198,16 @@ async def process_image(self, export_image: Path):
pdf = export_image.suffix == ".pdf"
img_path = export_image.resolve()

if pdf:
img_path = img_path.with_suffix(".svg")

checks = 0
while not img_path.exists():
await asyncio.sleep(0.2)
checks += 1
if checks > 50:
break

if pdf:
img_path = img_path.rename(img_path.with_suffix(".svg"))

if img_path.exists():
if pdf:
drawing = svg2rlg(img_path)
Expand Down Expand Up @@ -373,9 +373,9 @@ async def check_backend(self):
PyWry.__version__
) < version.parse("0.3.5"):
console.print(
"[bold red]Pywry version 0.3.5 or higher is required to use the "
"OpenBB Plots backend.[/bold red]\n"
"[yellow]Please update pywry with 'pip install pywry --upgrade'[/yellow]"
"[bold red]PyWry version 0.3.5 or higher is required to use the "
"OpenBB Plots backend.[/]\n"
"[yellow]Please update pywry with 'pip install pywry --upgrade'[/]"
)
self.max_retries = 0 # pylint: disable=W0201
return
Expand Down
10 changes: 5 additions & 5 deletions openbb_terminal/core/plots/plotly.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@
id="openbb_chart"
class="plotly-graph-div"
></div>
<div
id="openbb_footer"
class="_footer flex items-center justify-between gap-4 py-2 px-4 text-center"
style="z-index: 99999999999; width: 100%; bottom: 0; max-height: 60px;"
>......................</div>
</div>
<div
id="openbb_footer"
class="_footer flex items-center justify-between gap-4 py-2 px-4 text-center"
style="z-index: 99999999999; width: 100%; bottom: 0; max-height: 60px;"
>......................</div>

<div id="loading" class="saving">
<div id="loading_text" class="loading_text"></div><div id="loader" class="loader"></div>
Expand Down
20 changes: 12 additions & 8 deletions openbb_terminal/core/plots/plotly_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -838,15 +838,15 @@ def horizontal_legend(

@staticmethod
def chart_volume_scaling(
df_volume: pd.DataFrame, range_x: int = 7
df_volume: pd.DataFrame, volume_ticks_x: int = 7
) -> Dict[str, list]:
"""Takes df_volume and returns volume_ticks, tickvals for chart volume scaling
Parameters
----------
df_volume : pd.DataFrame
Dataframe of volume (e.g. df_volume = df["Volume"])
range_x : int, optional
volume_ticks_x : int, optional
Number to multiply volume, by default 7
Returns
Expand All @@ -871,7 +871,7 @@ def chart_volume_scaling(
floor(first_val * 3),
floor(first_val * 4),
]
volume_range = [0, floor(volume_ticks * range_x)]
volume_range = [0, floor(volume_ticks * volume_ticks_x)]

return {"range": volume_range, "ticks": tickvals}

Expand All @@ -882,6 +882,7 @@ def add_inchart_volume(
volume_col: Optional[str] = "Volume",
row: Optional[int] = 1,
col: Optional[int] = 1,
volume_ticks_x: int = 7,
) -> None:
"""Add in-chart volume to a subplot.
Expand All @@ -897,11 +898,13 @@ def add_inchart_volume(
Row number, by default 2
col : `int`, optional
Column number, by default 1
volume_ticks_x : int, optional
Number to multiply volume, by default 7
"""
colors = np.where(
df_stock.Open < df_stock[close_col], theme.up_color, theme.down_color
)
vol_scale = self.chart_volume_scaling(df_stock[volume_col])
vol_scale = self.chart_volume_scaling(df_stock[volume_col], volume_ticks_x)
self.add_bar(
x=df_stock.index,
y=df_stock[volume_col],
Expand All @@ -913,7 +916,7 @@ def add_inchart_volume(
opacity=0.5,
secondary_y=False,
)
ticksize = 14 - (self.subplots_kwargs["rows"] // 2)
ticksize = 13 - (self.subplots_kwargs["rows"] // 2)
self.update_layout(
yaxis=dict(
fixedrange=True,
Expand Down Expand Up @@ -1540,8 +1543,8 @@ def _adjust_margins(self) -> None:

if not plots_backend().isatty:
org_margin = self.layout.margin
margin = dict(l=40, r=60, b=80, t=50, pad=0)
for key, max_val in zip(["l", "r", "b", "t"], [60, 50, 80, 40]):
margin = dict(l=40, r=60, b=80, t=50)
for key, max_val in zip(["l", "r", "b", "t"], [60, 50, 80, 50]):
org = org_margin[key] or 0
if (org + margin[key]) > max_val:
self.layout.margin[key] = max_val
Expand All @@ -1555,7 +1558,8 @@ def _set_watermark(self) -> None:
if (
not plots_backend().isatty
or not get_current_user().preferences.PLOT_ENABLE_PYWRY
or self._export_image
or isinstance(self._export_image, Path)
and self._export_image.suffix == ".pdf"
):
self.add_annotation(
yref="paper",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ def is_resistance(df, i):
cond4 = df["High"][i - 1] > df["High"][i - 2]
return cond1 and cond2 and cond3 and cond4

df_ta2 = df_ta.copy().loc[
(df_ta.index >= datetime.now() - timedelta(days=window))
& (df_ta.index < datetime.now())
]
df_ta2 = df_ta.copy()
today = pd.to_datetime(datetime.now(), unit="ns")
start_date = pd.to_datetime(datetime.now() - timedelta(days=window), unit="ns")

df_ta2 = df_ta2.loc[(df_ta2.index >= start_date) & (df_ta2.index < today)]

if df_ta2.index[-2].date() != df_ta2.index[-1].date():
interval = 1440
else:
Expand All @@ -54,78 +56,69 @@ def is_resistance(df, i):
cut_days = 1 if interval < 15 else 2
dt_unique_days = df_ta2.index.normalize().unique()
df_ta2 = df_ta2.loc[
(df_ta.index >= dt_unique_days[-cut_days])
& (df_ta.index < datetime.now())
(df_ta2.index >= pd.to_datetime(dt_unique_days[-cut_days], unit="ns"))
& (df_ta2.index < today)
].copy()

levels: list = []
x_range = (
df_ta2.index[-1].replace(hour=17, minute=45)
if interval < 15
else df_ta2.index[-1].replace(hour=15, minute=45)
)
x_range = df_ta2.index[-1].replace(hour=15, minute=59)
if interval > 15:
x_range = df_ta2.index[-1] + timedelta(days=15)
if x_range.weekday() > 4:
x_range = x_range + timedelta(days=7 - x_range.weekday())

elif df_ta2.index[-1] >= today.replace(hour=15, minute=0):
x_range = (df_ta2.index[-1] + timedelta(days=1)).replace(hour=11, minute=0)
if x_range.weekday() > 4:
x_range = x_range + timedelta(days=7 - x_range.weekday())

for i in range(2, len(df_ta2) - 2):
if is_support(df_ta2, i):
lv = df_ta2["Low"][i]
if is_far_from_level(lv, levels, df_ta2):
levels.append((i, lv))
fig.add_scatter(
x=[x_range],
y=[lv],
x=[df_ta.index[0], x_range],
y=[lv, lv],
opacity=1,
mode="text",
text=f"{lv:{self.get_float_precision()}}",
textposition="top left",
mode="lines+text",
text=["", f"{lv:{self.get_float_precision()}}"],
textposition="top center",
textfont=dict(
family="Arial Black", color="rgb(120, 70, 200)", size=12
family="Arial Black", color="rgb(120, 70, 200)", size=10
),
line=dict(
width=2, dash="dash", color="rgba(120, 70, 200, 0.70)"
),
connectgaps=True,
showlegend=False,
row=1,
col=1,
secondary_y=self.show_volume,
)
fig.add_hline(
y=lv,
line_width=2,
line_dash="dash",
line_color="rgba(120, 70, 200, 0.70)",
row=1,
col=1,
secondary_y=self.show_volume,
)
elif is_resistance(df_ta2, i):
lv = df_ta2["High"][i]
if is_far_from_level(lv, levels, df_ta2):
levels.append((i, lv))
fig.add_scatter(
x=[x_range],
y=[lv],
x=[df_ta.index[0], x_range],
y=[lv, lv],
opacity=1,
mode="text",
text=f"{lv:{self.get_float_precision()}}",
textposition="top left",
mode="lines+text",
text=["", f"{lv:{self.get_float_precision()}}"],
textposition="top center",
textfont=dict(
family="Arial Black", color="rgb(120, 70, 200)", size=12
family="Arial Black", color="rgb(120, 70, 200)", size=10
),
line=dict(
width=2, dash="dash", color="rgba(120, 70, 200, 0.70)"
),
connectgaps=True,
showlegend=False,
row=1,
col=1,
secondary_y=self.show_volume,
)
fig.add_hline(
y=lv,
line_width=2,
line_dash="dash",
line_color="rgba(120, 70, 200, 0.70)",
row=1,
col=1,
secondary_y=self.show_volume,
)

return fig

Expand Down
27 changes: 22 additions & 5 deletions openbb_terminal/core/plots/plotly_ta/ta_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def subplots(self) -> List[str]:
def subplots(self, value: List[str]):
self.__subplots__ = value

# pylint: disable=R0913
def __plot__(
self,
df_stock: Union[pd.DataFrame, pd.Series],
Expand All @@ -142,6 +143,7 @@ def __plot__(
volume: bool = True,
prepost: bool = False,
fig: Optional[OpenBBFigure] = None,
volume_ticks_x: int = 7,
) -> OpenBBFigure:
"""This method should not be called directly. Use the PlotlyTA.plot() static method instead."""
if isinstance(df_stock, pd.Series):
Expand All @@ -163,7 +165,9 @@ def __plot__(

self.prepost = prepost

return self.plot_fig(fig=fig, symbol=symbol, candles=candles)
return self.plot_fig(
fig=fig, symbol=symbol, candles=candles, volume_ticks_x=volume_ticks_x
)

@staticmethod
def plot(
Expand All @@ -174,6 +178,7 @@ def plot(
volume: bool = True,
prepost: bool = False,
fig: Optional[OpenBBFigure] = None,
volume_ticks_x: int = 7,
) -> OpenBBFigure:
"""Plot a chart with the given indicators.
Expand All @@ -200,12 +205,14 @@ def plot(
Plot pre and post market data, by default False
fig : OpenBBFigure, optional
Plotly figure to plot on, by default None
volume_ticks_x : int, optional
Number to multiply volume, by default 7
"""
if indicators is None and PLOTLY_TA is not None:
indicators = PLOTLY_TA.indicators

return PlotlyTA().__plot__(
df_stock, indicators, symbol, candles, volume, prepost, fig
df_stock, indicators, symbol, candles, volume, prepost, fig, volume_ticks_x
)

@staticmethod
Expand Down Expand Up @@ -387,6 +394,7 @@ def plot_fig(
fig: Optional[OpenBBFigure] = None,
symbol: str = "",
candles: bool = True,
volume_ticks_x: int = 7,
) -> OpenBBFigure:
"""Plot indicators on plotly figure
Expand All @@ -398,6 +406,8 @@ def plot_fig(
Symbol to plot, by default uses the dataframe.name attribute if available or ""
candles : bool, optional
Plot a candlestick chart, by default True (if False, plots a line chart)
volume_ticks_x : int, optional
Number to multiply volume, by default 7
Returns
-------
Expand All @@ -418,7 +428,7 @@ def plot_fig(
subplot_row, fig_new = 2, {}
inchart_index, ma_done = 0, False

figure = self.process_fig(figure)
figure = self.process_fig(figure, volume_ticks_x)

# Aroon indicator is always plotted first since it has 2 subplot rows
plot_indicators = sorted(
Expand Down Expand Up @@ -480,6 +490,9 @@ def plot_fig(
figure.update_layout(showlegend=False)
figure.hide_holidays(self.prepost)

if not self.show_volume:
figure.update_layout(margin=dict(l=20))

# We remove xaxis labels from all but bottom subplot, and we make sure
# they all match the bottom one
xbottom = f"y{subplot_row}"
Expand All @@ -494,13 +507,15 @@ def plot_fig(

return figure

def process_fig(self, fig: OpenBBFigure) -> OpenBBFigure:
def process_fig(self, fig: OpenBBFigure, volume_ticks_x: int = 7) -> OpenBBFigure:
"""Process plotly figure before plotting indicators
Parameters
----------
fig : OpenBBFigure
Plotly figure to process
volume_ticks_x : int, optional
Number to multiply volume, by default 7
Returns
-------
Expand Down Expand Up @@ -550,6 +565,8 @@ def process_fig(self, fig: OpenBBFigure) -> OpenBBFigure:
new_subplot.layout.update({layout: fig.layout[layout]})

if self.show_volume:
new_subplot.add_inchart_volume(self.df_stock, self.close_column)
new_subplot.add_inchart_volume(
self.df_stock, self.close_column, volume_ticks_x=volume_ticks_x
)

return new_subplot
Loading

0 comments on commit 2c5ea5a

Please sign in to comment.