Handling log supression (#4625)

* simplifying the way we deal with the SystemModel by removing the need for system.json file

* removing the responsability of supression from the controllers

* supressing logs on reports

* fix etf report

* supressing logs on dashboards

* black

* typo

* ruff

* pylint

* refacotr def extract_parameters

---------

Co-authored-by: James Maslek <jmaslek11@gmail.com>
This commit is contained in:
Henrique Joaquim
2023-03-31 09:40:14 +01:00
committed by GitHub
parent 08f72e7e10
commit f87154beb8
15 changed files with 5590 additions and 5571 deletions

View File

@@ -3,7 +3,8 @@ from copy import deepcopy
# IMPORTS INTERNAL
from openbb_terminal.core.models import SystemModel
from openbb_terminal.core.session.system_handler import handle_system, save_system
from openbb_terminal.core.session.env_handler import read_env
from openbb_terminal.core.session.utils import load_dict_to_model
def get_current_system() -> SystemModel:
@@ -14,8 +15,8 @@ def get_current_system() -> SystemModel:
def set_current_system(system: SystemModel):
"""Set current system."""
global __system # pylint: disable=global-statement
save_system(system)
__system = system
__system = handle_system()
__env_dict = read_env()
__system = load_dict_to_model(__env_dict, SystemModel)

View File

@@ -1,86 +0,0 @@
import atexit
import json
import os
from pathlib import Path
from pydantic.json import pydantic_encoder
from openbb_terminal.core.config.paths import (
SETTINGS_DIRECTORY,
)
from openbb_terminal.core.models import SystemModel
from openbb_terminal.core.session.env_handler import read_env
from openbb_terminal.core.session.utils import load_dict_to_model
SYSTEM_FILE_PATH = SETTINGS_DIRECTORY / "system.json"
def handle_system(file_path: Path = SYSTEM_FILE_PATH) -> SystemModel:
"""
Handle the system.
Reads system data from file if it exists, else reads environment variable data.
Returns a system model built from the loaded data.
Parameters
----------
file_path (Path): The file path.
Returns
-------
SystemModel: The system model.
"""
try:
if os.path.isfile(file_path):
system_data = {}
with open(file_path) as f:
system_data = json.load(f)
if system_data:
return load_dict_to_model(system_data, SystemModel)
except Exception:
pass
__env_dict = read_env()
system = load_dict_to_model(__env_dict, SystemModel)
save_system(system=system, file_path=file_path)
subscribe_delete_system_on_exit(file_path=file_path)
return system
def save_system(system: SystemModel, file_path: Path = SYSTEM_FILE_PATH):
"""
Write system to file.
Parameters
----------
system (SystemModel): The system model.
file_path (Path): The file path.
Returns
-------
system (SystemModel): The system model.
"""
try:
with open(file_path, "w") as f:
json_content = json.dumps(system, default=pydantic_encoder)
f.write(json_content)
except Exception:
pass
def subscribe_delete_system_on_exit(file_path: Path):
"""
Deletes the system file on exiting the application.
Parameters
----------
file_path (Path): The file path.
"""
def delete_file(file_path: Path):
if os.path.isfile(file_path):
os.remove(file_path)
atexit.register(delete_file, file_path=file_path)

View File

@@ -18,7 +18,6 @@ import dotenv
import numpy as np
import psutil
import openbb_terminal.config_terminal as cfg
from openbb_terminal.base_helpers import load_env_vars, strtobool
from openbb_terminal.core.config.paths import REPOSITORY_DIRECTORY, SETTINGS_ENV_FILE
from openbb_terminal.core.plots.backend import plots_backend
@@ -199,7 +198,6 @@ class DashboardsController(BaseController):
cmd += " --theme=dark"
if ns_parser.input or response.lower() == "y" and not process_check:
cfg.change_logging_suppress(new_value=True)
self.processes.append(
psutil.Popen(
f"{cmd} --no-browser --port {port}"
@@ -211,7 +209,6 @@ class DashboardsController(BaseController):
env=os.environ,
)
)
cfg.change_logging_suppress(new_value=False)
atexit.register(self.kill_processes)
console.print(

View File

@@ -9,10 +9,17 @@ import streamlit as st
import yfinance as yf
from rich.table import Table
import openbb_terminal.config_terminal as cfg
from openbb_terminal.core.plots.plotly_helper import OpenBBFigure
from openbb_terminal.forecast import helpers
from openbb_terminal.rich_config import console
from openbb_terminal.sdk import openbb
# Suppressing sdk logs
cfg.change_logging_suppress(new_value=True)
# Import the OpenBB SDK
# pylint: disable=wrong-import-position
from openbb_terminal.sdk import openbb # noqa: E402
st.set_page_config(
layout="wide",

View File

@@ -8,11 +8,18 @@ import pandas as pd
import streamlit as st
from streamlit.delta_generator import DeltaGenerator
import openbb_terminal.config_terminal as cfg
from openbb_terminal.common.technical_analysis import ta_helpers
from openbb_terminal.core.plots.plotly_helper import OpenBBFigure
from openbb_terminal.core.plots.plotly_ta.ta_class import PlotlyTA
from openbb_terminal.rich_config import console
from openbb_terminal.sdk import openbb
# Suppressing sdk logs
cfg.change_logging_suppress(new_value=True)
# Import the OpenBB SDK
# pylint: disable=wrong-import-position
from openbb_terminal.sdk import openbb # noqa: E402
pd.options.plotting.backend = "plotly"
st.set_page_config(

View File

@@ -1,361 +1,374 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "e9ad60bc",
"metadata": {},
"outputs": [],
"source": [
"from datetime import datetime, timedelta\n",
"from typing import Callable, Any\n",
"from inspect import signature\n",
"import warnings, logging\n",
"\n",
"import ipywidgets as widgets\n",
"import yfinance as yf\n",
"import pandas as pd\n",
"from IPython.display import display\n",
"\n",
"from openbb_terminal.sdk import openbb, theme\n",
"\n",
"theme.apply_style()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "beb36881",
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" has_forecast = True\n",
" model_opts = {\n",
" \"expo\": openbb.forecast.expo_chart,\n",
" \"theta\": openbb.forecast.theta_chart,\n",
" \"linregr\": openbb.forecast.linregr_chart,\n",
" \"regr\": openbb.forecast.regr_chart,\n",
" \"rnn\": openbb.forecast.rnn_chart,\n",
" \"brnn\": openbb.forecast.brnn_chart,\n",
" \"nbeats\": openbb.forecast.nbeats_chart,\n",
" \"tcn\": openbb.forecast.tcn_chart,\n",
" \"trans\": openbb.forecast.trans_chart,\n",
" \"tft\": openbb.forecast.tft_chart,\n",
" \"nhits\": openbb.forecast.nhits_chart,\n",
" }\n",
"\n",
" feat_engs = {\n",
" \"ema\": openbb.forecast.ema,\n",
" \"sto\": openbb.forecast.sto,\n",
" \"rsi\": openbb.forecast.rsi,\n",
" \"roc\": openbb.forecast.roc,\n",
" \"mom\": openbb.forecast.mom,\n",
" \"atr\": openbb.forecast.atr,\n",
" \"delta\": openbb.forecast.delta,\n",
" \"signal\": openbb.forecast.signal,\n",
" }\n",
"except AttributeError as e:\n",
" print(e)\n",
" has_forecast = False\n",
" model_opts = {}\n",
" feat_engs = {}\n",
"\n",
"interval_opts = [\n",
" \"1m\",\n",
" \"2m\",\n",
" \"5m\",\n",
" \"15m\",\n",
" \"30m\",\n",
" \"60m\",\n",
" \"90m\",\n",
" \"1h\",\n",
" \"1d\",\n",
" \"5d\",\n",
" \"1wk\",\n",
" \"1mo\",\n",
" \"3mo\",\n",
"]\n",
"\n",
"\n",
"def format_df(df: pd.DataFrame) -> pd.DataFrame:\n",
" if len(df.columns) != 6:\n",
" df.columns = [\"_\".join(col).strip() for col in df.columns.values]\n",
" df.reset_index(inplace=True)\n",
" df.columns = [x.lower() for x in df.columns]\n",
" return df\n",
"\n",
"\n",
"def has_parameter(func: Callable[..., Any], parameter: str) -> bool:\n",
" params = signature(func).parameters\n",
" parameters = params.keys()\n",
" return parameter in parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8c2f65c",
"metadata": {
"tags": [
"parameters"
]
},
"outputs": [],
"source": [
"class Chart:\n",
" def __init__(self):\n",
" self.last_tickers = \"\"\n",
" self.last_interval = \"1d\"\n",
" self.df = pd.DataFrame()\n",
" self.result = pd.DataFrame()\n",
" self.infos = {}\n",
"\n",
" def handle_changes(\n",
" self,\n",
" past_covariates,\n",
" start,\n",
" end,\n",
" interval,\n",
" tickers,\n",
" target_column,\n",
" model,\n",
" naive,\n",
" forecast_only,\n",
" ):\n",
" ticker_list = tickers.split(\",\")\n",
" if ticker_list:\n",
" if tickers != self.last_tickers or interval != self.last_interval:\n",
" if interval in [\"1d\", \"5d\", \"1wk\", \"1mo\", \"3mo\"]:\n",
" self.df = yf.download(\n",
" tickers, period=\"max\", interval=interval, progress=False\n",
" )\n",
" else:\n",
" end_date = end + timedelta(days=1)\n",
" self.df = yf.download(\n",
" tickers,\n",
" start=start,\n",
" end=end_date,\n",
" interval=interval,\n",
" progress=False,\n",
" )\n",
" self.df = format_df(self.df)\n",
" self.last_tickers = tickers\n",
" self.last_interval = interval\n",
" forecast_model = model_opts[model]\n",
" self.forecast_model = forecast_model\n",
" contains_covariates = has_parameter(forecast_model, \"past_covariates\")\n",
"\n",
" # Update Inputs\n",
" if list(target_widget.options) != [\n",
" x for x in self.df.columns if x != \"date\"\n",
" ]:\n",
" target_widget.options = [x for x in self.df.columns if x != \"date\"]\n",
" return\n",
" if list(past_covs_widget.options) != [\n",
" x for x in self.df.columns if x != \"date\"\n",
" ]:\n",
" past_covs_widget.options = [x for x in self.df.columns if x != \"date\"]\n",
" past_covs_widget.disabled = not contains_covariates\n",
" return\n",
" if past_covs_widget.disabled == contains_covariates:\n",
" past_covs_widget.disabled = not contains_covariates\n",
" column_widget.options = [x for x in self.df.columns if x != \"date\"]\n",
"\n",
" start_n = datetime(start.year, start.month, start.day)\n",
" end_n = datetime(end.year, end.month, end.day)\n",
" calcs = self.df\n",
" if interval in [\"1d\", \"5d\", \"1wk\", \"1mo\", \"3mo\"]:\n",
" result = calcs.loc[\n",
" (calcs[\"date\"] >= start_n) & (calcs[\"date\"] <= end_n)\n",
" ]\n",
" else:\n",
" result = calcs\n",
" if not target_column:\n",
" target_column = self.df.columns[0]\n",
" kwargs = {}\n",
" if contains_covariates and past_covariates != ():\n",
" kwargs[\"past_covariates\"] = \",\".join(past_covariates)\n",
" if has_parameter(forecast_model, \"naive\"):\n",
" kwargs[\"naive\"] = naive\n",
" if has_parameter(forecast_model, \"forecast_only\"):\n",
" kwargs[\"forecast_only\"] = forecast_only\n",
" # This sets up everything to run the function on button click\n",
"\n",
" self.result = result\n",
" self.target_column = target_column\n",
" self.n_predict = 5\n",
" self.kwargs = kwargs\n",
"\n",
" def handle_click(self, to_run):\n",
" if to_run:\n",
" run_widget.value = False\n",
" else:\n",
" df = self.result.dropna()\n",
" if not df.empty:\n",
" self.forecast_model(\n",
" self.result,\n",
" target_column=self.target_column,\n",
" n_predict=5,\n",
" **self.kwargs\n",
" )\n",
"\n",
" def handle_eng(self, target, feature):\n",
" self.feature_target = target\n",
" self.feature_model = feat_engs[feature]\n",
"\n",
" def handle_click2(self, to_run):\n",
" if to_run:\n",
" add_widget.value = False\n",
" else:\n",
" kwargs = {}\n",
" if has_parameter(self.feature_model, \"target_column\"):\n",
" kwargs[\"target_column\"] = self.feature_target\n",
" self.df = self.feature_model(self.df, **kwargs)\n",
" past_covs_widget.options = self.df.columns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0d4de31-1b4f-4c1f-8467-ca314a578d56",
"metadata": {},
"outputs": [],
"source": [
"w_auto = widgets.Layout(width=\"auto\")\n",
"model_value = list(model_opts)[0] if model_opts else None\n",
"model_widget = widgets.Select(\n",
" options=list(model_opts),\n",
" value=model_value,\n",
" layout=widgets.Layout(\n",
" width=\"8%\",\n",
" ),\n",
")\n",
"past_covs_widget = widgets.SelectMultiple(\n",
" options=[\"\"],\n",
" value=[\"\"],\n",
" layout=widgets.Layout(\n",
" width=\"8%\",\n",
" ),\n",
")\n",
"\n",
"base_date = (datetime.today() - timedelta(days=365)).date()\n",
"start_widget = widgets.DatePicker(value=base_date, layout=w_auto, description=\"Start\")\n",
"end_widget = widgets.DatePicker(\n",
" value=datetime.today().date(), layout=w_auto, description=\"End\"\n",
")\n",
"\n",
"target_widget = widgets.Dropdown(options=[\"\"], value=\"\", description=\"Target\")\n",
"interval_widget = widgets.Dropdown(\n",
" options=interval_opts, value=\"1d\", layout=w_auto, description=\"Interval\"\n",
")\n",
"tickers_widget = widgets.Textarea(\n",
" value=\"TSLA,\", layout=widgets.Layout(width=\"auto\", height=\"100%\")\n",
")\n",
"\n",
"# Output box\n",
"naive_widget = widgets.ToggleButton(value=False, description=\"Show Naive\")\n",
"forecast_only_widget = widgets.ToggleButton(value=False, description=\"Forecast Only\")\n",
"run_widget = widgets.ToggleButton(value=False, description=\"Run Model\")\n",
"\n",
"# feat_box\n",
"feat_value = list(feat_engs.keys())[0] if feat_engs else None\n",
"column_widget = widgets.Dropdown(options=[\"\"], value=\"\", description=\"Target\")\n",
"feat_widget = widgets.Dropdown(\n",
" options=list(feat_engs.keys()), value=feat_value, description=\"Feature\"\n",
")\n",
"add_widget = widgets.ToggleButton(\n",
" value=False, description=\"Add Feature\", layout=widgets.Layout(align=\"flex_end\")\n",
") # get this to work\n",
"\n",
"selection_box = widgets.VBox([tickers_widget, target_widget])\n",
"date_box = widgets.VBox([start_widget, end_widget, interval_widget])\n",
"output_box = widgets.VBox([naive_widget, forecast_only_widget, run_widget])\n",
"feat_box = widgets.VBox([column_widget, feat_widget, add_widget])\n",
"controls = widgets.HBox(\n",
" [selection_box, model_widget, past_covs_widget, date_box, output_box, feat_box],\n",
")\n",
"if has_forecast:\n",
" chart = Chart()\n",
" widgets.interactive_output(\n",
" chart.handle_changes,\n",
" {\n",
" \"past_covariates\": past_covs_widget,\n",
" \"start\": start_widget,\n",
" \"end\": end_widget,\n",
" \"interval\": interval_widget,\n",
" \"tickers\": tickers_widget,\n",
" \"target_column\": target_widget,\n",
" \"model\": model_widget,\n",
" \"naive\": naive_widget,\n",
" \"forecast_only\": forecast_only_widget,\n",
" },\n",
" )\n",
"\n",
" widgets.interactive_output(\n",
" chart.handle_eng, {\"target\": column_widget, \"feature\": feat_widget}\n",
" )\n",
" widgets.interactive_output(chart.handle_click2, {\"to_run\": add_widget})\n",
"\n",
" stocks_view = widgets.interactive_output(chart.handle_click, {\"to_run\": run_widget})\n",
"\n",
" title_html = \"<h1>Timeseries Forecasting Dashboard</h1>\"\n",
" warning_html = '<p style=\"color:red\"=>Use a comma after EVERY stock typed.</p>'\n",
"\n",
" app_contents = [\n",
" widgets.HTML(title_html),\n",
" controls,\n",
" widgets.HTML(warning_html),\n",
" stocks_view,\n",
" ]\n",
" app = widgets.VBox(app_contents)\n",
"else:\n",
" title_html = \"<h1>Timeseries Forecasting Dashboard</h1>\"\n",
" warning_html = (\n",
" '<p style=\"color:red\"=>The forecasting dependencies are not installed.</p>'\n",
" )\n",
" app = widgets.VBox([widgets.HTML(title_html), widgets.HTML(warning_html)])\n",
"display(app)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9dbf43a2-70e1-44e9-a524-be3e97008b58",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
},
"metadata": {
"interpreter": {
"hash": "e896a00ead8b521528d79ac9ef24990696f2b751eb283ab8e0d078c9c4971ffc"
}
},
"vscode": {
"interpreter": {
"hash": "bd3088e2ce83c7406a082a5b650cfc74fd1480320510d276c9fa5780b96e5fae"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "e9ad60bc",
"metadata": {},
"outputs": [],
"source": [
"from datetime import datetime, timedelta\n",
"from typing import Callable, Any\n",
"from inspect import signature\n",
"import warnings, logging\n",
"\n",
"import ipywidgets as widgets\n",
"import yfinance as yf\n",
"import pandas as pd\n",
"from IPython.display import display\n",
"\n",
"theme.apply_style()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "404cb982",
"metadata": {},
"outputs": [],
"source": [
"# Suppressing sdk logs\n",
"import openbb_terminal.config_terminal as cfg\n",
"cfg.change_logging_suppress(new_value=True)\n",
"\n",
"# Import the OpenBB SDK\n",
"from openbb_terminal.sdk import openbb, widgets"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "beb36881",
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" has_forecast = True\n",
" model_opts = {\n",
" \"expo\": openbb.forecast.expo_chart,\n",
" \"theta\": openbb.forecast.theta_chart,\n",
" \"linregr\": openbb.forecast.linregr_chart,\n",
" \"regr\": openbb.forecast.regr_chart,\n",
" \"rnn\": openbb.forecast.rnn_chart,\n",
" \"brnn\": openbb.forecast.brnn_chart,\n",
" \"nbeats\": openbb.forecast.nbeats_chart,\n",
" \"tcn\": openbb.forecast.tcn_chart,\n",
" \"trans\": openbb.forecast.trans_chart,\n",
" \"tft\": openbb.forecast.tft_chart,\n",
" \"nhits\": openbb.forecast.nhits_chart,\n",
" }\n",
"\n",
" feat_engs = {\n",
" \"ema\": openbb.forecast.ema,\n",
" \"sto\": openbb.forecast.sto,\n",
" \"rsi\": openbb.forecast.rsi,\n",
" \"roc\": openbb.forecast.roc,\n",
" \"mom\": openbb.forecast.mom,\n",
" \"atr\": openbb.forecast.atr,\n",
" \"delta\": openbb.forecast.delta,\n",
" \"signal\": openbb.forecast.signal,\n",
" }\n",
"except AttributeError as e:\n",
" print(e)\n",
" has_forecast = False\n",
" model_opts = {}\n",
" feat_engs = {}\n",
"\n",
"interval_opts = [\n",
" \"1m\",\n",
" \"2m\",\n",
" \"5m\",\n",
" \"15m\",\n",
" \"30m\",\n",
" \"60m\",\n",
" \"90m\",\n",
" \"1h\",\n",
" \"1d\",\n",
" \"5d\",\n",
" \"1wk\",\n",
" \"1mo\",\n",
" \"3mo\",\n",
"]\n",
"\n",
"\n",
"def format_df(df: pd.DataFrame) -> pd.DataFrame:\n",
" if len(df.columns) != 6:\n",
" df.columns = [\"_\".join(col).strip() for col in df.columns.values]\n",
" df.reset_index(inplace=True)\n",
" df.columns = [x.lower() for x in df.columns]\n",
" return df\n",
"\n",
"\n",
"def has_parameter(func: Callable[..., Any], parameter: str) -> bool:\n",
" params = signature(func).parameters\n",
" parameters = params.keys()\n",
" return parameter in parameters"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8c2f65c",
"metadata": {
"tags": [
"parameters"
]
},
"outputs": [],
"source": [
"class Chart:\n",
" def __init__(self):\n",
" self.last_tickers = \"\"\n",
" self.last_interval = \"1d\"\n",
" self.df = pd.DataFrame()\n",
" self.result = pd.DataFrame()\n",
" self.infos = {}\n",
"\n",
" def handle_changes(\n",
" self,\n",
" past_covariates,\n",
" start,\n",
" end,\n",
" interval,\n",
" tickers,\n",
" target_column,\n",
" model,\n",
" naive,\n",
" forecast_only,\n",
" ):\n",
" ticker_list = tickers.split(\",\")\n",
" if ticker_list:\n",
" if tickers != self.last_tickers or interval != self.last_interval:\n",
" if interval in [\"1d\", \"5d\", \"1wk\", \"1mo\", \"3mo\"]:\n",
" self.df = yf.download(\n",
" tickers, period=\"max\", interval=interval, progress=False\n",
" )\n",
" else:\n",
" end_date = end + timedelta(days=1)\n",
" self.df = yf.download(\n",
" tickers,\n",
" start=start,\n",
" end=end_date,\n",
" interval=interval,\n",
" progress=False,\n",
" )\n",
" self.df = format_df(self.df)\n",
" self.last_tickers = tickers\n",
" self.last_interval = interval\n",
" forecast_model = model_opts[model]\n",
" self.forecast_model = forecast_model\n",
" contains_covariates = has_parameter(forecast_model, \"past_covariates\")\n",
"\n",
" # Update Inputs\n",
" if list(target_widget.options) != [\n",
" x for x in self.df.columns if x != \"date\"\n",
" ]:\n",
" target_widget.options = [x for x in self.df.columns if x != \"date\"]\n",
" return\n",
" if list(past_covs_widget.options) != [\n",
" x for x in self.df.columns if x != \"date\"\n",
" ]:\n",
" past_covs_widget.options = [x for x in self.df.columns if x != \"date\"]\n",
" past_covs_widget.disabled = not contains_covariates\n",
" return\n",
" if past_covs_widget.disabled == contains_covariates:\n",
" past_covs_widget.disabled = not contains_covariates\n",
" column_widget.options = [x for x in self.df.columns if x != \"date\"]\n",
"\n",
" start_n = datetime(start.year, start.month, start.day)\n",
" end_n = datetime(end.year, end.month, end.day)\n",
" calcs = self.df\n",
" if interval in [\"1d\", \"5d\", \"1wk\", \"1mo\", \"3mo\"]:\n",
" result = calcs.loc[\n",
" (calcs[\"date\"] >= start_n) & (calcs[\"date\"] <= end_n)\n",
" ]\n",
" else:\n",
" result = calcs\n",
" if not target_column:\n",
" target_column = self.df.columns[0]\n",
" kwargs = {}\n",
" if contains_covariates and past_covariates != ():\n",
" kwargs[\"past_covariates\"] = \",\".join(past_covariates)\n",
" if has_parameter(forecast_model, \"naive\"):\n",
" kwargs[\"naive\"] = naive\n",
" if has_parameter(forecast_model, \"forecast_only\"):\n",
" kwargs[\"forecast_only\"] = forecast_only\n",
" # This sets up everything to run the function on button click\n",
"\n",
" self.result = result\n",
" self.target_column = target_column\n",
" self.n_predict = 5\n",
" self.kwargs = kwargs\n",
"\n",
" def handle_click(self, to_run):\n",
" if to_run:\n",
" run_widget.value = False\n",
" else:\n",
" df = self.result.dropna()\n",
" if not df.empty:\n",
" self.forecast_model(\n",
" self.result,\n",
" target_column=self.target_column,\n",
" n_predict=5,\n",
" **self.kwargs\n",
" )\n",
"\n",
" def handle_eng(self, target, feature):\n",
" self.feature_target = target\n",
" self.feature_model = feat_engs[feature]\n",
"\n",
" def handle_click2(self, to_run):\n",
" if to_run:\n",
" add_widget.value = False\n",
" else:\n",
" kwargs = {}\n",
" if has_parameter(self.feature_model, \"target_column\"):\n",
" kwargs[\"target_column\"] = self.feature_target\n",
" self.df = self.feature_model(self.df, **kwargs)\n",
" past_covs_widget.options = self.df.columns"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e0d4de31-1b4f-4c1f-8467-ca314a578d56",
"metadata": {},
"outputs": [],
"source": [
"w_auto = widgets.Layout(width=\"auto\")\n",
"model_value = list(model_opts)[0] if model_opts else None\n",
"model_widget = widgets.Select(\n",
" options=list(model_opts),\n",
" value=model_value,\n",
" layout=widgets.Layout(\n",
" width=\"8%\",\n",
" ),\n",
")\n",
"past_covs_widget = widgets.SelectMultiple(\n",
" options=[\"\"],\n",
" value=[\"\"],\n",
" layout=widgets.Layout(\n",
" width=\"8%\",\n",
" ),\n",
")\n",
"\n",
"base_date = (datetime.today() - timedelta(days=365)).date()\n",
"start_widget = widgets.DatePicker(value=base_date, layout=w_auto, description=\"Start\")\n",
"end_widget = widgets.DatePicker(\n",
" value=datetime.today().date(), layout=w_auto, description=\"End\"\n",
")\n",
"\n",
"target_widget = widgets.Dropdown(options=[\"\"], value=\"\", description=\"Target\")\n",
"interval_widget = widgets.Dropdown(\n",
" options=interval_opts, value=\"1d\", layout=w_auto, description=\"Interval\"\n",
")\n",
"tickers_widget = widgets.Textarea(\n",
" value=\"TSLA,\", layout=widgets.Layout(width=\"auto\", height=\"100%\")\n",
")\n",
"\n",
"# Output box\n",
"naive_widget = widgets.ToggleButton(value=False, description=\"Show Naive\")\n",
"forecast_only_widget = widgets.ToggleButton(value=False, description=\"Forecast Only\")\n",
"run_widget = widgets.ToggleButton(value=False, description=\"Run Model\")\n",
"\n",
"# feat_box\n",
"feat_value = list(feat_engs.keys())[0] if feat_engs else None\n",
"column_widget = widgets.Dropdown(options=[\"\"], value=\"\", description=\"Target\")\n",
"feat_widget = widgets.Dropdown(\n",
" options=list(feat_engs.keys()), value=feat_value, description=\"Feature\"\n",
")\n",
"add_widget = widgets.ToggleButton(\n",
" value=False, description=\"Add Feature\", layout=widgets.Layout(align=\"flex_end\")\n",
") # get this to work\n",
"\n",
"selection_box = widgets.VBox([tickers_widget, target_widget])\n",
"date_box = widgets.VBox([start_widget, end_widget, interval_widget])\n",
"output_box = widgets.VBox([naive_widget, forecast_only_widget, run_widget])\n",
"feat_box = widgets.VBox([column_widget, feat_widget, add_widget])\n",
"controls = widgets.HBox(\n",
" [selection_box, model_widget, past_covs_widget, date_box, output_box, feat_box],\n",
")\n",
"if has_forecast:\n",
" chart = Chart()\n",
" widgets.interactive_output(\n",
" chart.handle_changes,\n",
" {\n",
" \"past_covariates\": past_covs_widget,\n",
" \"start\": start_widget,\n",
" \"end\": end_widget,\n",
" \"interval\": interval_widget,\n",
" \"tickers\": tickers_widget,\n",
" \"target_column\": target_widget,\n",
" \"model\": model_widget,\n",
" \"naive\": naive_widget,\n",
" \"forecast_only\": forecast_only_widget,\n",
" },\n",
" )\n",
"\n",
" widgets.interactive_output(\n",
" chart.handle_eng, {\"target\": column_widget, \"feature\": feat_widget}\n",
" )\n",
" widgets.interactive_output(chart.handle_click2, {\"to_run\": add_widget})\n",
"\n",
" stocks_view = widgets.interactive_output(chart.handle_click, {\"to_run\": run_widget})\n",
"\n",
" title_html = \"<h1>Timeseries Forecasting Dashboard</h1>\"\n",
" warning_html = '<p style=\"color:red\"=>Use a comma after EVERY stock typed.</p>'\n",
"\n",
" app_contents = [\n",
" widgets.HTML(title_html),\n",
" controls,\n",
" widgets.HTML(warning_html),\n",
" stocks_view,\n",
" ]\n",
" app = widgets.VBox(app_contents)\n",
"else:\n",
" title_html = \"<h1>Timeseries Forecasting Dashboard</h1>\"\n",
" warning_html = (\n",
" '<p style=\"color:red\"=>The forecasting dependencies are not installed.</p>'\n",
" )\n",
" app = widgets.VBox([widgets.HTML(title_html), widgets.HTML(warning_html)])\n",
"display(app)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9dbf43a2-70e1-44e9-a524-be3e97008b58",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
},
"metadata": {
"interpreter": {
"hash": "e896a00ead8b521528d79ac9ef24990696f2b751eb283ab8e0d078c9c4971ffc"
}
},
"vscode": {
"interpreter": {
"hash": "bd3088e2ce83c7406a082a5b650cfc74fd1480320510d276c9fa5780b96e5fae"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -8,7 +8,6 @@ import os
# pylint: disable=R1732, R0912
from typing import Any, Dict, List, Optional
import openbb_terminal.config_terminal as cfg
from openbb_terminal.core.session.current_user import get_current_user
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
from openbb_terminal.decorators import log_start_end
@@ -191,7 +190,6 @@ class ReportController(BaseController):
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
cfg.change_logging_suppress(new_value=True)
if report_name == "equity" and "." in ns_parser.symbol:
console.print(
"[red]Cannot currently handle tickers with a '.' in them[/red]\n"
@@ -203,7 +201,6 @@ class ReportController(BaseController):
input_path=str(reports_model.REPORTS_FOLDER / report_name),
args_dict=parameters,
)
cfg.change_logging_suppress(new_value=False)
else:
console.print("[red]Notebook not found![/red]\n")

View File

@@ -5,12 +5,12 @@ import logging
# pylint: disable=R1732, R0912
import os
from ast import literal_eval
from datetime import datetime
from pathlib import Path
from threading import Thread
from typing import Any, Dict, List, Union
import nbformat
import pandas as pd
import papermill as pm
from ipykernel.kernelapp import IPKernelApp
@@ -85,6 +85,7 @@ REPORT_CHOICES = {
"--symbol": {c: None for c in STOCKS_TICKERS + ETF_TICKERS},
},
}
TARGET_TAG = "parameters"
@log_start_end(log=logger)
@@ -157,43 +158,34 @@ def extract_parameters(input_path: str) -> Dict[str, str]:
input_path = add_ipynb_extension(input_path)
with open(input_path, encoding="utf-8") as file:
notebook_content = file.read()
with open(input_path) as f:
nb = nbformat.read(f, as_version=nbformat.NO_CONVERT)
# Look for the metadata cell to understand if there are parameters required by the report
metadata_cell = """"metadata": {\n "tags": [\n "parameters"\n ]\n },\n "outputs":"""
target_tag = TARGET_TAG
# Locate position of the data of interest and get parameters
if notebook_content.find(metadata_cell) >= 0:
position = notebook_content.find(metadata_cell)
else:
return {}
cell_lines = []
for cell in nb.cells:
# Filter cells by tag
if target_tag in cell.metadata.get("tags", []):
# Split cell source into separate lines
lines = cell.source.split("\n")
# Remove empty and commented lines
lines = [
line for line in lines if line.strip() and not line.startswith("#")
]
cell_lines.extend(lines)
metadata = notebook_content[position:] # noqa: E203
cell_start = 'source": '
cell_end = "]"
start_position = metadata.find(cell_start)
params = metadata[
start_position : metadata.find(cell_end, start_position) + 1 # noqa: E203
]
parameters_dict = {}
for line in cell_lines:
# Extract key and value from each line
key, value = line.split("=")
key = key.strip()
value = value.strip()
# Add key-value pair to dictionary
parameters_dict[key] = value
# Make sure that the parameters provided are relevant
if "parameters" in notebook_content:
parameters_names = [
param.split("=")[0][:-1]
for param in literal_eval(params.strip('source": '))
if param[0] not in ["#", "\n"]
]
parameters_values = [
param.split("=")[1][2:-2]
for param in literal_eval(params.strip('source": '))
if param[0] not in ["#", "\n"]
]
if "report_name" in parameters_names:
parameters_names.remove("report_name")
parameters_dict = dict(zip(parameters_names, parameters_values))
if "report_name" in parameters_dict:
parameters_dict.pop("report_name")
return parameters_dict

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,350 +1,364 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "70ba2c8a",
"metadata": {},
"source": [
"## Notebook setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "driven-billion",
"metadata": {},
"outputs": [],
"source": [
"import warnings\n",
"import numpy as np\n",
"import datetime\n",
"\n",
"from IPython.display import HTML\n",
"\n",
"import openbb_terminal.config_terminal as cfg\n",
"from openbb_terminal.reports import widget_helpers as widgets\n",
"from openbb_terminal.sdk import openbb\n",
"from openbb_terminal.core.config.paths import REPOSITORY_DIRECTORY\n",
"from openbb_terminal import OpenBBFigure, theme\n",
"from openbb_terminal.helper_classes import TerminalStyle\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3fe7db5-ec6a-42cf-9e66-52dc1de22370",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.core.session.current_user import get_current_user\n",
"\n",
"user = get_current_user()\n",
"\n",
"cfg.theme = TerminalStyle(\n",
" user.preferences.MPL_STYLE,\n",
" user.preferences.PMF_STYLE,\n",
" user.preferences.RICH_STYLE,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36091f30",
"metadata": {},
"outputs": [],
"source": [
"stylesheet = widgets.html_report_stylesheet()"
]
},
{
"cell_type": "markdown",
"id": "807fda4e",
"metadata": {},
"source": [
"## Select Ticker"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "intended-sherman",
"metadata": {
"tags": [
"parameters"
]
},
"outputs": [],
"source": [
"# Parameters that will be replaced when calling this notebook\n",
"# Do not leave parameters blank as notebook will not run otherwise\n",
"symbol = \"VTI\"\n",
"report_name = \"Equity Report for VTI\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1f87190",
"metadata": {},
"outputs": [],
"source": [
"ticker = symbol"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a441ddbc-d75b-4a18-84d4-e01b677674d3",
"metadata": {},
"outputs": [],
"source": [
"if \".\" in ticker:\n",
" import sys\n",
"\n",
" sys.exit(0)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1be26dae-cafe-4a22-80aa-eff296fc1a9b",
"metadata": {},
"outputs": [],
"source": [
"ticker_data = openbb.stocks.load(\n",
" symbol=ticker, start_date=datetime.datetime.now() - datetime.timedelta(days=4 * 30)\n",
")\n",
"ticker_data = openbb.stocks.process_candle(data=ticker_data)\n",
"\n",
"author = \"OpenBB\"\n",
"report_title = f\"ETF RESEARCH REPORT ON {ticker.upper()}\"\n",
"report_date = datetime.datetime.now().strftime(\"%d %B, %Y\")\n",
"report_time = datetime.datetime.now().strftime(\"%H:%M\")\n",
"report_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo\n",
"report_title, report_date, report_time, report_timezone"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ecfcf81",
"metadata": {},
"outputs": [],
"source": [
"# info = openbb.etf.summary(name=ticker)\n",
"# info"
]
},
{
"cell_type": "markdown",
"id": "f8a0d747",
"metadata": {},
"source": [
"## Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39b8e77d",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.stocks.load(ticker)\n",
"\n",
"candle_chart = openbb.etf.candle(\n",
" symbol=ticker,\n",
" data=data,\n",
" external_axes=True,\n",
")\n",
"\n",
"if candle_chart:\n",
" candle_chart = candle_chart.to_html()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f751f57c",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"\n",
"sectors = openbb.etf.weights(ticker)\n",
"sector_weights_formatted = {}\n",
"for sector_weight in sectors:\n",
" sector_weights_formatted[sector_weight[\"sector\"]] = (\n",
" float(sector_weight[\"weightPercentage\"].strip(\"%\")) / 100\n",
" )\n",
"sector_weights_formatted = dict(sorted(sector_weights_formatted.items()))\n",
"weights_df = pd.DataFrame.from_dict(sector_weights_formatted, orient=\"index\")\n",
"weights_df.columns = [\"Percentage\"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "324e2d4b-b2f6-4da3-bac4-ef785ad4e436",
"metadata": {},
"outputs": [],
"source": [
"weights_chart = \"\"\n",
"\n",
"if len(weights_df[weights_df[\"Percentage\"] > 10].index) > 1:\n",
" colors = theme.get_colors()\n",
" fig = OpenBBFigure.create_subplots(\n",
" 1,\n",
" 3,\n",
" specs=[[{\"type\": \"domain\"}, {\"type\": \"pie\", \"colspan\": 2}, None]],\n",
" row_heights=[1],\n",
" column_widths=[0.1, 0.8, 0.1],\n",
" )\n",
"\n",
" fig.add_pie(\n",
" labels=weights_df[weights_df[\"Percentage\"] > 10].index,\n",
" values=weights_df[weights_df[\"Percentage\"] > 10][\"Percentage\"],\n",
" textinfo=\"label+percent\",\n",
" hoverinfo=\"label+percent\",\n",
" automargin=True,\n",
" rotation=45,\n",
" row=1,\n",
" col=2,\n",
" )\n",
" fig.update_traces(\n",
" textposition=\"outside\",\n",
" textfont_size=15,\n",
" marker=dict(\n",
" colors=colors,\n",
" line=dict(color=\"#F5EFF3\", width=0.8),\n",
" ),\n",
" )\n",
"\n",
" fig.update_layout(\n",
" margin=dict(t=40, b=20),\n",
" title=dict(\n",
" text=f\"Sector holdings of {ticker}\",\n",
" y=0.98,\n",
" x=0.5,\n",
" xanchor=\"center\",\n",
" yanchor=\"top\",\n",
" ),\n",
" colorway=colors,\n",
" showlegend=False,\n",
" )\n",
"\n",
" weights_chart = fig.to_html()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8bb53c1-147e-4342-af61-0d0a9cc56e59",
"metadata": {},
"outputs": [],
"source": [
"holdings = openbb.etf.holdings(ticker)\n",
"holdings"
]
},
{
"cell_type": "markdown",
"id": "ac501a32-124d-4d21-aae1-a8d147bb1071",
"metadata": {},
"source": [
"## Render the report template to a file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "abfe37e4-ea38-42c3-8fb5-7cceb233fb12",
"metadata": {},
"outputs": [],
"source": [
"body = f\"\"\"\n",
"<style>\n",
"@font-face {{\n",
" font-family: \"Fira Code\";\n",
" src: url(\"{(OpenBBFigure.plotlyjs_path.parent.parent / \"web\"/\"css\").as_uri()}/fonts/FiraCode-Regular.ttf\") format(\"truetype\");\n",
" font-weight: 400 700;\n",
" font-stretch: 50%;\n",
"}}\n",
"</style>\n",
"<script src=\"/{OpenBBFigure.plotlyjs_path.as_uri()}\"></script>\n",
"\"\"\"\n",
"\n",
"img = (\n",
" str(REPOSITORY_DIRECTORY)\n",
" + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
")\n",
"floppy_disk_img = (\n",
" str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
")\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
" author,\n",
" report_date,\n",
" report_time,\n",
" report_timezone,\n",
" f\"<b>ETF RESEARCH REPORT:</b> {ticker.upper()}\",\n",
")\n",
"\n",
"body += widgets.tablinks([\"SUMMARY\"])\n",
"\n",
"htmlcode = widgets.h(3, \"Summary\")\n",
"# htmlcode += widgets.p(info)\n",
"htmlcode += widgets.row([candle_chart])\n",
"\n",
"htmlcode += widgets.p(\"The ETF has the following weights distribution:\")\n",
"\n",
"htmlcode += widgets.row([weights_df.to_html()])\n",
"htmlcode += widgets.row([weights_chart])\n",
"\n",
"htmlcode += widgets.p(\"And has funds in the following assets:\")\n",
"htmlcode += widgets.row([holdings.to_html()])\n",
"\n",
"body += widgets.add_tab(\"SUMMARY\", htmlcode)\n",
"\n",
"body += widgets.tab_clickable_and_save_evt()\n",
"\n",
"report = widgets.html_report(title=report_name, stylesheet=stylesheet, body=body)\n",
"\n",
"# to save the results\n",
"with open(report_name + \".html\", \"w\", encoding=\"utf-8\") as fh:\n",
" fh.write(report)"
]
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.9"
},
"vscode": {
"interpreter": {
"hash": "381a209f752e9104bff95ec5e80679ea2e476866b80e4326f1ee6b7658af6049"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}
"cells": [
{
"cell_type": "markdown",
"id": "70ba2c8a",
"metadata": {},
"source": [
"## Notebook setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "driven-billion",
"metadata": {},
"outputs": [],
"source": [
"import warnings\n",
"import numpy as np\n",
"import datetime\n",
"\n",
"from IPython.display import HTML\n",
"\n",
"import openbb_terminal.config_terminal as cfg\n",
"from openbb_terminal.core.config.paths import REPOSITORY_DIRECTORY\n",
"from openbb_terminal import OpenBBFigure, theme\n",
"from openbb_terminal.helper_classes import TerminalStyle\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "473e83c7",
"metadata": {},
"outputs": [],
"source": [
"# Suppressing sdk logs\n",
"cfg.change_logging_suppress(new_value=True)\n",
"\n",
"# Import the OpenBB SDK\n",
"from openbb_terminal.sdk import openbb, widgets"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3fe7db5-ec6a-42cf-9e66-52dc1de22370",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.core.session.current_user import get_current_user\n",
"\n",
"user = get_current_user()\n",
"\n",
"cfg.theme = TerminalStyle(\n",
" user.preferences.MPL_STYLE,\n",
" user.preferences.PMF_STYLE,\n",
" user.preferences.RICH_STYLE,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36091f30",
"metadata": {},
"outputs": [],
"source": [
"stylesheet = widgets.html_report_stylesheet()"
]
},
{
"cell_type": "markdown",
"id": "807fda4e",
"metadata": {},
"source": [
"## Select Ticker"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "intended-sherman",
"metadata": {
"tags": [
"parameters"
]
},
"outputs": [],
"source": [
"# Parameters that will be replaced when calling this notebook\n",
"# Do not leave parameters blank as notebook will not run otherwise\n",
"symbol = \"VTI\"\n",
"report_name = \"Equity Report for VTI\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1f87190",
"metadata": {},
"outputs": [],
"source": [
"ticker = symbol"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a441ddbc-d75b-4a18-84d4-e01b677674d3",
"metadata": {},
"outputs": [],
"source": [
"if \".\" in ticker:\n",
" import sys\n",
"\n",
" sys.exit(0)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1be26dae-cafe-4a22-80aa-eff296fc1a9b",
"metadata": {},
"outputs": [],
"source": [
"ticker_data = openbb.stocks.load(\n",
" symbol=ticker, start_date=datetime.datetime.now() - datetime.timedelta(days=4 * 30)\n",
")\n",
"ticker_data = openbb.stocks.process_candle(data=ticker_data)\n",
"\n",
"author = \"OpenBB\"\n",
"report_title = f\"ETF RESEARCH REPORT ON {ticker.upper()}\"\n",
"report_date = datetime.datetime.now().strftime(\"%d %B, %Y\")\n",
"report_time = datetime.datetime.now().strftime(\"%H:%M\")\n",
"report_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo\n",
"report_title, report_date, report_time, report_timezone"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ecfcf81",
"metadata": {},
"outputs": [],
"source": [
"# info = openbb.etf.summary(name=ticker)\n",
"# info"
]
},
{
"cell_type": "markdown",
"id": "f8a0d747",
"metadata": {},
"source": [
"## Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39b8e77d",
"metadata": {},
"outputs": [],
"source": [
"data = openbb.stocks.load(ticker)\n",
"\n",
"candle_chart = openbb.etf.candle(\n",
" symbol=ticker,\n",
" data=data,\n",
" external_axes=True,\n",
")\n",
"\n",
"if candle_chart:\n",
" candle_chart = candle_chart.to_html()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f751f57c",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"\n",
"sectors = openbb.etf.weights(ticker)\n",
"sector_weights_formatted = {}\n",
"for sector_weight in sectors:\n",
" sector_weights_formatted[sector_weight[\"sector\"]] = (\n",
" float(sector_weight[\"weightPercentage\"].strip(\"%\")) / 100\n",
" )\n",
"sector_weights_formatted = dict(sorted(sector_weights_formatted.items()))\n",
"weights_df = pd.DataFrame.from_dict(sector_weights_formatted, orient=\"index\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "324e2d4b-b2f6-4da3-bac4-ef785ad4e436",
"metadata": {},
"outputs": [],
"source": [
"weights_chart = \"\"\n",
"\n",
"if \"Percentage\" in weights_df.columns:\n",
" weights_df.columns = [\"Percentage\"]\n",
"\n",
"if \"Percentage\" in weights_df.columns and len(weights_df[weights_df[\"Percentage\"] > 10].index) > 1:\n",
" colors = theme.get_colors()\n",
" fig = OpenBBFigure.create_subplots(\n",
" 1,\n",
" 3,\n",
" specs=[[{\"type\": \"domain\"}, {\"type\": \"pie\", \"colspan\": 2}, None]],\n",
" row_heights=[1],\n",
" column_widths=[0.1, 0.8, 0.1],\n",
" )\n",
"\n",
" fig.add_pie(\n",
" labels=weights_df[weights_df[\"Percentage\"] > 10].index,\n",
" values=weights_df[weights_df[\"Percentage\"] > 10][\"Percentage\"],\n",
" textinfo=\"label+percent\",\n",
" hoverinfo=\"label+percent\",\n",
" automargin=True,\n",
" rotation=45,\n",
" row=1,\n",
" col=2,\n",
" )\n",
" fig.update_traces(\n",
" textposition=\"outside\",\n",
" textfont_size=15,\n",
" marker=dict(\n",
" colors=colors,\n",
" line=dict(color=\"#F5EFF3\", width=0.8),\n",
" ),\n",
" )\n",
"\n",
" fig.update_layout(\n",
" margin=dict(t=40, b=20),\n",
" title=dict(\n",
" text=f\"Sector holdings of {ticker}\",\n",
" y=0.98,\n",
" x=0.5,\n",
" xanchor=\"center\",\n",
" yanchor=\"top\",\n",
" ),\n",
" colorway=colors,\n",
" showlegend=False,\n",
" )\n",
"\n",
" weights_chart = fig.to_html()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8bb53c1-147e-4342-af61-0d0a9cc56e59",
"metadata": {},
"outputs": [],
"source": [
"holdings = openbb.etf.holdings(ticker)\n",
"holdings"
]
},
{
"cell_type": "markdown",
"id": "ac501a32-124d-4d21-aae1-a8d147bb1071",
"metadata": {},
"source": [
"## Render the report template to a file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "abfe37e4-ea38-42c3-8fb5-7cceb233fb12",
"metadata": {},
"outputs": [],
"source": [
"body = f\"\"\"\n",
"<style>\n",
"@font-face {{\n",
" font-family: \"Fira Code\";\n",
" src: url(\"{(OpenBBFigure.plotlyjs_path.parent.parent / \"web\"/\"css\").as_uri()}/fonts/FiraCode-Regular.ttf\") format(\"truetype\");\n",
" font-weight: 400 700;\n",
" font-stretch: 50%;\n",
"}}\n",
"</style>\n",
"<script src=\"/{OpenBBFigure.plotlyjs_path.as_uri()}\"></script>\n",
"\"\"\"\n",
"\n",
"img = (\n",
" str(REPOSITORY_DIRECTORY)\n",
" + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
")\n",
"floppy_disk_img = (\n",
" str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
")\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
" author,\n",
" report_date,\n",
" report_time,\n",
" report_timezone,\n",
" f\"<b>ETF RESEARCH REPORT:</b> {ticker.upper()}\",\n",
")\n",
"\n",
"body += widgets.tablinks([\"SUMMARY\"])\n",
"\n",
"htmlcode = widgets.h(3, \"Summary\")\n",
"# htmlcode += widgets.p(info)\n",
"htmlcode += widgets.row([candle_chart])\n",
"\n",
"htmlcode += widgets.p(\"The ETF has the following weights distribution:\")\n",
"\n",
"htmlcode += widgets.row([weights_df.to_html()])\n",
"htmlcode += widgets.row([weights_chart])\n",
"\n",
"htmlcode += widgets.p(\"And has funds in the following assets:\")\n",
"htmlcode += widgets.row([holdings.to_html()])\n",
"\n",
"body += widgets.add_tab(\"SUMMARY\", htmlcode)\n",
"\n",
"body += widgets.tab_clickable_and_save_evt()\n",
"\n",
"report = widgets.html_report(title=report_name, stylesheet=stylesheet, body=body)\n",
"\n",
"# to save the results\n",
"with open(report_name + \".html\", \"w\", encoding=\"utf-8\") as fh:\n",
" fh.write(report)"
]
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.13"
},
"vscode": {
"interpreter": {
"hash": "381a209f752e9104bff95ec5e80679ea2e476866b80e4326f1ee6b7658af6049"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,311 +1,321 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "70ba2c8a",
"metadata": {},
"source": [
"## Notebook setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "driven-billion",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import io\n",
"import warnings\n",
"import datetime\n",
"import numpy as np\n",
"import base64\n",
"\n",
"from IPython.display import HTML\n",
"\n",
"import openbb_terminal.config_terminal as cfg\n",
"from openbb_terminal.reports import widget_helpers as widgets\n",
"from openbb_terminal.sdk import openbb\n",
"from openbb_terminal.helper_classes import TerminalStyle\n",
"from openbb_terminal.core.config.paths import REPOSITORY_DIRECTORY\n",
"\n",
"from openbb_terminal import OpenBBFigure\n",
"\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3fe7db5-ec6a-42cf-9e66-52dc1de22370",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.core.session.current_user import get_current_user\n",
"\n",
"user = get_current_user()\n",
"\n",
"cfg.theme = TerminalStyle(\n",
" user.preferences.MPL_STYLE,\n",
" user.preferences.PMF_STYLE,\n",
" user.preferences.RICH_STYLE,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f6d22d4f",
"metadata": {},
"outputs": [],
"source": [
"stylesheet = widgets.html_report_stylesheet()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9c3ae3e3-2b91-43a6-aa44-7b10986fd369",
"metadata": {},
"outputs": [],
"source": [
"def get_html(func, title, *args, **kwargs):\n",
" kwargs[\"external_axes\"] = True\n",
" local_fig = func(*args, **kwargs)\n",
" # kwargs[\"chart\"] = True\n",
" if title:\n",
" local_fig.set_title(title)\n",
" local_fig.set_xaxis_title(\"Lag\")\n",
" local_fig.set_yaxis_title(\"Autocorrelation\")\n",
"\n",
" return local_fig.to_html()"
]
},
{
"cell_type": "markdown",
"id": "807fda4e",
"metadata": {},
"source": [
"## Select Ticker"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "intended-sherman",
"metadata": {
"tags": [
"parameters"
]
},
"outputs": [],
"source": [
"# Parameters that will be replaced when calling this notebook\n",
"# Do not leave parameters blank as notebook will not run otherwise\n",
"symbol = \"VTI\"\n",
"report_name = \"Forecast Report for VTI\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1f87190",
"metadata": {},
"outputs": [],
"source": [
"ticker = symbol"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a441ddbc-d75b-4a18-84d4-e01b677674d3",
"metadata": {},
"outputs": [],
"source": [
"if \".\" in ticker:\n",
" import sys\n",
"\n",
" sys.exit(0)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1be26dae-cafe-4a22-80aa-eff296fc1a9b",
"metadata": {},
"outputs": [],
"source": [
"ticker_data = openbb.stocks.load(\n",
" symbol=ticker, start_date=datetime.datetime.now() - datetime.timedelta(days=400)\n",
")\n",
"\n",
"author = \"OpenBB\"\n",
"report_title = f\"FORECAST RESEARCH REPORT ON {ticker.upper()}\"\n",
"report_date = datetime.datetime.now().strftime(\"%d %B, %Y\")\n",
"report_time = datetime.datetime.now().strftime(\"%H:%M\")\n",
"report_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo\n",
"report_title, report_date, report_time, report_timezone"
]
},
{
"cell_type": "markdown",
"id": "f8a0d747",
"metadata": {},
"source": [
"## Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb31e9ee-e7ae-4e1b-89b4-bb6f571cfdfa",
"metadata": {},
"outputs": [],
"source": [
"local_fig = openbb.stocks.candle(ticker, ticker_data, external_axes=True)\n",
"plot_chart = local_fig.to_html()\n",
"\n",
"# Prepare date for forecast\n",
"ticker_data = ticker_data.reset_index()\n",
"ticker_data.columns = [x.lower() for x in ticker_data.columns]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8bb53c1-147e-4342-af61-0d0a9cc56e59",
"metadata": {},
"outputs": [],
"source": [
"corr_chart = get_html(openbb.forecast.corr_chart, None, ticker_data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74b914a1-ed32-4a17-a943-7879bc8f00df",
"metadata": {},
"outputs": [],
"source": [
"season_chart = get_html(openbb.forecast.season_chart, \"Seasonality Data\", ticker_data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53568c9b-aa06-4ba0-ad86-ede6732e3187",
"metadata": {},
"outputs": [],
"source": [
"expo_chart = get_html(openbb.forecast.expo_chart, None, ticker_data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a919f67-ac9f-4b75-a3b3-102e0fbe5862",
"metadata": {},
"outputs": [],
"source": [
"rnn_chart = get_html(openbb.forecast.rnn_chart, None, ticker_data)"
]
},
{
"cell_type": "markdown",
"id": "ac501a32-124d-4d21-aae1-a8d147bb1071",
"metadata": {},
"source": [
"## Render the report template to a file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "abfe37e4-ea38-42c3-8fb5-7cceb233fb12",
"metadata": {},
"outputs": [],
"source": [
"body = f\"\"\"\n",
"<style>\n",
"@font-face {{\n",
" font-family: \"Fira Code\";\n",
" src: url(\"{(OpenBBFigure.plotlyjs_path.parent.parent / \"web\"/\"css\").as_uri()}/fonts/FiraCode-Regular.ttf\") format(\"truetype\");\n",
" font-weight: 400 700;\n",
" font-stretch: 50%;\n",
"}}\n",
"</style>\n",
"<script src=\"/{OpenBBFigure.plotlyjs_path.as_uri()}\"></script>\n",
"\"\"\"\n",
"\n",
"\n",
"img = (\n",
" str(REPOSITORY_DIRECTORY)\n",
" + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
")\n",
"floppy_disk_img = (\n",
" str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
")\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
" author,\n",
" report_date,\n",
" report_time,\n",
" report_timezone,\n",
" f\"<b>FORECAST REPORT ON:</b> {ticker.upper()}\",\n",
")\n",
"\n",
"body += widgets.tablinks([\"SUMMARY\"])\n",
"\n",
"htmlcode = widgets.h(3, \"Summary\")\n",
"htmlcode += widgets.row([widgets.h(3, \"Understand the data\")])\n",
"htmlcode += widgets.row([ticker_data.head(10).to_html()])\n",
"htmlcode += widgets.row([plot_chart])\n",
"htmlcode += widgets.row([widgets.h(3, \"Analyzing the data\")])\n",
"htmlcode += widgets.row([corr_chart])\n",
"htmlcode += widgets.row([season_chart])\n",
"htmlcode += widgets.row([widgets.h(3, \"Creating predictions\")])\n",
"htmlcode += widgets.row([expo_chart])\n",
"htmlcode += widgets.row([rnn_chart])\n",
"\n",
"body += widgets.add_tab(\"SUMMARY\", htmlcode)\n",
"body += widgets.tab_clickable_and_save_evt()\n",
"report = widgets.html_report(title=report_name, stylesheet=stylesheet, body=body)\n",
"\n",
"# to save the results\n",
"with open(report_name + \".html\", \"w\", encoding=\"utf-8\") as fh:\n",
" fh.write(report)"
]
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.8"
},
"vscode": {
"interpreter": {
"hash": "381a209f752e9104bff95ec5e80679ea2e476866b80e4326f1ee6b7658af6049"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}
"cells": [
{
"cell_type": "markdown",
"id": "70ba2c8a",
"metadata": {},
"source": [
"## Notebook setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "driven-billion",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import io\n",
"import warnings\n",
"import datetime\n",
"import numpy as np\n",
"import base64\n",
"\n",
"from IPython.display import HTML\n",
"\n",
"import openbb_terminal.config_terminal as cfg\n",
"from openbb_terminal.helper_classes import TerminalStyle\n",
"from openbb_terminal.core.config.paths import REPOSITORY_DIRECTORY\n",
"from openbb_terminal import OpenBBFigure\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "632bdfbc",
"metadata": {},
"outputs": [],
"source": [
"# Suppressing sdk logs\n",
"cfg.change_logging_suppress(new_value=True)\n",
"\n",
"# Import the OpenBB SDK\n",
"from openbb_terminal.sdk import openbb, widgets"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3fe7db5-ec6a-42cf-9e66-52dc1de22370",
"metadata": {},
"outputs": [],
"source": [
"from openbb_terminal.core.session.current_user import get_current_user\n",
"\n",
"user = get_current_user()\n",
"\n",
"cfg.theme = TerminalStyle(\n",
" user.preferences.MPL_STYLE,\n",
" user.preferences.PMF_STYLE,\n",
" user.preferences.RICH_STYLE,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f6d22d4f",
"metadata": {},
"outputs": [],
"source": [
"stylesheet = widgets.html_report_stylesheet()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9c3ae3e3-2b91-43a6-aa44-7b10986fd369",
"metadata": {},
"outputs": [],
"source": [
"def get_html(func, title, *args, **kwargs):\n",
" kwargs[\"external_axes\"] = True\n",
" local_fig = func(*args, **kwargs)\n",
" # kwargs[\"chart\"] = True\n",
" if title:\n",
" local_fig.set_title(title)\n",
" local_fig.set_xaxis_title(\"Lag\")\n",
" local_fig.set_yaxis_title(\"Autocorrelation\")\n",
"\n",
" return local_fig.to_html()"
]
},
{
"cell_type": "markdown",
"id": "807fda4e",
"metadata": {},
"source": [
"## Select Ticker"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "intended-sherman",
"metadata": {
"tags": [
"parameters"
]
},
"outputs": [],
"source": [
"# Parameters that will be replaced when calling this notebook\n",
"# Do not leave parameters blank as notebook will not run otherwise\n",
"symbol = \"VTI\"\n",
"report_name = \"Forecast Report for VTI\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1f87190",
"metadata": {},
"outputs": [],
"source": [
"ticker = symbol"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a441ddbc-d75b-4a18-84d4-e01b677674d3",
"metadata": {},
"outputs": [],
"source": [
"if \".\" in ticker:\n",
" import sys\n",
"\n",
" sys.exit(0)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1be26dae-cafe-4a22-80aa-eff296fc1a9b",
"metadata": {},
"outputs": [],
"source": [
"ticker_data = openbb.stocks.load(\n",
" symbol=ticker, start_date=datetime.datetime.now() - datetime.timedelta(days=400)\n",
")\n",
"\n",
"author = \"OpenBB\"\n",
"report_title = f\"FORECAST RESEARCH REPORT ON {ticker.upper()}\"\n",
"report_date = datetime.datetime.now().strftime(\"%d %B, %Y\")\n",
"report_time = datetime.datetime.now().strftime(\"%H:%M\")\n",
"report_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo\n",
"report_title, report_date, report_time, report_timezone"
]
},
{
"cell_type": "markdown",
"id": "f8a0d747",
"metadata": {},
"source": [
"## Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb31e9ee-e7ae-4e1b-89b4-bb6f571cfdfa",
"metadata": {},
"outputs": [],
"source": [
"local_fig = openbb.stocks.candle(ticker, ticker_data, external_axes=True)\n",
"plot_chart = local_fig.to_html()\n",
"\n",
"# Prepare date for forecast\n",
"ticker_data = ticker_data.reset_index()\n",
"ticker_data.columns = [x.lower() for x in ticker_data.columns]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8bb53c1-147e-4342-af61-0d0a9cc56e59",
"metadata": {},
"outputs": [],
"source": [
"corr_chart = get_html(openbb.forecast.corr_chart, None, ticker_data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74b914a1-ed32-4a17-a943-7879bc8f00df",
"metadata": {},
"outputs": [],
"source": [
"season_chart = get_html(openbb.forecast.season_chart, \"Seasonality Data\", ticker_data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53568c9b-aa06-4ba0-ad86-ede6732e3187",
"metadata": {},
"outputs": [],
"source": [
"expo_chart = get_html(openbb.forecast.expo_chart, None, ticker_data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a919f67-ac9f-4b75-a3b3-102e0fbe5862",
"metadata": {},
"outputs": [],
"source": [
"rnn_chart = get_html(openbb.forecast.rnn_chart, None, ticker_data)"
]
},
{
"cell_type": "markdown",
"id": "ac501a32-124d-4d21-aae1-a8d147bb1071",
"metadata": {},
"source": [
"## Render the report template to a file"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "abfe37e4-ea38-42c3-8fb5-7cceb233fb12",
"metadata": {},
"outputs": [],
"source": [
"body = f\"\"\"\n",
"<style>\n",
"@font-face {{\n",
" font-family: \"Fira Code\";\n",
" src: url(\"{(OpenBBFigure.plotlyjs_path.parent.parent / \"web\"/\"css\").as_uri()}/fonts/FiraCode-Regular.ttf\") format(\"truetype\");\n",
" font-weight: 400 700;\n",
" font-stretch: 50%;\n",
"}}\n",
"</style>\n",
"<script src=\"/{OpenBBFigure.plotlyjs_path.as_uri()}\"></script>\n",
"\"\"\"\n",
"\n",
"\n",
"img = (\n",
" str(REPOSITORY_DIRECTORY)\n",
" + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
")\n",
"floppy_disk_img = (\n",
" str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
")\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
" author,\n",
" report_date,\n",
" report_time,\n",
" report_timezone,\n",
" f\"<b>FORECAST REPORT ON:</b> {ticker.upper()}\",\n",
")\n",
"\n",
"body += widgets.tablinks([\"SUMMARY\"])\n",
"\n",
"htmlcode = widgets.h(3, \"Summary\")\n",
"htmlcode += widgets.row([widgets.h(3, \"Understand the data\")])\n",
"htmlcode += widgets.row([ticker_data.head(10).to_html()])\n",
"htmlcode += widgets.row([plot_chart])\n",
"htmlcode += widgets.row([widgets.h(3, \"Analyzing the data\")])\n",
"htmlcode += widgets.row([corr_chart])\n",
"htmlcode += widgets.row([season_chart])\n",
"htmlcode += widgets.row([widgets.h(3, \"Creating predictions\")])\n",
"htmlcode += widgets.row([expo_chart])\n",
"htmlcode += widgets.row([rnn_chart])\n",
"\n",
"body += widgets.add_tab(\"SUMMARY\", htmlcode)\n",
"body += widgets.tab_clickable_and_save_evt()\n",
"report = widgets.html_report(title=report_name, stylesheet=stylesheet, body=body)\n",
"\n",
"# to save the results\n",
"with open(report_name + \".html\", \"w\", encoding=\"utf-8\") as fh:\n",
" fh.write(report)"
]
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.8"
},
"vscode": {
"interpreter": {
"hash": "381a209f752e9104bff95ec5e80679ea2e476866b80e4326f1ee6b7658af6049"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff