mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-28 21:12:16 +00:00
* Add default stylesheets * Add terminal style helper class and global style initialization in cfg * Style comments and docstrings * Load rich terminal theme from config file * Add application chart styles to candle charts * Add todos * Remove explicit color setting for some ta charts * Add user styles folder to gitignore * Update default stylesheets * Add matplotlib font manager support * Add matplotlib font manager support * Update docstrings and default style * Update stocks candle chart formatting (return fig to style title) * Style common ta overlap view * Make up and down market colors a part of the style helper * Update stylesheets * Style common ta volume view * Style common ta momentum view * Style common ta trend indicators view * Style common ta volatility view * Style common ta volume view * Style common ta custom indicators view * Fix styling bugs and remove the obvious time x lablel * Style charts in the covid menu * Set legend position to upper left in the mpl stylesheet * Add mpl_rcparams configs for parameters not covered by stylesheets * Remove font configuration files * Update style class utility functions * Implement passing external axes and style utility usage in ema & stoch * Add theme watermark and output helpers * Rename style to theme * Update helper usage in ta/ma and ta/stoch * Update style to theme in sample menus * Style forex (#1305) * Make tight layout optional 'cause mplfinance doesn't support it * Apply global style to the forex menu * Update code layout in oanda view and black * Style common TA (#1315) * Make tight layout optional 'cause mplfinance doesn't support it * Apply global style to the forex menu * Add linewidth to theme for use in mpf's addplots * Add vwap to the stocks notebook api * Update common/ta overlap to follow charting style * Apply style on TerminalStyle init * Enable infrastructure for excluding non-trading days from plots * Update notebook api to include there and resolve bandit warning * Update ta/common/overlap to exclude non-trading days * Enable external ax, style and non-trading days in common/ta/momentum * Enable external ax, style and non-trading days in common/ta/trend * Update vwap to the argument naming convention * Enable external ax, style and non-trading days in common/ta/volatility * Enable external ax, style and non-trading days in common/ta/volume * Enable external ax, style and non-trading days in common/ta/custom * Fix controller tests * Forgot to disable rewriting of the cassettes ... * Fix controller errors that came up because a merge conflict * Fix price label position on fib * Fix line having wrong x values in fib Co-authored-by: Colin Delahunty <72827203+colin99d@users.noreply.github.com> * Style economy (#1308) * Began converting * Added alphavan_view * Added CNN View * Updated nasdaq view, fixed glitch * Added fred * Refactored URL * Theo's requested changes * Updated docstrings * Updated tests * Fixed pylint * Fixed tests * Theo changes * Econ Fix * Refactor chart style for Crypto context (#1306) * Remove mock for gff * Mock visualize_output helper function * Refactor * Fix plot helper * Update legend loc * Refactor mplfinance candle plot * Fix errors in the helper function * Fix binbook having the wrong call_ function name * Remove hardcoded style params * Resolve kwargs future warning from pandas * Remove warnings import Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com> * funds + custom (#1311) * funds + custom * cleanup cleanup everybody everywhere * Fix external axes conditional and a typo Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com> * Add external axes mode to covid charts (#1328) * Add portfolio menu plots (#1318) * Portfolio view plots (commenting out report stuff) * PA Menu broken. Commenting out and fix tests * portfolio optimization * comment out commented api line * Add notes on disabling the pa submenu Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com> * Plot updates in common BA (#1335) * Add external axes support to common/ba/finbrain * Add external axes support to common/ba/twitter * Add external axes support to common/ba/google * Add external axes support to common/ba/sentimentinvestor * Add sentimentinvestor to the notebooks API * Fix tests * Etf refactor (#1323) * Refactored no ETF * Fixed gtff import * Fixed tests * Fix pie chart style * Refactored etf/candle * Added pylint fix * Fixed tests * Update candle chart layout * Update etf controller test * Remove strange binary file Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com> * Expose ETF candle function in the notebooks API * Common BA and Common QA charts update (#1342) * Add external axes support to common/ba/finbrain * Add external axes support to common/ba/twitter * Add external axes support to common/ba/google * Add external axes support to common/ba/sentimentinvestor * Add sentimentinvestor to the notebooks API * Fix tests * Update stylesheet files * Refactor charts for common/qa * Update the forgotten line plot * Update tests * Add missing arg to a docstring * Remove scientific notation * Black imports Co-authored-by: Minh Hoang <nminh.hoang1023@gmail.com> * Options refactor (#1324) * Fixed alphaquery_view * finished options * Fixed pylint * Fixed tests * Fixed tests * Fixed tests * update yfinance * Tradier + Chartexchange * change mocks from gtff to theme.visualize output * tests Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com> Co-authored-by: james <jmaslek11@gmail.com> * Refactor Stocks menu (#1325) * Fix backtesting menu * Refactor comparison analysis * Refactor Dark pool shorts * Refactor rest of menu * Fix test * Fix tests failing * Fix tests fail * Fix test failing * Remove record mode=none to record new output * Rewrite test output * Rewrite test outputs * Adding more rewritten test output * Mock plt.show * Mock missing plt.show * Missing @pytest.mark.vcr * Updating tests : common/behavioural_analysis/finbrain * Improve notebooks API coverage for CA and DPS * Silence annoying flake8 warning Co-authored-by: Chavithra PARANA <chavithra@gmail.com> Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com> * Charts update for common/pred (#1344) * Add external axes support to common/ba/finbrain * Add external axes support to common/ba/twitter * Add external axes support to common/ba/google * Add external axes support to common/ba/sentimentinvestor * Add sentimentinvestor to the notebooks API * Fix tests * Update stylesheet files * Refactor charts for common/qa * Update the forgotten line plot * Update tests * Add missing arg to a docstring * Style pred helper and controllers * Update ETS plot * Update plots in KNN and pred helper * Update plot and pretty table for arima * Update plot for common/pred/regression * Refactor mc_view * Fix linting * Fix mypy * Move plot title to the axis level to make more vertical space Co-authored-by: Minh Hoang <nminh.hoang1023@gmail.com> Co-authored-by: jmaslek <jmaslek11@gmail.com> * linter * Update common/ba test data * Change etf candle to match stock candle * try updating sia test Co-authored-by: Colin Delahunty <72827203+colin99d@users.noreply.github.com> Co-authored-by: jmaslek <jmaslek11@gmail.com> Co-authored-by: minhhoang1023 <40023817+minhhoang1023@users.noreply.github.com> Co-authored-by: Minh Hoang <nminh.hoang1023@gmail.com> Co-authored-by: Chavithra PARANA <chavithra@gmail.com>
340 lines
12 KiB
Python
340 lines
12 KiB
Python
"""Helper classes."""
|
|
__docformat__ = "numpy"
|
|
import os
|
|
import json
|
|
from importlib import machinery, util
|
|
from typing import Union, List, Dict, Optional
|
|
|
|
import matplotlib
|
|
import matplotlib.pyplot as plt
|
|
from matplotlib import font_manager, ticker
|
|
|
|
|
|
class LineAnnotateDrawer:
|
|
"""Line drawing class."""
|
|
|
|
def __init__(self, ax: matplotlib.axes = None):
|
|
self.ax = ax
|
|
|
|
def draw_lines_and_annotate(self):
|
|
"""Draw lines."""
|
|
print("Click twice for annotation.\nClose window to keep using terminal.\n")
|
|
|
|
while True:
|
|
xy = plt.ginput(2)
|
|
# Check whether the user has closed the window or not
|
|
if not plt.get_fignums():
|
|
print("")
|
|
return
|
|
|
|
if len(xy) == 2:
|
|
x = [p[0] for p in xy]
|
|
y = [p[1] for p in xy]
|
|
|
|
if (x[0] == x[1]) and (y[0] == y[1]):
|
|
txt = input("Annotation: ")
|
|
self.ax.annotate(txt, (x[0], y[1]), ha="center", va="center")
|
|
else:
|
|
self.ax.plot(x, y)
|
|
|
|
self.ax.figure.canvas.draw()
|
|
|
|
|
|
# pylint: disable=too-few-public-methods
|
|
class ModelsNamespace:
|
|
"""A namespace placeholder for the menu models.
|
|
|
|
This class is used in all api wrappers to create a `models` namespace and import
|
|
all the model functions.
|
|
"""
|
|
|
|
def __init__(self, folders: Union[str, List[str]]) -> None:
|
|
"""Import all menu models into the models namespace.
|
|
|
|
Instantiation of the namespace requires either a path to the folder that
|
|
contains model files or a list of such folders.
|
|
|
|
Parameters
|
|
----------
|
|
folders : Union[str, List[str]]
|
|
a folder or a list of folders to import models from
|
|
"""
|
|
if isinstance(folders, str):
|
|
folders = [folders]
|
|
for folder in folders:
|
|
menu_models = [
|
|
(
|
|
f.replace("_model.py", ""),
|
|
os.path.abspath(os.path.join(folder, f)),
|
|
)
|
|
for f in os.listdir(folder)
|
|
if f.endswith("_model.py")
|
|
]
|
|
|
|
for model_name, model_file in menu_models:
|
|
loader = machinery.SourceFileLoader(model_name, model_file)
|
|
spec = util.spec_from_loader(model_name, loader)
|
|
if spec is not None:
|
|
setattr(self, model_name, util.module_from_spec(spec))
|
|
loader.exec_module(getattr(self, model_name))
|
|
else:
|
|
pass
|
|
|
|
|
|
# pylint: disable=R0902
|
|
class TerminalStyle:
|
|
"""The class that helps with handling of style configurations.
|
|
|
|
It serves styles for 3 libraries. For `Matplotlib` this class serves absolute paths
|
|
to the .mplstyle files. For `Matplotlib Finance` and `Rich` this class serves custom
|
|
styles as python dictionaries.
|
|
"""
|
|
|
|
_STYLES_FOLDER = os.path.abspath(
|
|
os.path.join(os.path.dirname(__file__), "..", "styles")
|
|
)
|
|
DEFAULT_STYLES_LOCATION = os.path.join(_STYLES_FOLDER, "default")
|
|
USER_STYLES_LOCATION = os.path.join(_STYLES_FOLDER, "user")
|
|
|
|
mpl_styles_available: Dict[str, str] = {}
|
|
mpl_style: str = ""
|
|
|
|
mpl_rcparams_available: Dict[str, str] = {}
|
|
mpl_rcparams: Dict = {}
|
|
|
|
mpf_styles_available: Dict[str, str] = {}
|
|
mpf_style: Dict = {}
|
|
|
|
console_styles_available: Dict[str, str] = {}
|
|
console_style: Dict[str, str] = {}
|
|
|
|
down_color: str = ""
|
|
up_color: str = ""
|
|
|
|
xticks_rotation: str = ""
|
|
tight_layout_padding: int = 0
|
|
pie_wedgeprops: Dict = {}
|
|
pie_startangle: int = 0
|
|
line_width: float = 1.5
|
|
volume_bar_width: float = 0.8
|
|
|
|
def __init__(
|
|
self,
|
|
mpl_style: Optional[str] = "",
|
|
mpf_style: Optional[str] = "",
|
|
console_style: Optional[str] = "",
|
|
) -> None:
|
|
"""Instantiate a terminal style class
|
|
|
|
The stylesheet files should be placed to the `styles/default` or `styles/user`
|
|
folders. The parameters required for class instantiation are stylesheet names
|
|
without extensions (following matplotlib convention).
|
|
|
|
Ex. `styles/default/boring.mplstyle` should be passed as `boring`.
|
|
|
|
Parameters
|
|
----------
|
|
mpl_style : str, optional
|
|
Style name without extension, by default ""
|
|
mpf_style : str, optional
|
|
Style name without extension, by default ""
|
|
console_style : str, optional
|
|
Style name without extension, by default ""
|
|
"""
|
|
for folder in [self.DEFAULT_STYLES_LOCATION, self.USER_STYLES_LOCATION]:
|
|
self.load_available_styles_from_folder(folder)
|
|
self.load_custom_fonts_from_folder(folder)
|
|
|
|
if mpl_style in self.mpl_styles_available:
|
|
self.mpl_style = self.mpl_styles_available[mpl_style]
|
|
else:
|
|
self.mpl_style = self.mpl_styles_available["dark"]
|
|
|
|
if mpl_style in self.mpl_rcparams_available:
|
|
with open(self.mpl_rcparams_available[mpl_style]) as stylesheet:
|
|
self.mpl_rcparams = json.load(stylesheet)
|
|
else:
|
|
with open(self.mpl_rcparams_available["dark"]) as stylesheet:
|
|
self.mpl_rcparams = json.load(stylesheet)
|
|
|
|
if mpf_style in self.mpf_styles_available:
|
|
with open(self.mpf_styles_available[mpf_style]) as stylesheet:
|
|
self.mpf_style = json.load(stylesheet)
|
|
self.mpf_style["base_mpl_style"] = self.mpl_style
|
|
else:
|
|
with open(self.mpf_styles_available["dark"]) as stylesheet:
|
|
self.mpf_style = json.load(stylesheet)
|
|
self.mpf_style["base_mpl_style"] = self.mpl_style
|
|
|
|
if console_style in self.console_styles_available:
|
|
with open(self.console_styles_available[console_style]) as stylesheet:
|
|
self.console_style = json.load(stylesheet)
|
|
else:
|
|
with open(self.console_styles_available["dark"]) as stylesheet:
|
|
self.console_style = json.load(stylesheet)
|
|
|
|
self.applyMPLstyle()
|
|
|
|
def load_custom_fonts_from_folder(self, folder: str) -> None:
|
|
"""Load custom fonts form folder.
|
|
|
|
TTF and OTF fonts are loaded into the mpl font manager and are available for
|
|
selection in mpl by their name (for example "Consolas" or "Hack").
|
|
|
|
Parameters
|
|
----------
|
|
folder : str
|
|
Path to the folder containing the fonts
|
|
"""
|
|
for font_file in os.listdir(folder):
|
|
if font_file.endswith(".otf") or font_file.endswith(".ttf"):
|
|
font_path = os.path.abspath(os.path.join(folder, font_file))
|
|
font_manager.fontManager.addfont(font_path)
|
|
|
|
def load_available_styles_from_folder(self, folder: str) -> None:
|
|
"""Load custom styles from folder.
|
|
|
|
Parses the styles/default and styles/user folders and loads style files.
|
|
To be recognized files need to follow a naming convention:
|
|
*.mplstyle - matplotlib stylesheets
|
|
*.mplrc.json - matplotlib rc stylesheets that are not handled by mplstyle
|
|
*.mpfstyle.json - matplotlib finance stylesheets
|
|
*.richstyle.json - rich stylesheets
|
|
|
|
Parameters
|
|
----------
|
|
folder : str
|
|
Path to the folder containing the stylesheets
|
|
"""
|
|
for stf in os.listdir(folder):
|
|
if stf.endswith(".mplstyle"):
|
|
self.mpl_styles_available[stf.replace(".mplstyle", "")] = os.path.join(
|
|
folder, stf
|
|
)
|
|
elif stf.endswith(".mplrc.json"):
|
|
self.mpl_rcparams_available[
|
|
stf.replace(".mplrc.json", "")
|
|
] = os.path.join(folder, stf)
|
|
elif stf.endswith(".mpfstyle.json"):
|
|
self.mpf_styles_available[
|
|
stf.replace(".mpfstyle.json", "")
|
|
] = os.path.join(folder, stf)
|
|
elif stf.endswith(".richstyle.json"):
|
|
self.console_styles_available[
|
|
stf.replace(".richstyle.json", "")
|
|
] = os.path.join(folder, stf)
|
|
|
|
def applyMPLstyle(self):
|
|
"""Apply style to the current matplotlib context."""
|
|
plt.style.use(self.mpl_style)
|
|
self.xticks_rotation = self.mpl_rcparams["xticks_rotation"]
|
|
self.tight_layout_padding = self.mpl_rcparams["tight_layout_padding"]
|
|
self.pie_wedgeprops = self.mpl_rcparams["pie_wedgeprops"]
|
|
self.pie_startangle = self.mpl_rcparams["pie_startangle"]
|
|
self.mpf_style["mavcolors"] = plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
|
self.down_color = self.mpf_style["marketcolors"]["volume"]["down"]
|
|
self.up_color = self.mpf_style["marketcolors"]["volume"]["up"]
|
|
self.line_width = plt.rcParams["lines.linewidth"]
|
|
self.volume_bar_width = self.mpl_rcparams["volume_bar_width"]
|
|
|
|
def get_colors(self, reverse: bool = False) -> List:
|
|
"""Get hex color sequence from the stylesheet."""
|
|
plt.style.use(self.mpl_style)
|
|
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
|
if reverse:
|
|
colors.reverse()
|
|
return colors
|
|
|
|
def style_primary_axis(
|
|
self,
|
|
ax: plt.Axes,
|
|
data_index: Optional[List[int]] = None,
|
|
tick_labels: Optional[List[str]] = None,
|
|
):
|
|
"""Apply styling to a primary axis.
|
|
|
|
Parameters
|
|
----------
|
|
ax : plt.Axes
|
|
A matplolib axis
|
|
"""
|
|
ax.yaxis.set_label_position("right")
|
|
ax.grid(axis="both", visible=True, zorder=0)
|
|
if (
|
|
all([data_index, tick_labels])
|
|
and isinstance(data_index, list)
|
|
and isinstance(tick_labels, list)
|
|
):
|
|
ax.xaxis.set_major_formatter(
|
|
ticker.FuncFormatter(
|
|
lambda value, _: tick_labels[int(value)]
|
|
if int(value) in data_index
|
|
else ""
|
|
)
|
|
)
|
|
ax.xaxis.set_major_locator(ticker.MaxNLocator(6, integer=True))
|
|
ax.tick_params(axis="x", labelrotation=self.xticks_rotation)
|
|
|
|
def style_twin_axis(self, ax: plt.Axes):
|
|
"""Apply styling to a twin axis.
|
|
|
|
Parameters
|
|
----------
|
|
ax : plt.Axes
|
|
A matplolib axis
|
|
"""
|
|
ax.yaxis.set_label_position("left")
|
|
|
|
def style_twin_axes(self, ax1: plt.Axes, ax2: plt.Axes):
|
|
"""Apply styling to a twin axes
|
|
|
|
Parameters
|
|
----------
|
|
ax1 : plt.Axes
|
|
Primary matplolib axis
|
|
ax2 : plt.Axes
|
|
Twinx matplolib axis
|
|
|
|
"""
|
|
|
|
ax1.tick_params(axis="x", labelrotation=self.xticks_rotation)
|
|
ax1.grid(axis="both", visible=True, zorder=0)
|
|
|
|
ax2.grid(visible=False)
|
|
|
|
def add_label(
|
|
self,
|
|
fig: plt.figure,
|
|
):
|
|
"""Add a text label to a figure in a funny position.
|
|
|
|
Parameters
|
|
----------
|
|
fig : plt.figure
|
|
A matplotlib figure
|
|
"""
|
|
label = "Gamestonk Terminal"
|
|
fig.text(
|
|
0.69,
|
|
0.0420,
|
|
label,
|
|
fontsize=12,
|
|
color="gray",
|
|
alpha=0.5,
|
|
)
|
|
|
|
# pylint: disable=import-outside-toplevel
|
|
def visualize_output(self, force_tight_layout: bool = True):
|
|
"""Show chart in an interactive widget."""
|
|
import gamestonk_terminal.feature_flags as gtff
|
|
from gamestonk_terminal.rich_config import console
|
|
|
|
if gtff.USE_WATERMARK:
|
|
self.add_label(plt.gcf())
|
|
if force_tight_layout:
|
|
plt.tight_layout(pad=self.tight_layout_padding)
|
|
if gtff.USE_ION:
|
|
plt.ion()
|
|
plt.show()
|
|
console.print()
|