Refactor reports menu and make it work on installer (#2878)

* start papermill rollback

* move reports to templates folder

* comment some stuff

* refactor reports controller

* change var names

* clean ctrl class vars

* small refactoring on metadata search

* relocate some func

* breaking changes

* revert some changes

* destroying changes

* change menu appearance

* change menu appeaerance again

* update choices

* change some assets locations

* remove unused vars

* change some docstrings

* rename vars

* its working

* forgot one dash

* omg its working

* clean some code and doc

* change func name

* fix a bug and add some comments

* fix economy report startup

* add custom reports

* allow custom reports

* forgot to delete this

* unused import

* make somestuff more explicit, 5 min rule

* add helper func

* supress errors--no-verify

* add some type checks

* fix check ipynb func

* check inputs in run

* rewrite some integration tests

* remove unused import

* add some docs to website

* add auto completion

* warn if no forecast

* add to do

* add arg name if 1 arg

* add papermill pointing to github main

* add notebooks

* fix terminal spec

* relocated choices files

* merge main

* relocate website docs for reports

* limit user choices

* fix portfolio auto completion

* limit choices fix

* clean som edocstrings

* limit user choices in run command

* improve run parser

* fix msg

* add reports controller tests

* relax crypto choices limit

* contain commands

* pass pre commit

* flake8

* flake8

* ignore mypy

* pylint

* website

* move docs to jupyter tab

* add installer dep

* remove etf

* remove reports

* update reports to main

* handle events erros

* rever reports to previous commits before main merge

* add papermill installer strategies

* add flags to terminal

* avoid sdk crash

* execute notebook in thread

* remove unused sys

* execute report in a child process

* fix some notebook stuff

* fix portfolio report

* fix reports controller volatility

* change api to sdk

* move images

* remove som unucessary installer imports

* remove debugger warning

* move ipykernel launcher

* supress notebook crash

* remove portfolio decorator

* fix run command

* forgot to change this

* add images

* fix portfolio report

* fix crypto report

* linting and tests

* add run integration test

* pylint

* hide reports module args

* try at supressing args

* try at supreccing warning

* solve some os issues

* handle pm errors

* fix warning unrecognized arguments

* change comment

* avoid crash when parameters not found

* fix etf commands

* minor cleanup

* update forecasting check

* fix some linting

* fix futures test

* pylint

* limit user choices to prompt

* limit user choices to prompt

* supress warnings

* revert poetry lock back to main

* papermill pointing to main

* flake8

* forgot requirements

* Update helper_funcs.py

* Update reports_model.py

* choices

Co-authored-by: hjoaquim <h.joaquim@campus.fct.unl.pt>
Co-authored-by: James Simmons <53658028+simmonsj330@users.noreply.github.com>
This commit is contained in:
montezdesousa
2022-10-26 21:27:30 +01:00
committed by GitHub
parent b5e807b188
commit a9787dd2d1
50 changed files with 6274 additions and 3936 deletions

2
.gitignore vendored
View File

@@ -28,7 +28,7 @@ openbb_terminal/portfolio/portfolios/*
*.swp
*.http
.coverage.*
!openbb_terminal/custom_reports/*/.ipynb
!openbb_terminal/reports/templates/*.ipynb
# pyinstaller artifacts
*.pyo

View File

@@ -0,0 +1,8 @@
import os
# This environment variable suppresses the warning:
# 0.00s - Debugger warning: The os.path.realpath.__code__.co_filename (posixpath.py)
# 0.00s - is not absolute, which may make the debugger miss breakpoints.
# 0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
os.environ["PYDEVD_DISABLE_FILE_VALIDATION"] = "1"

View File

@@ -62,6 +62,7 @@ added_files = [
"pymongo",
),
(os.path.join(pathex, "bson"), "bson"),
(os.path.join(pathex, "debugpy", "_vendored"), os.path.join("debugpy", "_vendored")),
(".env", "."),
]
@@ -89,6 +90,7 @@ hidden_imports = [
"bson",
"_sysconfigdata__darwin_darwin",
"prophet",
"debugpy",
]
@@ -100,7 +102,7 @@ analysis_kwargs = dict(
hiddenimports=hidden_imports,
hookspath=["build/pyinstaller/hooks"],
hooksconfig={},
runtime_hooks=[],
runtime_hooks=["build/pyinstaller/hooks/hook-debugpy.py"],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,

View File

@@ -32,3 +32,5 @@ USER_PORTFOLIO_DATA_DIRECTORY = USER_DATA_DIRECTORY / "portfolio"
USER_ROUTINES_DIRECTORY = USER_DATA_DIRECTORY / "routines"
USER_DATA_SOURCES_DEFAULT_FILE = USER_DATA_DIRECTORY / "data_sources_default.json"
USER_PRESETS_DIRECTORY = USER_DATA_DIRECTORY / "presets"
USER_REPORTS_DIRECTORY = USER_DATA_DIRECTORY / "reports"
USER_CUSTOM_REPORTS_DIRECTORY = USER_DATA_DIRECTORY / "reports" / "custom reports"

View File

@@ -43,12 +43,13 @@ dirs_list = [
SETTINGS_DIRECTORY,
USER_DATA_DIRECTORY,
USER_DATA_DIRECTORY / "styles",
USER_DATA_DIRECTORY / "reports",
USER_DATA_DIRECTORY / "reports" / "custom reports",
USER_CUSTOM_IMPORTS_DIRECTORY,
USER_CUSTOM_IMPORTS_DIRECTORY / "econometrics",
USER_CUSTOM_IMPORTS_DIRECTORY / "stocks",
USER_CUSTOM_IMPORTS_DIRECTORY / "dashboards",
USER_EXPORTS_DIRECTORY,
USER_EXPORTS_DIRECTORY / "reports",
USER_PORTFOLIO_DATA_DIRECTORY,
USER_PORTFOLIO_DATA_DIRECTORY / "views",
USER_PORTFOLIO_DATA_DIRECTORY / "holdings",

View File

@@ -153,7 +153,7 @@ def parse_and_split_input(an_input: str, custom_filters: List) -> List[str]:
# everything from ` -f ` to the next known extension
file_flag = r"(\ -f |\ --file )"
up_to = r".*?"
known_extensions = r"(\.xlsx|.csv|.xls|.tsv|.json|.yaml|.ini|.openbb)"
known_extensions = r"(\.xlsx|.csv|.xls|.tsv|.json|.yaml|.ini|.openbb|.ipynb)"
unix_path_arg_exp = f"({file_flag}{up_to}{known_extensions})"
# Add custom expressions to handle edge cases of individual controllers

View File

@@ -1,3 +1,4 @@
import sys
from typing import Optional
import logging
import os
@@ -19,6 +20,23 @@ def is_jupyter() -> bool:
return False
def is_papermill() -> bool:
"""Check if session is being launched with args '-m ipykernel_launcher',
thus coming from papermill Popen. See 'ipykernel_launcher' in reports_model
for more detail.
"""
return all(
i in sys.argv
for i in [
"-m",
"ipykernel_launcher",
"-f",
"--HistoryManager.hist_file=:memory:",
]
)
def inputhook(inputhook_context):
while not inputhook_context.input_is_ready():
try:
@@ -32,7 +50,7 @@ def inputhook(inputhook_context):
history_file = os.path.join(os.path.expanduser("~"), ".openbb_terminal.his")
try:
if not is_jupyter():
if not is_jupyter() and not is_papermill():
session: Optional[PromptSession] = PromptSession(
history=FileHistory(history_file)
)

View File

@@ -80,15 +80,14 @@ en:
forex: foreign exchanges, quotes, forward rates for currency pairs and oanda integration
funds: mutual funds search, overview, holdings and sector weights
futures: commodities, bonds, index, bitcoin and forex
futures/search: search for a future based on category or exchange
futures/historical: get historical timeseries
futures/curve: get futures curves
alternative: alternative datasets, such as COVID and open source metrics
econometrics: statistical and quantitative methods for relationships between datasets
forecast: timeseries forecasting with machine learning
portfolio: perform portfolio optimization and look at portfolio performance and attribution
dashboards: interactive dashboards using voila and jupyter notebooks
reports: customizable research reports through jupyter notebooks
futures/search: search for a future based on category or exchange
futures/historical: get historical timeseries
futures/curve: get futures curves
stocks/search: search a specific stock ticker for analysis
stocks/load: load a specific stock ticker and additional info for analysis
stocks/_ticker: Stock
@@ -1018,7 +1017,11 @@ en:
dashboards/shortdata: finra shortdata analysis
dashboards/crypto: cryptocurrency exchange rates against USD
dashboards/futures: historical futures performance
reports/_reports_: Select one of the following report
reports: customizable research reports through jupyter notebooks
reports/_reports_: Build and run your custom reports or try one of our templates
reports/_OpenBB_reports_: OpenBB reports
reports/_Custom_reports_: Custom reports
reports/run: Run a Jupyter notebook from OpenBBUserData/reports/custom reports
forecast/_disclaimer_: DISCLAIMER
forecast/_data_loc: Looking for data in
forecast/load: load a dataset from csv

View File

@@ -1,12 +1,15 @@
reports
## defaults
crypto
## simple coin
crypto BTC
crypto --symbol BTC
## small-cap coin
crypto STEPN
crypto --symbol STEPN
## shitcoins
crypto LUCKY
crypto --symbol LUCKY
exit

View File

@@ -2,7 +2,8 @@ reports
economy
# Test with random tickers
# Test with random tickers, that should be ignored by the report.
economy AAPL
economy --symbol MSFT
exit

View File

@@ -1,12 +1,15 @@
reports
## defaults
equity
## simple ticker
equity AAPL
equity --symbol AAPL
## Non-US ticker
equity NXPI
equity --symbol NXPI
## Non-US ticker with suffix
equity PETR3.SA
equity --symbol PETR3.SA
exit

View File

@@ -1,10 +1,13 @@
reports
etf VEA
## defaults
etf
etf VWCE
etf --symbol VEA
etf --symbol VWCE
# test with random pairs
etf ABCEF
etf --symbol ABCEF
exit

View File

@@ -1,11 +1,14 @@
reports
forex USDEUR
## defaults
forex
forex --symbol USDEUR
# Test with emerging pairs
forex USDVND
forex --symbol USDVND
# Test with invalid pairs
forex USDABCXYZ
forex --symbol USDABCXYZ
exit

View File

@@ -0,0 +1,10 @@
reports
## defaults
run
## Invalid inputs
run --file dummyfile.csv
run --file dummyfile.ipynb
exit

View File

@@ -223,7 +223,7 @@ def get_info_from_ticker(ticker: str) -> list:
filename = "tickers_info.csv"
file_path = Path(str(DEFAULT_HOLDINGS_PATH), filename)
file_path = Path(str(USER_PORTFOLIO_DATA_DIRECTORY), filename)
if file_path.is_file() and os.stat(file_path).st_size > 0:
# file exists and is not empty, so append if necessary

View File

@@ -56,7 +56,6 @@ class PortfolioModel:
"""
@log_start_end(log=logger)
def __init__(self, orderbook: pd.DataFrame = pd.DataFrame()):
"""Initialize PortfolioModel class"""
@@ -129,7 +128,6 @@ class PortfolioModel:
return df
@staticmethod
@log_start_end(log=logger)
def read_orderbook(path: str) -> pd.DataFrame:
"""Static method to read orderbook from file
@@ -148,23 +146,24 @@ class PortfolioModel:
@log_start_end(log=logger)
def preprocess_orderbook(self):
"""Method to preprocess, format and compute auxiliary fields"""
"""Method to preprocess, format and compute auxiliary fields
# Preprocessing steps:
# 0. If optional fields not in the orderbook add missing
# 1. Convert Date to datetime
# 2. Sort orderbook by date
# 3. Capitalize Ticker and Type [of instrument...]
# 4. Translate side: ["deposit", "buy"] -> 1 and ["withdrawal", "sell"] -> -1
# 5. Convert quantity to signed integer
# 6. Determining the investment/divestment value
# 7. Reformat crypto tickers to yfinance format (e.g. BTC -> BTC-USD)
# 8. Reformat STOCK/ETF tickers to yfinance format if ISIN provided
# 9. Remove unsupported ISINs that came out empty
# 10. Create tickers dictionary with structure {'Type': [Ticker]}
# 11. Create list with tickers except cash
# 12. Save orderbook inception date
# 13. Populate fields Sector, Industry and Country
Preprocessing steps:
0. If optional fields not in the orderbook add missing
1. Convert Date to datetime
2. Sort orderbook by date
3. Capitalize Ticker and Type [of instrument...]
4. Translate side: ["deposit", "buy"] -> 1 and ["withdrawal", "sell"] -> -1
5. Convert quantity to signed integer
6. Determining the investment/divestment value
7. Reformat crypto tickers to yfinance format (e.g. BTC -> BTC-USD)
8. Reformat STOCK/ETF tickers to yfinance format if ISIN provided
9. Remove unsupported ISINs that came out empty
10. Create tickers dictionary with structure {'Type': [Ticker]}
11. Create list with tickers except cash
12. Save orderbook inception date
13. Populate fields Sector, Industry and Country
"""
try:
console.print(" Preprocessing orderbook: ", end="")
@@ -412,7 +411,6 @@ class PortfolioModel:
full_shares: bool
whether to mimic the portfolio trades exactly (partial shares) or round down the
quantity to the nearest number.
"""
console.print("\n Loading benchmark: ", end="")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
Symbol
BTC
ETH
BNB
ADA
DOT
XRP
UNI
LTC
LINK
THETA
USDC
BCH
XLM
LUNA
WBTC
DOGE
VET
CRO
FIL
AAVE
TRX
MIOTA
ATOM
SOL
EOS
AVAX
XMR
BSV
KSM
FTT
XTZ
XEM
BTT
ALGO
NEO
CHZ
DAI
HT
HBAR
EGLD
SUSHI
SNX
DASH
TFUEL
LEO
GRT
DCR
MKR
ZIL
COMP
MATIC
NEAR
ENJ
RVN
BAT
NPXS
ZEC
CAKE
ONE
MANA
NEXO
UMA
ETC
BNT
CHSB
RUNE
YFI
HOT
RSR
ICX
CEL
ZRX
WAVES
FTM
REN
SC
REV
VGX
ONT
DGB
DENT
OKB
IOST
OMG
PAX
CELO
1INCH
NANO
NANO
QTUM
LRC
OCEAN
CRV
KNC
ZEN
MVL
BTG
LSK
XVG
1 Symbol
2 BTC
3 ETH
4 BNB
5 ADA
6 DOT
7 XRP
8 UNI
9 LTC
10 LINK
11 THETA
12 USDC
13 BCH
14 XLM
15 LUNA
16 WBTC
17 DOGE
18 VET
19 CRO
20 FIL
21 AAVE
22 TRX
23 MIOTA
24 ATOM
25 SOL
26 EOS
27 AVAX
28 XMR
29 BSV
30 KSM
31 FTT
32 XTZ
33 XEM
34 BTT
35 ALGO
36 NEO
37 CHZ
38 DAI
39 HT
40 HBAR
41 EGLD
42 SUSHI
43 SNX
44 DASH
45 TFUEL
46 LEO
47 GRT
48 DCR
49 MKR
50 ZIL
51 COMP
52 MATIC
53 NEAR
54 ENJ
55 RVN
56 BAT
57 NPXS
58 ZEC
59 CAKE
60 ONE
61 MANA
62 NEXO
63 UMA
64 ETC
65 BNT
66 CHSB
67 RUNE
68 YFI
69 HOT
70 RSR
71 ICX
72 CEL
73 ZRX
74 WAVES
75 FTM
76 REN
77 SC
78 REV
79 VGX
80 ONT
81 DGB
82 DENT
83 OKB
84 IOST
85 OMG
86 PAX
87 CELO
88 1INCH
89 NANO
90 NANO
91 QTUM
92 LRC
93 OCEAN
94 CRV
95 KNC
96 ZEN
97 MVL
98 BTG
99 LSK
100 XVG

View File

@@ -0,0 +1,101 @@
Symbol
SPY
IVV
VOO
VTI
QQQ
VTV
VEA
BND
AGG
IEFA
VUG
VWO
IJR
VIG
IJH
IEMG
IWF
GLD
IWM
IWD
VO
BNDX
VYM
VXUS
VCSH
EFA
VB
BSV
XLE
ITOT
VGT
VCIT
XLV
SCHD
XLK
LQD
VNQ
MUB
SHY
VEU
RSP
SCHX
USMV
XLF
IVW
TIP
BIL
DIA
IAU
IWB
IWR
GOVT
TLT
IXUS
SHV
SCHF
VV
JPST
IVE
VBR
IEF
DGRO
IGSB
VT
SDY
EEM
ESGU
DVY
VTEB
MBB
SCHB
VTIP
IUSB
VGSH
MDY
QUAL
VHT
XLU
XLP
ACWI
VOE
SCHP
DFAC
VMBS
XLY
PFF
EMB
SPLG
SCHG
JEPI
EFV
SCHA
VXF
HYG
BIV
VGK
STIP
IWS
SPYG
VGIT
1 Symbol
2 SPY
3 IVV
4 VOO
5 VTI
6 QQQ
7 VTV
8 VEA
9 BND
10 AGG
11 IEFA
12 VUG
13 VWO
14 IJR
15 VIG
16 IJH
17 IEMG
18 IWF
19 GLD
20 IWM
21 IWD
22 VO
23 BNDX
24 VYM
25 VXUS
26 VCSH
27 EFA
28 VB
29 BSV
30 XLE
31 ITOT
32 VGT
33 VCIT
34 XLV
35 SCHD
36 XLK
37 LQD
38 VNQ
39 MUB
40 SHY
41 VEU
42 RSP
43 SCHX
44 USMV
45 XLF
46 IVW
47 TIP
48 BIL
49 DIA
50 IAU
51 IWB
52 IWR
53 GOVT
54 TLT
55 IXUS
56 SHV
57 SCHF
58 VV
59 JPST
60 IVE
61 VBR
62 IEF
63 DGRO
64 IGSB
65 VT
66 SDY
67 EEM
68 ESGU
69 DVY
70 VTEB
71 MBB
72 SCHB
73 VTIP
74 IUSB
75 VGSH
76 MDY
77 QUAL
78 VHT
79 XLU
80 XLP
81 ACWI
82 VOE
83 SCHP
84 DFAC
85 VMBS
86 XLY
87 PFF
88 EMB
89 SPLG
90 SCHG
91 JEPI
92 EFV
93 SCHA
94 VXF
95 HYG
96 BIV
97 VGK
98 STIP
99 IWS
100 SPYG
101 VGIT

View File

@@ -0,0 +1,52 @@
Symbol
AAPL
ABBV
ABT
ACN
AMZN
ASML
AVGO
AZN
BABA
BAC
BMY
BRK-A
COP
COST
CRM
CSCO
CVX
DHR
DIS
GOOGL
HD
JNJ
JPM
KO
LLY
MA
MCD
META
MRK
MSFT
NEE
NVDA
NVO
NVS
ORCL
PEP
PFE
PG
TM
TMO
TMUS
TSLA
TSM
UNH
UPS
V
VZ
WFC
WMT
XOM
Summ
1 Symbol
2 AAPL
3 ABBV
4 ABT
5 ACN
6 AMZN
7 ASML
8 AVGO
9 AZN
10 BABA
11 BAC
12 BMY
13 BRK-A
14 COP
15 COST
16 CRM
17 CSCO
18 CVX
19 DHR
20 DIS
21 GOOGL
22 HD
23 JNJ
24 JPM
25 KO
26 LLY
27 MA
28 MCD
29 META
30 MRK
31 MSFT
32 NEE
33 NVDA
34 NVO
35 NVS
36 ORCL
37 PEP
38 PFE
39 PG
40 TM
41 TMO
42 TMUS
43 TSLA
44 TSM
45 UNH
46 UPS
47 V
48 VZ
49 WFC
50 WMT
51 XOM
52 Summ

View File

@@ -1,28 +1,19 @@
"""Reports Controller Module."""
__docformat__ = "numpy"
import argparse
import logging
import os
# pylint: disable=R1732, R0912
import os
from pathlib import Path
import re
import webbrowser
from ast import literal_eval
from datetime import datetime
from typing import List
import importlib
import contextlib
import io
from nbconvert.nbconvertapp import NbConvertApp
from nbconvert.exporters import ScriptExporter
from nbconvert.writers import FilesWriter
from matplotlib import pyplot as plt
from typing import Any, Dict, List
from openbb_terminal import feature_flags as obbff
from openbb_terminal.core.config.paths import USER_EXPORTS_DIRECTORY
from openbb_terminal.core.config.paths import (
USER_CUSTOM_REPORTS_DIRECTORY,
)
from openbb_terminal.helper_funcs import parse_simple_args
from openbb_terminal.reports import reports_model
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
from openbb_terminal.decorators import log_start_end
from openbb_terminal.menu import session
@@ -35,283 +26,239 @@ logger = logging.getLogger(__name__)
class ReportController(BaseController):
"""Report Controller class."""
reports_folder = Path(__file__).parent
report_names = [
notebooks[:-6]
for notebooks in os.listdir(reports_folder)
if notebooks.endswith(".ipynb")
CHOICES_COMMANDS: List[str] = [
"crypto",
"economy",
"equity",
"etf",
"forecast",
"forex",
"portfolio",
"run",
"load",
]
ids_reports = [str(val + 1) for val in range(len(report_names))]
d_id_to_report_name = {}
for id_report, report_name in enumerate(report_names):
d_id_to_report_name[str(id_report + 1)] = report_name
d_params = {}
max_len_name = max(len(name) for name in report_names) + 2
reports_opts = ""
for k, report_to_run in d_id_to_report_name.items():
# Crawl data to look into what
notebook_file = Path(__file__).parent / report_to_run
# Open notebook with report template
with open(str(notebook_file) + ".ipynb") as n_file:
notebook_content = n_file.read()
# 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":"""
# Locate position of the data of interest and get parameters
notebook_metadata_content = notebook_content[
notebook_content.find(metadata_cell) : # noqa: E203
]
cell_start = 'source": '
cell_end = "]"
params = notebook_metadata_content[
notebook_metadata_content.find(
cell_start
) : notebook_metadata_content.find( # noqa: E203
cell_end, notebook_metadata_content.find(cell_start)
)
+ 1
]
# Make sure that the parameters provided are relevant
if "parameters" in notebook_content:
l_params = [
param.split("=")[0][:-1]
for param in literal_eval(params.strip('source": '))
if param[0] not in ["#", "\n"]
]
def_params = [
param.split("=")[1][2:-1]
for param in literal_eval(params.strip('source": '))
if param[0] not in ["#", "\n"]
]
# to ensure default value is correctly selected
for param in range(len(def_params) - 1):
def_params[param] = def_params[param][:-1]
if "report_name" in l_params:
l_params.remove("report_name")
d_params[report_to_run] = [l_params, def_params]
# On the menu of choices add the parameters necessary for each template report
if len(l_params) > 1 or not l_params:
args = f"<{'> <'.join(l_params)}>"
else:
args = f"<{l_params[0]}>"
reports_opts += (
f" {k}. {report_to_run}"
+ f"{(max_len_name-len(report_to_run))*' '} "
+ f"{args if args != '<>' else ''}\n"
)
CHOICES_MENUS = report_names + ids_reports + ["r", "reset"]
PATH = "/reports/"
REPORTS: List[str] = reports_model.get_reports_available()
REPORTS = sorted(list(set(CHOICES_COMMANDS).intersection(REPORTS)))
PARAMETERS_DICT: Dict[str, Any] = {}
def __init__(self, queue: List[str] = None):
"""Constructor"""
super().__init__(queue)
self.update_choices()
def update_choices(self):
"""Update controller choices with reports available under templates folder."""
for report_name in self.REPORTS:
# Extract report parameters do display on menu
self.PARAMETERS_DICT[report_name] = reports_model.extract_parameters(
str(reports_model.REPORTS_FOLDER / report_name)
)
if session and obbff.USE_PROMPT_TOOLKIT:
choices: dict = {c: {} for c in self.controller_choices}
choices["support"] = self.SUPPORT_CHOICES
choices["about"] = self.ABOUT_CHOICES
self.choices: dict = {c: {} for c in self.controller_choices}
self.choices["run"] = {
"--file": {c: None for c in reports_model.USER_REPORTS},
"-f": "--file",
"--parameters": {},
"-p": "--parameters",
}
for report_name in self.REPORTS:
self.completer = NestedCompleter.from_nested_dict(choices)
# Completer with limited user choices to avoid unforeseen problems
self.choices[report_name] = {}
for arg in self.PARAMETERS_DICT[report_name]:
if report_name in reports_model.REPORT_CHOICES:
self.choices[report_name][
"--" + arg
] = reports_model.REPORT_CHOICES[report_name]["--" + arg]
self.choices["support"] = self.SUPPORT_CHOICES
self.choices["about"] = self.ABOUT_CHOICES
self.completer = NestedCompleter.from_nested_dict(self.choices)
def print_help(self):
"""Print help."""
self.REPORTS = reports_model.get_reports_available()
self.REPORTS = sorted(
list(set(self.CHOICES_COMMANDS).intersection(self.REPORTS))
)
self.update_choices()
mt = MenuText("reports/")
mt.add_info("_reports_")
mt.add_raw(f"[cmds]{self.reports_opts}[/cmds]")
mt.add_raw("\n")
mt.add_info("_OpenBB_reports_")
MAX_LEN_NAME = max(len(name) for name in self.REPORTS) + 2
templates_string = ""
for report_name in self.REPORTS:
parameters_names = list(self.PARAMETERS_DICT[report_name].keys())
if len(parameters_names) > 1 or not parameters_names:
args = f"<{'> <'.join(parameters_names)}>"
else:
args = f"<{parameters_names[0]}>"
templates_string += (
f" [cmds]{report_name}[/cmds]"
+ f"{(MAX_LEN_NAME-len(report_name))*' '} "
+ f"[param]{args if args != '<>' else ''}[/param]\n"
)
mt.add_raw(f"{templates_string}")
mt.add_raw("\n")
mt.add_info("_Custom_reports_")
mt.add_cmd("run")
console.print(text=mt.menu_text, menu="Reports - WORK IN PROGRESS")
@log_start_end(log=logger)
def switch(self, an_input: str):
"""Process and dispatch input.
def call_etf(self, other_args: List[str]):
self.run_report("etf", other_args)
Parameters
----------
an_input : str
string with input arguments
@log_start_end(log=logger)
def call_forex(self, other_args: List[str]):
self.run_report("forex", other_args)
Returns
-------
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
console.print("")
return self.queue
@log_start_end(log=logger)
def call_portfolio(self, other_args: List[str]):
self.run_report("portfolio", other_args)
# Navigation slash is being used
if "/" in an_input:
actions = an_input.split("/")
@log_start_end(log=logger)
def call_economy(self, other_args: List[str]):
self.run_report("economy", other_args)
# Absolute path is specified
if not actions[0]:
an_input = "home"
# Relative path so execute first instruction
else:
an_input = actions[0]
@log_start_end(log=logger)
def call_equity(self, other_args: List[str]):
self.run_report("equity", other_args)
# Add all instructions to the queue
for cmd in actions[1:][::-1]:
if cmd:
self.queue.insert(0, cmd)
@log_start_end(log=logger)
def call_crypto(self, other_args: List[str]):
self.run_report("crypto", other_args)
(known_args, other_args) = self.parser.parse_known_args(an_input.split())
# Redirect commands to their correct functions
if known_args.cmd:
if known_args.cmd in ("..", "q"):
known_args.cmd = "quit"
elif known_args.cmd in ("?", "h"):
known_args.cmd = "help"
elif known_args.cmd == "r":
known_args.cmd = "reset"
if known_args.cmd in ["quit", "help", "reset", "home", "exit", "cls"]:
getattr(
self,
"call_" + known_args.cmd,
lambda _: "Command not recognized!",
)(other_args)
return self.queue
# Execute the requested report
if known_args.cmd in self.d_id_to_report_name:
report_to_run = self.d_id_to_report_name[known_args.cmd]
else:
report_to_run = known_args.cmd
params = self.d_params[report_to_run][0]
# Check that the number of arguments match. We can't check validity of the
# argument used because this depends on what the user will use it for in
# the notebook. This is a downside of allowing the user to have this much
# flexibility.
if len(other_args) != len(params):
console.print("Wrong number of arguments provided!")
if len(params):
console.print("Provide, in order:")
for k, v in enumerate(params):
console.print(f"{k+1}. {v}")
else:
console.print("No argument required.")
console.print("")
return []
args_to_output = f"_{'_'.join(other_args)}" if "_".join(other_args) else ""
report_output_name = (
f"{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+ "_"
+ f"{report_to_run}{args_to_output}"
)
notebook_output = str(
USER_EXPORTS_DIRECTORY / "reports" / report_output_name
@log_start_end(log=logger)
def call_forecast(self, other_args: List[str]):
try:
import darts # pyright: reportMissingImports=false # noqa: F401, E501 #pylint: disable=import-outside-toplevel, unused-import
from darts import ( # pyright: reportMissingImports=false # noqa: F401, E501 #pylint: disable=import-outside-toplevel, unused-import
utils,
)
# only a single backslash appears in the .py file otherwise
notebook_output = notebook_output.replace("\\", "\\\\")
notebook_output = notebook_output.replace("\\", "\\\\")
forecast = True
except ImportError:
forecast = False
console.print(
"\n'forecast' menu dependencies are not installed."
" This part of the SDK will not be usable.\n\n"
"For more information see the official documentation at: "
"[blue]https://openbb-finance.github.io/OpenBBTerminal/sdk/[/blue]\n"
)
if forecast:
self.run_report("forecast", other_args)
# gather params from user
d_report_params = {}
for idx, param in enumerate(params):
d_report_params[param] = other_args[idx]
@log_start_end(log=logger)
def run_report(self, report_name: str, other_args: List[str]):
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog=report_name,
description=f"Run {report_name} report.",
)
d_report_params["report_name"] = notebook_output
if report_name in self.PARAMETERS_DICT:
notebook_file = str(Path(__file__).parent / report_to_run)
# Assign respective parameters as arguments
for arg_name, arg_default in self.PARAMETERS_DICT[report_name].items():
with open(notebook_file + ".ipynb") as n_file:
notebook_content = n_file.read()
choices = reports_model.get_arg_choices(report_name, arg_name)
params.append("report_name") # re-add report_name
# replace params in notebook
if "parameters" in notebook_content:
params_to_change = self.d_params[report_to_run][1]
for idx, param in enumerate(params_to_change):
notebook_content = notebook_content.replace(
param, d_report_params[params[idx]], 1
)
notebook_file_copy = notebook_file + "copy"
with open(notebook_file_copy + ".ipynb", "w") as file:
file.write(notebook_content)
# use nbconvert to switch to .py
converter = NbConvertApp()
converter.log_level = 40
converter.export_format = "script"
converter.exporter = ScriptExporter()
converter.writer = FilesWriter()
converter.convert_single_notebook(notebook_file_copy + ".ipynb")
python_file = Path(notebook_file_copy + ".py")
python_text = python_file.read_text().split("\n")
# remove lines with get_ipython
lines = [
line
for line in python_text
if len(line) == 0 or "get_ipython()" not in line
]
clean_code = "\n".join(lines)
clean_code = re.sub(r"\n{2,}", "\n\n", clean_code)
python_file.write_text(clean_code.strip())
# Check for existing matplotlib figures
_figures_before = plt.get_fignums()
# Run the report
try:
notebook_execution_output = io.StringIO()
with contextlib.redirect_stdout(notebook_execution_output):
importlib.import_module(
f"openbb_terminal.reports.{report_to_run}copy"
)
except Exception as error:
console.print("[red]\nReport wasn't executed correctly.\n[/red]")
console.print(f"[red]\nError : {error}\n[/red]")
# Close the figures that were created during report execution
_figures_after = plt.get_fignums()
for fig_number in _figures_after:
if fig_number not in _figures_before:
plt.close(fig_number)
notebook_output = notebook_output.replace("\\\\", "\\")
notebook_output = notebook_output.replace("\\\\", "\\")
report_output_path = notebook_output + ".html"
params.remove("report_name") # remove report_name
os.remove(notebook_file_copy + ".ipynb")
os.remove(notebook_file_copy + ".py")
if Path(report_output_path).is_file():
if obbff.OPEN_REPORT_AS_HTML:
console.print(report_output_path)
webbrowser.open(f"file://{report_output_path}")
console.print("")
console.print(
f"Exported: {report_output_path}",
"\n",
getattr(parser, "add_argument")(
"--" + arg_name,
type=str,
default=arg_default,
choices=choices,
dest=arg_name,
help=arg_name,
)
else:
console.print("[red]\nReport couldn't be created.\n[/red]")
return self.queue
if (
other_args
and "-" not in other_args[0][0]
and len(self.PARAMETERS_DICT[report_name]) == 1
):
other_args.insert(
0, "--" + list(self.PARAMETERS_DICT[report_name].keys())[0]
)
ns_parser = parse_simple_args(parser, other_args)
if ns_parser:
parameters = vars(ns_parser)
parameters.pop("help")
reports_model.render_report(
input_path=str(reports_model.REPORTS_FOLDER / report_name),
args_dict=parameters,
)
else:
console.print("[red]Notebook not found![/red]\n")
@log_start_end(log=logger)
def call_run(self, other_args: List[str]):
"""Process run command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="run",
description=f"Run a notebook from this folder: '{str(USER_CUSTOM_REPORTS_DIRECTORY)}'.",
)
parser.add_argument(
"-f",
"--file",
dest="file",
choices=reports_model.USER_REPORTS,
required="-h" not in other_args,
help="The file to be loaded",
)
parser.add_argument(
"-p",
"--parameters",
nargs="+",
dest="parameters",
help="Report parameters with format 'name:value'.",
)
ns_parser = self.parse_known_args_and_warn(parser, other_args)
if ns_parser:
# Validate parameter inputs
parameters_dict = {}
if ns_parser.parameters:
for p in ns_parser.parameters:
if ":" in p:
item = p.split(":")
if item[1]:
parameters_dict[item[0]] = item[1]
else:
console.print(
f"[red]Bad format '{p}': empty value.[/red]\nExecuting with defaults.\n"
)
else:
console.print(
f"[red]Bad format '{p}': use format 'name:value'.[/red]\nExecuting with defaults.\n"
)
if ns_parser.file:
complete_file_path = str(USER_CUSTOM_REPORTS_DIRECTORY / ns_parser.file)
if os.path.exists(complete_file_path):
reports_model.render_report(
input_path=complete_file_path, args_dict=parameters_dict
)
else:
console.print(
f"[red]Notebook '{ns_parser.file}' not found![/red]\n"
)

View File

@@ -0,0 +1,420 @@
"""Reports Model Module."""
__docformat__ = "numpy"
import logging
# pylint: disable=R1732, R0912
import os
from pathlib import Path
from threading import Thread
import webbrowser
from ast import literal_eval
from datetime import datetime
from typing import Any, Dict, List, Union
from ipykernel.kernelapp import IPKernelApp
import papermill as pm
import pandas as pd
from openbb_terminal import feature_flags as obbff
from openbb_terminal.core.config.paths import (
MISCELLANEOUS_DIRECTORY,
USER_PORTFOLIO_DATA_DIRECTORY,
USER_REPORTS_DIRECTORY,
USER_CUSTOM_REPORTS_DIRECTORY,
)
from openbb_terminal.decorators import log_start_end
from openbb_terminal.rich_config import console
from openbb_terminal.forex.forex_controller import FX_TICKERS
logger = logging.getLogger(__name__)
CURRENT_LOCATION = Path(__file__)
REPORTS_FOLDER = CURRENT_LOCATION.parent / "templates"
USER_REPORTS = {
filepath.name: filepath
for file_type in ["ipynb"]
for filepath in USER_CUSTOM_REPORTS_DIRECTORY.rglob(f"*.{file_type}")
}
# TODO: Trim available choices to avoid errors in notebooks.
etf_data_path = CURRENT_LOCATION.parent / "data" / "etf_tickers.csv"
ETF_TICKERS = pd.read_csv(etf_data_path).iloc[:, 0].to_list()
crypto_data_path = CURRENT_LOCATION.parent / "data" / "crypto_tickers.csv"
CRYPTO_TICKERS = pd.read_csv(crypto_data_path).iloc[:, 0].to_list()
stocks_data_path = CURRENT_LOCATION.parent / "data" / "stocks_tickers.csv"
STOCKS_TICKERS = pd.read_csv(stocks_data_path).iloc[:, 0].to_list()
PORTFOLIO_HOLDINGS_FILES = {
filepath.name: filepath
for file_type in ["xlsx", "csv"]
for filepath in (USER_PORTFOLIO_DATA_DIRECTORY / "holdings").rglob(f"*.{file_type}")
}
PORTFOLIO_HOLDINGS_FILES.update(
{
filepath.name: filepath
for file_type in ["xlsx", "csv"]
for filepath in (
MISCELLANEOUS_DIRECTORY / "portfolio_examples" / "holdings"
).rglob(f"*.{file_type}")
}
)
REPORT_CHOICES = {
"etf": {
"--symbol": {c: None for c in ETF_TICKERS},
},
"forex": {
"--symbol": {c: None for c in FX_TICKERS},
},
"portfolio": {
"--orderbook": {c: None for c in PORTFOLIO_HOLDINGS_FILES},
},
"economy": None,
"equity": {
"--symbol": {c: None for c in STOCKS_TICKERS},
},
"crypto": {
"--symbol": {c: None for c in CRYPTO_TICKERS},
},
"forecast": {
"--symbol": {c: None for c in STOCKS_TICKERS + ETF_TICKERS},
},
}
@log_start_end(log=logger)
def get_arg_choices(report_name: str, arg_name: str) -> Union[List[str], None]:
"""Get argument choices from autocompletion for crypto, forex and portfolio.
Parameters
----------
report_name: str
Name of report chosen.
arg_name: str
Argument to limit choices.
Returns:
List[str]: List with argument choices from autocompletion.
"""
choices = None
if report_name in ("forex", "portfolio"):
if "--" + arg_name in REPORT_CHOICES[report_name]: # type: ignore
choices = list(REPORT_CHOICES[report_name]["--" + arg_name].keys()) # type: ignore
return choices
@log_start_end(log=logger)
def get_reports_available(
folder: Path = REPORTS_FOLDER, warn: bool = True
) -> List[str]:
"""Get Jupyter notebook available in folder.
Parameters
----------
folder: Path
Path to folder.
Returns:
List[str]: List with names of notebooks available.
"""
bad_format = []
available = []
for notebook in os.listdir(folder):
if notebook.endswith(".ipynb"):
if " " in notebook:
bad_format.append(notebook)
else:
available.append(notebook[:-6])
if bad_format and warn:
s = ", ".join(bad_format)
console.print(
f"[red]Character '_' not allowed in the following names: {s}.[/red]"
)
return available
@log_start_end(log=logger)
def extract_parameters(input_path: str) -> Dict[str, str]:
"""Extract required parameters from notebook content.
Parameters
----------
input_path: str
Path of report to be rendered.
Returns:
Dict[str, str]: Dictionary with parameters names and values.
"""
input_path = add_ipynb_extension(input_path)
with open(input_path, encoding="utf-8") as file:
notebook_content = file.read()
# 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":"""
# 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 {}
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
]
# 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))
return parameters_dict
@log_start_end(log=logger)
def render_report(input_path: str, args_dict: Dict[str, str]):
"""Report rendering end to end.
Workflow:
1. Update parameters to use in notebook with received arguments
2. Create output path
3. Update parameters with output_path
4. Validate and execute notebook in a new thread.
Parameters
----------
input_path: str
Path of report to be rendered.
args_dict: Dict[str, str]
Dictionary with received arguments dictionary.
"""
try:
parameters_dict = update_parameters(input_path, args_dict)
output_path = create_output_path(input_path, parameters_dict)
parameters_dict["report_name"] = output_path
if parameters_dict:
t = Thread(
target=execute_notebook, args=(input_path, parameters_dict, output_path)
)
t.start()
t.join()
except Exception as e:
console.print(f"[red]Cannot execute notebook - {e}")
@log_start_end(log=logger)
def update_parameters(input_path: str, args_dict: Dict[str, str]) -> Dict[str, Any]:
"""Update dictionary of parameters to be used in report with received arguments.
Parameters
----------
input_path: str
Path of report to be rendered.
args_dict: Dict[str, str]
Dictionary with received arguments dictionary.
Returns
-------
Dict[str, Any]
Dictionary with report parameters.
"""
parameters_dict = extract_parameters(input_path)
for key, val in args_dict.items():
if key in parameters_dict:
parameters_dict[key] = val
else:
console.print(f"[red]'{key}' not found in notebook parameters.[/red]")
return parameters_dict
@log_start_end(log=logger)
def create_output_path(input_path: str, parameters_dict: Dict[str, Any]) -> str:
"""Create path to save rendered report, thus the output path.
Parameters
----------
input_path: str
Path of report to be rendered.
parameters_dict: Dict[str, Any]
Dictionary with report parameters.
Returns
-------
str
Path of rendered report.
"""
report_name = os.path.split(input_path)[-1]
param_values = list(parameters_dict.values())
args_to_output = f"_{'_'.join(param_values)}" if "_".join(param_values) else ""
report_output_name = (
f"{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+ "_"
+ f"{report_name}{args_to_output}"
)
output_path = str(USER_REPORTS_DIRECTORY / report_output_name)
output_path = output_path.replace(".", "_")
return output_path
@log_start_end(log=logger)
def execute_notebook(input_path, parameters, output_path):
"""Execute the input path's notebook with the parameters provided.
Then, save it in the output path.
Parameters
----------
input_path: str
Path of report to be rendered.
parameters: Dict[str, Any]
Dictionary with report parameters.
output_path: str
Path of rendered report.
"""
input_path = add_ipynb_extension(input_path)
try:
result = pm.execute_notebook(
input_path=input_path,
output_path=output_path + ".ipynb",
parameters=parameters,
)
if not result["metadata"]["papermill"]["exception"]:
console.print(f"\n[green]Notebook:[/green] {output_path}.ipynb")
if obbff.OPEN_REPORT_AS_HTML:
report_output_path = os.path.join(
os.path.abspath(os.path.join(".")), output_path + ".html"
)
webbrowser.open(f"file://{report_output_path}")
console.print(f"\n[green]Report:[/green] {report_output_path}\n")
else:
console.print("\n")
else:
console.print("[red]\nReport .html couldn't be created.\n[/red]")
except pm.PapermillExecutionError as e:
console.print(
f"[red]An error was encountered in cell [{e.cell_index}], check the notebook:[/red]\n"
f"{output_path}\n"
)
@log_start_end(log=logger)
def add_ipynb_extension(path: str) -> str:
"""Add .ipynb extension to path.
Parameters
----------
path: str
Path to notebook file.
Returns
-------
str
Path to .ipynb file.
"""
if not path.endswith(".ipynb"):
return path + ".ipynb"
return path
@log_start_end(log=logger)
def check_ipynb(path: str) -> str:
"""Check if there is .ipynb extension in path.
This is useful to the controller type check.
Parameters
----------
path: str
Path to notebook file.
Returns
-------
bool
Path if paths endswith .ipynb, else empty string.
"""
if not path.endswith(".ipynb"):
console.print("[red]Please provide a .ipynb file.[/red]\n")
return ""
return path
def ipykernel_launcher(module_file: str, module_hist_file: str):
"""This function mocks 'ipykernel_launcher.py' launching a Jupyter notebook kernel.
It is useful when running python commands inside a frozen application like our
installer distribution, where sys.executable[0] is not the path to python
interpreter, rather it is the path to the application executable.
Problem:
'papermill' was trying to execute the following command on a subprocess:
$ .../bin/python -m ipykernel_launcher -f ... --HistoryManager.hist_file ...
'papermill' was using '.../bin/python' because it is looks for the sys.executable[0],
which most of the time leads to the python interpreter. In our frozen app,
sys.executable[0] leads to 'OpenBB Terminal/.OpenBB/OpenBBTerminal', which in turn
executes 'terminal.py.
This means that the command was being executed in 'terminal.py'. Consequently,
one gets the following error message:
$ terminal: error: unrecognized arguments: -m ipykernel_launcher -f ... --HistoryManager.hist_file ...
Solution:
Parse 'papermill' command in the 'terminal_controller', which is what follows
'terminal.py' and here receive the parsed 'papermill' command arguments and
route them to IPKernelApp as if this is 'ipykernel_launcher' module
- the kernel is launched.
Source: https://pyinstaller.org/en/stable/runtime-information.html#using-sys-executable-and-sys-argv-0
Parameters
----------
module_file: str
Specified connection file.
module_hist_file: str
History manager file.
"""
IPKernelApp.launch_instance(
argv=[
"-f",
module_file,
"--HistoryManager.hist_file",
module_hist_file,
]
)

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

View File

@@ -34,7 +34,7 @@
"from openbb_terminal.sdk import helper\n",
"from openbb_terminal.sdk import openbb\n",
"from openbb_terminal.helper_classes import TerminalStyle\n",
"\n",
"from openbb_terminal.core.config.paths import REPOSITORY_DIRECTORY\n",
"\n",
"%matplotlib inline\n",
"matplotlib_inline.backend_inline.set_matplotlib_formats(\"svg\")\n",
@@ -103,7 +103,7 @@
"id": "0b9ec3f9",
"metadata": {},
"source": [
"Overview"
"Summary"
]
},
{
@@ -171,24 +171,27 @@
" start_date=datetime.datetime.now().strftime(\"%Y-%m-%d\"),\n",
" importance=\"high\",\n",
")\n",
"events_1 = events_1.set_index(\"Date\")\n",
"if not events_1.empty:\n",
" events_1 = events_1.set_index(\"Date\")\n",
"\n",
"events_2, title_2 = openbb.economy.events(\n",
" country=\"Euro zone\",\n",
" start_date=datetime.datetime.now().strftime(\"%Y-%m-%d\"),\n",
" importance=\"all\",\n",
")\n",
"events_2 = events_2[\n",
" (events_2[\"Importance\"] == \"High\") | (events_2[\"Importance\"] == \"Medium\")\n",
"]\n",
"events_2 = events_2.set_index(\"Date\")\n",
"if not events_2.empty:\n",
" events_2 = events_2[\n",
" (events_2[\"Importance\"] == \"High\") | (events_2[\"Importance\"] == \"Medium\")\n",
" ]\n",
" events_2 = events_2.set_index(\"Date\")\n",
"\n",
"events_3, title_3 = openbb.economy.events(\n",
" country=\"China\",\n",
" start_date=datetime.datetime.now().strftime(\"%Y-%m-%d\"),\n",
" importance=\"all\",\n",
")\n",
"events_3 = events_3.set_index(\"Date\")"
"if not events_3.empty:\n",
" events_3 = events_3.set_index(\"Date\")"
]
},
{
@@ -223,20 +226,21 @@
"fig.savefig(f, format=\"svg\")\n",
"ycrv_chart_1 = f.getvalue().decode(\"utf-8\")\n",
"\n",
"ycrv_data_1.set_index(\"Tenor\", inplace=True)\n",
"ycrv_data_1.index = [\n",
" \"1M\",\n",
" \"3M\",\n",
" \"6M\",\n",
" \"1Y\",\n",
" \"2Y\",\n",
" \"3Y\",\n",
" \"5Y\",\n",
" \"7Y\",\n",
" \"10Y\",\n",
" \"20Y\",\n",
" \"30Y\",\n",
"]"
"if not ycrv_data_1.empty:\n",
" ycrv_data_1.set_index(\"Tenor\", inplace=True)\n",
" ycrv_data_1.index = [\n",
" \"1M\",\n",
" \"3M\",\n",
" \"6M\",\n",
" \"1Y\",\n",
" \"2Y\",\n",
" \"3Y\",\n",
" \"5Y\",\n",
" \"7Y\",\n",
" \"10Y\",\n",
" \"20Y\",\n",
" \"30Y\",\n",
" ]"
]
},
{
@@ -262,26 +266,27 @@
"f = io.BytesIO()\n",
"fig.savefig(f, format=\"svg\")\n",
"ycrv_chart_2 = f.getvalue().decode(\"utf-8\")\n",
"ycrv_data_2.set_index(\"Tenor\", inplace=True)\n",
"ycrv_data_2.index = [\n",
" \"3M\",\n",
" \"6M\",\n",
" \"9M\",\n",
" \"1Y\",\n",
" \"2Y\",\n",
" \"3Y\",\n",
" \"4Y\",\n",
" \"5Y\",\n",
" \"6Y\",\n",
" \"7Y\",\n",
" \"8Y\",\n",
" \"9Y\",\n",
" \"10Y\",\n",
" \"15Y\",\n",
" \"20Y\",\n",
" \"25Y\",\n",
" \"30Y\",\n",
"]"
"if not ycrv_data_1.empty:\n",
" ycrv_data_2.set_index(\"Tenor\", inplace=True)\n",
" ycrv_data_2.index = [\n",
" \"3M\",\n",
" \"6M\",\n",
" \"9M\",\n",
" \"1Y\",\n",
" \"2Y\",\n",
" \"3Y\",\n",
" \"4Y\",\n",
" \"5Y\",\n",
" \"6Y\",\n",
" \"7Y\",\n",
" \"8Y\",\n",
" \"9Y\",\n",
" \"10Y\",\n",
" \"15Y\",\n",
" \"20Y\",\n",
" \"25Y\",\n",
" \"30Y\",\n",
" ]"
]
},
{
@@ -663,8 +668,8 @@
"source": [
"body = \"\"\n",
"\n",
"img = \"./openbb_terminal/reports/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = \"./openbb_terminal/reports/floppy-disc.png\"\n",
"img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
@@ -677,7 +682,7 @@
"\n",
"body += widgets.tablinks(\n",
" [\n",
" \"Overview\",\n",
" \"SUMMARY\",\n",
" \"Events\",\n",
" \"Yields\",\n",
" \"Equity Market\",\n",
@@ -685,7 +690,7 @@
" ]\n",
")\n",
"\n",
"# Overview\n",
"# Summary\n",
"htmlcode = widgets.row(\n",
" [widgets.h(3, \"Global markets (source: WSJ)\") + overview.to_html()]\n",
")\n",
@@ -704,7 +709,7 @@
" + news[:6].to_html()\n",
" ]\n",
")\n",
"body += widgets.add_tab(\"Overview\", htmlcode)\n",
"body += widgets.add_tab(\"SUMMARY\", htmlcode)\n",
"\n",
"# Events\n",
"htmlcode = widgets.row([widgets.h(3, \"Economic calendars (source: investing.com)\")])\n",
@@ -762,20 +767,12 @@
"with open(report_name + \".html\", \"w\", encoding=\"utf-8\") as fh:\n",
" fh.write(report)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "44d43dde-189c-4371-aa14-e72c428de830",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "Python 3.10.4 ('obb')",
"display_name": "Python 3.9.13 ('obb')",
"language": "python",
"name": "python3"
},
@@ -789,11 +786,11 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
"version": "3.9.13"
},
"vscode": {
"interpreter": {
"hash": "1a8cc6b6e60740679b24fc1ea93bdeb94d949a22102a80c99b7fd3f0d572afd6"
"hash": "e784374cc4f9a84ed6af2983247aaf351b155c82bcc2188134a65f3c99031000"
}
}
},

View File

@@ -35,7 +35,7 @@
"from openbb_terminal.sdk import openbb\n",
"from openbb_terminal import config_terminal as cfg\n",
"from openbb_terminal.helper_classes import TerminalStyle\n",
"from openbb_terminal.core.config.paths import USER_ENV_FILE\n",
"from openbb_terminal.core.config.paths import USER_ENV_FILE, REPOSITORY_DIRECTORY\n",
"\n",
"%matplotlib inline\n",
"matplotlib_inline.backend_inline.set_matplotlib_formats(\"svg\")\n",
@@ -88,7 +88,7 @@
"# Parameters that will be replaced when calling this notebook\n",
"# Do not leave parameters blank as notebook will not run otherwise\n",
"symbol = \"msft\"\n",
"report_name = \"Equity report for AAPL\""
"report_name = f\"Equity report for {symbol}\""
]
},
{
@@ -133,6 +133,7 @@
"outputs": [],
"source": [
"info = openbb.stocks.fa.info(symbol=symbol).transpose()\n",
"\n",
"if info[\"Long business summary\"][0] != \"NA\":\n",
" overview = info[\"Long business summary\"][0]\n",
"else:\n",
@@ -181,10 +182,6 @@
"outputs": [],
"source": [
"tables = openbb.etf.news(info[\"Short name\"][0], 5)\n",
"\n",
"# Add an empty iterator if it the returned type is None\n",
"# i.e.: in case of lack of a valid API\n",
"tables = tables if tables is not None else []\n",
"for table in tables:\n",
" table[0].loc[\"link\"] = (\n",
" table[0].loc[\"link\"].apply(lambda x: f'<a href=\"{x}\">{x}</a>')\n",
@@ -535,10 +532,6 @@
"outputs": [],
"source": [
"similar_companies = openbb.stocks.ca.polygon_peers(symbol)\n",
"\n",
"# Add an empty list if it the returned list is empty\n",
"# i.e.: in case of lack of a valid API\n",
"similar_companies = similar_companies if similar_companies is not None else []\n",
"similar_companies.append(symbol)"
]
},
@@ -1231,8 +1224,8 @@
"source": [
"body = \"\"\n",
"\n",
"img = \"./openbb_terminal/reports/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = \"./openbb_terminal/reports/floppy-disc.png\"\n",
"img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
@@ -1245,7 +1238,7 @@
"\n",
"body += widgets.tablinks(\n",
" [\n",
" \"Summary\",\n",
" \"SUMMARY\",\n",
" \"Overview\",\n",
" \"Fundamental Analysis\",\n",
" \"Technical Analysis\",\n",
@@ -1320,7 +1313,7 @@
" ],\n",
" round(regression_val - last_price, 2),\n",
" )\n",
"body += widgets.add_tab(\"Summary\", htmlcode)\n",
"body += widgets.add_tab(\"SUMMARY\", htmlcode)\n",
"\n",
"htmlcode = widgets.row([widgets.h(3, \"Description\") + widgets.p(overview)])\n",
"htmlcode += widgets.row([widgets.h(3, \"Price Chart\") + price_chart])\n",
@@ -1382,13 +1375,9 @@
" )\n",
"except:\n",
" pass\n",
"\n",
"try:\n",
" htmlcode += widgets.row(\n",
" [widgets.h(3, f\"Enterprise data for {symbol}\") + enterprise_df.to_html()]\n",
" )\n",
"except:\n",
" pass\n",
"htmlcode += widgets.row(\n",
" [widgets.h(3, f\"Enterprise data for {symbol}\") + enterprise_df.to_html()]\n",
")\n",
"htmlcode += widgets.row(\n",
" [widgets.h(3, f\"Income Statement for {symbol}\") + income_df.to_html()]\n",
")\n",
@@ -1402,7 +1391,9 @@
" )\n",
"except:\n",
" pass\n",
"\n",
"body += widgets.add_tab(\"Fundamental Analysis\", htmlcode)\n",
"\n",
"htmlcode = widgets.row([widgets.h(3, \"Put to call ratio\") + pcr_chart])\n",
"htmlcode += widgets.row(\n",
" [widgets.h(3, \"Option Volume for closest expiry date\") + vol_chart]\n",
@@ -1570,14 +1561,6 @@
" os.getenv(\"OPENBB_RICHSTYLE\") or \"dark\",\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f20745a4-053f-4cd3-8bb2-96c88c0600df",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
@@ -1597,11 +1580,11 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
"version": "3.9.13"
},
"vscode": {
"interpreter": {
"hash": "1a8cc6b6e60740679b24fc1ea93bdeb94d949a22102a80c99b7fd3f0d572afd6"
"hash": "e784374cc4f9a84ed6af2983247aaf351b155c82bcc2188134a65f3c99031000"
}
}
},

View File

@@ -33,6 +33,7 @@
"from openbb_terminal.sdk import 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",
"%matplotlib inline\n",
"matplotlib_inline.backend_inline.set_matplotlib_formats(\"svg\")\n",
@@ -62,12 +63,7 @@
" theme = TerminalStyle(\"light\", \"light\", \"light\")\n",
"except:\n",
" pass\n",
"stylesheet = widgets.html_report_stylesheet()\n",
"try:\n",
" with open(\"openbb_terminal/reports/OpenBB_reports_logo.png\", \"rb\") as image_file:\n",
" openbb_image_encoded = base64.b64encode(image_file.read())\n",
"except FileNotFoundError:\n",
" openbb_image_encoded = None"
"stylesheet = widgets.html_report_stylesheet()"
]
},
{
@@ -240,8 +236,8 @@
"source": [
"body = \"\"\n",
"\n",
"img = \"./openbb_terminal/reports/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = (\"./openbb_terminal/reports/floppy-disc.png\",)\n",
"img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
@@ -275,20 +271,12 @@
"with open(report_name + \".html\", \"w\", encoding=\"utf-8\") as fh:\n",
" fh.write(report)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "703368dd-a57d-4584-a85c-319ead55f628",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"display_name": "Python 3.9.13 ('obb')",
"language": "python",
"name": "python3"
},
@@ -302,11 +290,11 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
"version": "3.9.13"
},
"vscode": {
"interpreter": {
"hash": "ab0e87f2bc73919b75956d1b3e303320699d53da895be1bb46287d7dc67c6497"
"hash": "e784374cc4f9a84ed6af2983247aaf351b155c82bcc2188134a65f3c99031000"
}
}
},

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -30,6 +30,7 @@
"from openbb_terminal.sdk import 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",
"%matplotlib inline\n",
"matplotlib_inline.backend_inline.set_matplotlib_formats(\"svg\")\n",
@@ -47,12 +48,7 @@
" theme = TerminalStyle(\"light\", \"light\", \"light\")\n",
"except:\n",
" pass\n",
"stylesheet = widgets.html_report_stylesheet()\n",
"try:\n",
" with open(\"openbb_terminal/reports/OpenBB_reports_logo.png\", \"rb\") as image_file:\n",
" openbb_image_encoded = base64.b64encode(image_file.read())\n",
"except FileNotFoundError:\n",
" openbb_image_encoded = None"
"stylesheet = widgets.html_report_stylesheet()"
]
},
{
@@ -230,8 +226,8 @@
"source": [
"body = \"\"\n",
"\n",
"img = \"./openbb_terminal/reports/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = (\"./openbb_terminal/reports/floppy-disc.png\",)\n",
"img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
@@ -260,20 +256,12 @@
"with open(report_name + \".html\", \"w\", encoding=\"utf-8\") as fh:\n",
" fh.write(report)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7f52d81-8362-4a68-9830-fee24c16fa08",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "Python 3.10.4 ('obb')",
"display_name": "Python 3.9.13 ('obb')",
"language": "python",
"name": "python3"
},
@@ -287,11 +275,11 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
"version": "3.9.13"
},
"vscode": {
"interpreter": {
"hash": "1a8cc6b6e60740679b24fc1ea93bdeb94d949a22102a80c99b7fd3f0d572afd6"
"hash": "e784374cc4f9a84ed6af2983247aaf351b155c82bcc2188134a65f3c99031000"
}
}
},

View File

@@ -34,6 +34,7 @@
"from openbb_terminal.sdk import 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",
"%matplotlib inline\n",
"matplotlib_inline.backend_inline.set_matplotlib_formats(\"svg\")\n",
@@ -51,12 +52,7 @@
" theme = TerminalStyle(\"light\", \"light\", \"light\")\n",
"except:\n",
" pass\n",
"stylesheet = widgets.html_report_stylesheet()\n",
"try:\n",
" with open(\"./openbb_terminal/reports/OpenBB_reports_logo.png\", \"rb\") as image_file:\n",
" openbb_image_encoded = base64.b64encode(image_file.read())\n",
"except FileNotFoundError:\n",
" openbb_image_encoded = None"
"stylesheet = widgets.html_report_stylesheet()"
]
},
{
@@ -630,11 +626,8 @@
"source": [
"body = \"\"\n",
"\n",
"try:\n",
" img = f'<img src=\"data:image/png;base64,{openbb_image_encoded.decode()}\" alt=\"OpenBB\" style=\"width:144px;\">'\n",
"except AttributeError:\n",
" img = None\n",
"floppy_disk_img = \"openbb_terminal/reports/floppy-disc.png\"\n",
"img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
@@ -757,7 +750,7 @@
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "Python 3.10.4 ('obb')",
"display_name": "Python 3.9.13 ('obb')",
"language": "python",
"name": "python3"
},
@@ -771,11 +764,11 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
"version": "3.9.13"
},
"vscode": {
"interpreter": {
"hash": "1a8cc6b6e60740679b24fc1ea93bdeb94d949a22102a80c99b7fd3f0d572afd6"
"hash": "e784374cc4f9a84ed6af2983247aaf351b155c82bcc2188134a65f3c99031000"
}
}
},

View File

@@ -38,6 +38,7 @@
"from openbb_terminal.sdk import openbb\n",
"from openbb_terminal.sdk import Portfolio\n",
"from openbb_terminal.helper_classes import TerminalStyle\n",
"from openbb_terminal.core.config.paths import REPOSITORY_DIRECTORY\n",
"\n",
"%matplotlib inline\n",
"matplotlib_inline.backend_inline.set_matplotlib_formats(\"svg\")\n",
@@ -93,19 +94,6 @@
"report_name = \"Portfolio Report\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ee7112da",
"metadata": {},
"outputs": [],
"source": [
"if \"reports\" in os.getcwd():\n",
" holdings = \"../../portfolio/holdings/\"\n",
"else:\n",
" holdings = \"portfolio/holdings/\""
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -113,6 +101,7 @@
"metadata": {},
"outputs": [],
"source": [
"holdings = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/miscellaneous/portfolio_examples/holdings/\"\n",
"orderbook_path = holdings + orderbook\n",
"_, orderbook_file = os.path.split(orderbook_path)\n",
"report_title = f\"Portfolio report for {orderbook_file}\"\n",
@@ -153,7 +142,7 @@
" P.get_orderbook()\n",
"except ValueError:\n",
" raise ValueError(\n",
" \"Failed to load the orderbook. Is this file inside the 'Holdings' folder?\"\n",
" \"Failed to load the orderbook. Is this file inside the 'holdings' folder?\"\n",
" )"
]
},
@@ -348,13 +337,10 @@
"metadata": {},
"outputs": [],
"source": [
"from glob import escape\n",
"\n",
"\n",
"body = \"\"\n",
"\n",
"img = \"./openbb_terminal/reports/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = (\"./openbb_terminal/reports/floppy-disc.png\",)\n",
"img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/OpenBB_reports_logo.png\"\n",
"floppy_disk_img = str(REPOSITORY_DIRECTORY) + \"/openbb_terminal/reports/templates/floppy-disc.png\"\n",
"body += widgets.header(\n",
" img,\n",
" floppy_disk_img,\n",
@@ -365,7 +351,7 @@
" report_title,\n",
")\n",
"\n",
"body += widgets.tablinks([\"Summary\", \"In-Depth Analysis\", \"Statistics\"])\n",
"body += widgets.tablinks([\"SUMMARY\", \"In-Depth Analysis\", \"Statistics\"])\n",
"\n",
"# Summary\n",
"htmlcode = widgets.p(\n",
@@ -409,7 +395,7 @@
" [widgets.h(3, \"Benchmark Trades\") + P.benchmark_trades.to_html()]\n",
")\n",
"\n",
"body += widgets.add_tab(\"Summary\", htmlcode)\n",
"body += widgets.add_tab(\"SUMMARY\", htmlcode)\n",
"\n",
"# In Depth Analysis\n",
"htmlcode = widgets.p(\n",
@@ -480,7 +466,7 @@
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "Python 3.10.4 ('obb')",
"display_name": "Python 3.9.13 ('obb')",
"language": "python",
"name": "python3"
},
@@ -494,11 +480,11 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
"version": "3.9.13"
},
"vscode": {
"interpreter": {
"hash": "1a8cc6b6e60740679b24fc1ea93bdeb94d949a22102a80c99b7fd3f0d572afd6"
"hash": "e784374cc4f9a84ed6af2983247aaf351b155c82bcc2188134a65f3c99031000"
}
}
},

View File

@@ -7,7 +7,7 @@ import warnings
import types
import functools
import importlib
from typing import Optional, Callable, List
from typing import Optional, Callable, List, Union
import logging
from traceback import format_stack
@@ -2383,7 +2383,7 @@ class Loader:
log_all_settings()
@classmethod
def get_function(cls, function_path: str) -> Callable:
def get_function(cls, function_path: str) -> Union[Callable, None]:
"""Get function from string path.
Parameters
@@ -2399,7 +2399,16 @@ class Loader:
Function
"""
module_path, function_name = function_path.rsplit(sep=".", maxsplit=1)
module = cls.__load_module(module_path=module_path)
try:
module = cls.__load_module(module_path=module_path)
except Exception as e:
# This avoids crash on loading SDK under installer application.
# SDK is used by papermill to generate reports and some dependencies
# are not compatible. Since the reports are run in a subprocess,
# this error message is actually not displayed on screen.
# TODO: Fix this.
console.print(f"Cannot load: {module_path} -> {e}")
return None
try:
function = getattr(module, function_name)

View File

@@ -18,7 +18,6 @@ from prompt_toolkit.completion import NestedCompleter
from prompt_toolkit.styles import Style
from prompt_toolkit.formatted_text import HTML
import pandas as pd
from openbb_terminal import feature_flags as obbff
from openbb_terminal import keys_controller
from openbb_terminal.terminal_helper import is_packaged_application
@@ -40,7 +39,7 @@ from openbb_terminal.helper_funcs import (
)
from openbb_terminal.loggers import setup_logging
from openbb_terminal.core.log.generation.settings_logger import log_all_settings
from openbb_terminal.menu import session
from openbb_terminal.menu import session, is_papermill
from openbb_terminal.parent_classes import BaseController
from openbb_terminal.rich_config import console, MenuText
from openbb_terminal.terminal_helper import (
@@ -55,8 +54,9 @@ from openbb_terminal.terminal_helper import (
)
from openbb_terminal.helper_funcs import parse_and_split_input
from openbb_terminal.common import feedparser_view
from openbb_terminal.reports.reports_model import ipykernel_launcher
# pylint: disable=too-many-public-methods,import-outside-toplevel
# pylint: disable=too-many-public-methods,import-outside-toplevel, too-many-function-args
# pylint: disable=too-many-branches,no-member,C0302,too-many-return-statements
logger = logging.getLogger(__name__)
@@ -889,8 +889,10 @@ def terminal(jobs_cmds: List[str] = None, test_mode=False):
except (KeyboardInterrupt, EOFError):
print_goodbye()
break
# Get input from user without auto-completion
elif is_papermill():
pass
else:
# Get input from user without auto-completion
an_input = input(f"{get_flair()} / $ ")
try:
@@ -1098,6 +1100,7 @@ def main(
path_list: List[str],
verbose: bool,
routines_args: List[str] = None,
**kwargs,
):
"""
Runs the terminal with various options
@@ -1119,12 +1122,16 @@ def main(
E.g. GME,AMC,BTC-USD
"""
if kwargs["module"] == "ipykernel_launcher":
ipykernel_launcher(kwargs["module_file"], kwargs["module_hist_file"])
if test:
run_test_list(path_list=path_list, filtert=filtert, verbose=verbose)
return
if debug:
os.environ["DEBUG_MODE"] = "true"
if isinstance(path_list, list) and path_list[0].endswith(".openbb"):
run_routine(file=path_list[0], routines_args=routines_args)
elif path_list:
@@ -1186,10 +1193,41 @@ def parse_args_and_run():
type=lambda s: [str(item) for item in s.split(",")],
default=None,
)
# The args -m, -f and --HistoryManager.hist_file are used only in reports menu
# by papermill and that's why they have suppress help.
parser.add_argument(
"-m",
help=argparse.SUPPRESS,
dest="module",
default="",
type=str,
)
parser.add_argument(
"-f",
help=argparse.SUPPRESS,
dest="module_file",
default="",
type=str,
)
parser.add_argument(
"--HistoryManager.hist_file",
help=argparse.SUPPRESS,
dest="module_hist_file",
default="",
type=str,
)
if sys.argv[1:] and "-" not in sys.argv[1][0]:
sys.argv.insert(1, "--file")
ns_parser = parser.parse_args()
ns_parser, unknown = parser.parse_known_args()
# This ensures that if terminal.py receives unknown args it will not start.
# Use -d flag if you want to see the unknown args.
if unknown:
if ns_parser.debug:
console.print(unknown)
else:
sys.exit(-1)
main(
ns_parser.debug,
ns_parser.test,
@@ -1197,6 +1235,9 @@ def parse_args_and_run():
ns_parser.path,
ns_parser.verbose,
ns_parser.routine_args,
module=ns_parser.module,
module_file=ns_parser.module_file,
module_hist_file=ns_parser.module_hist_file,
)

2614
poetry.lock generated vendored

File diff suppressed because it is too large Load Diff

1
pyproject.toml vendored
View File

@@ -100,6 +100,7 @@ ipyflex = "^0.2.4"
setuptools = "^65.4.1"
investiny = "^0.5.0"
numpy = "1.21.6"
papermill = {git = "https://github.com/nteract/papermill.git", rev = "main"}
stocksera = "^0.1.21"
[tool.poetry.dev-dependencies]

View File

@@ -38,7 +38,7 @@ cfgv==3.3.1; python_full_version >= "3.6.1" and python_version >= "3.7"
charset-normalizer==2.1.0; python_full_version >= "3.6.0"
click==8.1.3; python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7" and python_version >= "3.7"
codespell==2.2.2; python_version >= "3.7"
colorama==0.4.6; python_full_version >= "3.7.2" and python_version >= "3.8" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.7.0") and (python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.7.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and platform_system == "Windows" or python_full_version >= "3.7.0" and platform_system == "Windows") and (python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" and (python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7" and python_version >= "3.7") or platform_system == "Windows" and python_version >= "3.7" and (python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7" and python_version >= "3.7") and python_full_version >= "3.7.0") and python_full_version < "4.0.0"
colorama==0.4.6; python_full_version >= "3.7.2" and python_version >= "3.8" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.7.0") and (python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.7.0") and (python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" and (python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7" and python_version >= "3.7") or platform_system == "Windows" and python_version >= "3.7" and (python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7" and python_version >= "3.7") and python_full_version >= "3.7.0") and python_full_version < "4.0.0"
commonmark==0.9.1; python_full_version >= "3.6.3" and python_full_version < "4.0.0" and (python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7")
convertdate==2.4.0; python_version >= "3.7" and python_version < "4"
coverage==6.5.0; python_version >= "3.7"
@@ -164,6 +164,7 @@ pandas-market-calendars==3.2; python_full_version >= "3.7.0"
pandas-ta==0.3.14b
pandas==1.5.1; python_version >= "3.8"
pandocfilters==1.5.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
papermill @ git+https://github.com/nteract/papermill.git@main ; python_version >= "3.7"
parso==0.8.3; python_version >= "3.8" and python_full_version >= "3.7.0" and python_full_version < "4.0.0"
pathspec==0.10.1; python_version >= "3.7"
patsy==0.5.3; python_version >= "3.8"
@@ -277,7 +278,7 @@ stocksera==0.1.21; python_version >= "3.6"
streamlit==1.13.0; (python_version >= "3.7" and python_full_version < "3.9.7") or (python_full_version > "3.9.7")
tabulate==0.9.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7"
temporal-cache==0.1.4
tenacity==8.1.0; python_version >= "3.6"
tenacity==8.1.0; python_version >= "3.7"
terminado==0.16.0; python_version >= "3.7"
thepassiveinvestor==1.0.10
threadpoolctl==3.1.0; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.8"
@@ -290,7 +291,7 @@ tomlkit==0.11.5; python_version >= "3.6" and python_version < "4.0" and python_f
toolz==0.12.0; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.7.0" and (python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7" and python_version >= "3.7")
torch==1.11.0; python_full_version >= "3.7.0"
tornado==6.2; python_version >= "3.7" and python_full_version < "3.9.7" and python_full_version >= "3.7.0" or python_full_version > "3.9.7" and python_version >= "3.7" and python_full_version < "4.0.0"
tqdm==4.64.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0"
tqdm==4.64.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
tradingview-ta==3.3.0; python_version >= "3.6"
traitlets==5.5.0; python_full_version >= "3.7.0" and python_full_version < "4.0.0" and python_version >= "3.8"
types-python-dateutil==2.8.19.2

5
requirements.txt vendored
View File

@@ -140,6 +140,7 @@ pandas-market-calendars==3.2; python_full_version >= "3.7.0"
pandas-ta==0.3.14b
pandas==1.5.1; python_version >= "3.8"
pandocfilters==1.5.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
papermill @ git+https://github.com/nteract/papermill.git@main ; python_version >= "3.7"
parso==0.8.3; python_version >= "3.8"
patsy==0.5.3; python_version >= "3.8"
pexpect==4.8.0; sys_platform != "win32" and python_version >= "3.8"
@@ -232,7 +233,7 @@ stocksera==0.1.21; python_version >= "3.6"
streamlit==1.13.0; (python_version >= "3.7" and python_full_version < "3.9.7") or (python_full_version > "3.9.7")
tabulate==0.9.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7"
temporal-cache==0.1.4
tenacity==8.1.0; python_version >= "3.6"
tenacity==8.1.0; python_version >= "3.7"
terminado==0.16.0; python_version >= "3.7"
thepassiveinvestor==1.0.10
threadpoolctl==3.1.0; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.8"
@@ -242,7 +243,7 @@ toml==0.10.2; python_version >= "3.7" and python_full_version < "3.0.0" or pytho
tomli==2.0.1; python_version >= "3.8"
toolz==0.12.0; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.7.0" and (python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7" and python_version >= "3.7")
tornado==6.2; python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7" and python_version >= "3.7"
tqdm==4.64.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0"
tqdm==4.64.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
tradingview-ta==3.3.0; python_version >= "3.6"
traitlets==5.5.0; python_full_version >= "3.7.0" and python_version >= "3.8"
typing-extensions==4.4.0; python_full_version >= "3.7.0" and python_full_version < "4.0.0" and python_version < "3.9" and python_version >= "3.7" and (python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7" and python_version >= "3.7") and (python_version >= "3.7" and python_full_version < "3.9.7" or python_full_version > "3.9.7")

View File

@@ -0,0 +1,358 @@
# IMPORTATION STANDARD
import os
# IMPORTATION THIRDPARTY
import pytest
# IMPORTATION INTERNAL
from openbb_terminal.reports import reports_controller, reports_model
# pylint: disable=E1101
# pylint: disable=W0603
# pylint: disable=E1111
@pytest.mark.vcr(record_mode="none")
@pytest.mark.parametrize(
"queue, expected",
[
(["load", "help"], ["help"]),
(["quit", "help"], ["help"]),
],
)
def test_menu_with_queue(expected, mocker, queue):
path_controller = "openbb_terminal.reports.reports_controller"
# MOCK SWITCH
mocker.patch(
target=f"{path_controller}.ReportController.switch",
return_value=["quit"],
)
result_menu = reports_controller.ReportController(queue=queue).menu()
assert result_menu == expected
@pytest.mark.vcr(record_mode="none")
def test_menu_without_queue_completion(mocker):
path_controller = "openbb_terminal.reports.reports_controller"
# ENABLE AUTO-COMPLETION : HELPER_FUNCS.MENU
mocker.patch(
target="openbb_terminal.feature_flags.USE_PROMPT_TOOLKIT",
new=True,
)
mocker.patch(
target="openbb_terminal.parent_classes.session",
)
mocker.patch(
target="openbb_terminal.parent_classes.session.prompt",
return_value="quit",
)
# DISABLE AUTO-COMPLETION : CONTROLLER.COMPLETER
mocker.patch.object(
target=reports_controller.obbff,
attribute="USE_PROMPT_TOOLKIT",
new=True,
)
mocker.patch(
target=f"{path_controller}.session",
)
mocker.patch(
target=f"{path_controller}.session.prompt",
return_value="quit",
)
result_menu = reports_controller.ReportController(queue=None).menu()
assert result_menu == ["help"]
@pytest.mark.vcr(record_mode="none")
@pytest.mark.parametrize(
"mock_input",
["help", "homee help", "home help", "mock"],
)
def test_menu_without_queue_sys_exit(mock_input, mocker):
path_controller = "openbb_terminal.reports.reports_controller"
# DISABLE AUTO-COMPLETION
mocker.patch.object(
target=reports_controller.obbff,
attribute="USE_PROMPT_TOOLKIT",
new=False,
)
mocker.patch(
target=f"{path_controller}.session",
return_value=None,
)
# MOCK USER INPUT
mocker.patch("builtins.input", return_value=mock_input)
# MOCK SWITCH
class SystemExitSideEffect:
def __init__(self):
self.first_call = True
def __call__(self, *args, **kwargs):
if self.first_call:
self.first_call = False
raise SystemExit()
return ["quit"]
mock_switch = mocker.Mock(side_effect=SystemExitSideEffect())
mocker.patch(
target=f"{path_controller}.ReportController.switch",
new=mock_switch,
)
result_menu = reports_controller.ReportController(queue=None).menu()
assert result_menu == ["help"]
@pytest.mark.vcr(record_mode="none")
@pytest.mark.record_stdout
def test_print_help():
controller = reports_controller.ReportController(queue=None)
controller.print_help()
@pytest.mark.vcr(record_mode="none")
@pytest.mark.parametrize(
"an_input, expected_queue",
[
("", []),
("/help", ["home", "help"]),
("help/help", ["help", "help"]),
("q", ["quit"]),
("h", []),
(
"r",
[
"quit",
"reset",
"reports",
],
),
],
)
def test_switch(an_input, expected_queue):
controller = reports_controller.ReportController(queue=None)
queue = controller.switch(an_input=an_input)
assert queue == expected_queue
@pytest.mark.vcr(record_mode="none")
def test_call_cls(mocker):
mocker.patch("os.system")
controller = reports_controller.ReportController(queue=None)
controller.call_cls([])
assert controller.queue == []
os.system.assert_called_once_with("cls||clear")
@pytest.mark.vcr(record_mode="none")
@pytest.mark.parametrize(
"func, queue, expected_queue",
[
(
"call_exit",
[],
["quit", "quit"],
),
("call_exit", ["help"], ["quit", "quit", "help"]),
("call_home", [], ["quit"]),
("call_help", [], []),
("call_quit", [], ["quit"]),
("call_quit", ["help"], ["quit", "help"]),
(
"call_reset",
[],
[
"quit",
"reset",
"reports",
],
),
(
"call_reset",
["help"],
[
"quit",
"reset",
"reports",
"help",
],
),
],
)
def test_call_func_expect_queue(expected_queue, func, queue):
controller = reports_controller.ReportController(queue=queue)
result = getattr(controller, func)([])
assert result is None
assert controller.queue == expected_queue
@pytest.mark.vcr(record_mode="none")
@pytest.mark.parametrize(
"tested_func, other_args, mocked_func, called_args, called_kwargs",
[
(
"call_etf",
[
"--symbol=SPY",
],
"reports_model.render_report",
[],
dict(
input_path=str(reports_model.REPORTS_FOLDER / "etf"),
args_dict={"symbol": "SPY"},
),
),
(
"call_forex",
[
"--symbol=EURUSD",
],
"reports_model.render_report",
[],
dict(
input_path=str(reports_model.REPORTS_FOLDER / "forex"),
args_dict={"symbol": "EURUSD"},
),
),
(
"call_portfolio",
[
"--orderbook=market.csv",
],
"reports_model.render_report",
[],
dict(
input_path=str(reports_model.REPORTS_FOLDER / "portfolio"),
args_dict={"orderbook": "market.csv"},
),
),
(
"call_economy",
[],
"reports_model.render_report",
[],
dict(
input_path=str(reports_model.REPORTS_FOLDER / "economy"),
args_dict={},
),
),
(
"call_equity",
[
"--symbol=TSLA",
],
"reports_model.render_report",
[],
dict(
input_path=str(reports_model.REPORTS_FOLDER / "equity"),
args_dict={"symbol": "TSLA"},
),
),
(
"call_crypto",
[
"--symbol=BTC",
],
"reports_model.render_report",
[],
dict(
input_path=str(reports_model.REPORTS_FOLDER / "crypto"),
args_dict={"symbol": "BTC"},
),
),
],
)
def test_call_func(
tested_func, mocked_func, other_args, called_args, called_kwargs, mocker
):
path_controller = "openbb_terminal.reports.reports_controller"
# MOCK REMOVE
mocker.patch(target=f"{path_controller}.os.remove")
if mocked_func:
mock = mocker.Mock()
mocker.patch(
target=f"{path_controller}.{mocked_func}",
new=mock,
)
controller = reports_controller.ReportController(queue=None)
getattr(controller, tested_func)(other_args)
if called_args or called_kwargs:
mock.assert_called_once_with(*called_args, **called_kwargs)
else:
mock.assert_called_once()
else:
controller = reports_controller.ReportController(queue=None)
getattr(controller, tested_func)(other_args)
# TODO: No test for forecast command, because it depends on darts being available
@pytest.mark.vcr(record_mode="none")
@pytest.mark.parametrize(
"tested_func, other_args, mocked_func, called_args, called_kwargs",
[
(
"call_forecast",
[
"--symbol=TSLA",
],
"reports_model.render_report",
[],
dict(
input_path=str(reports_model.REPORTS_FOLDER / "forecast"),
args_dict={"symbol": "TSLA"},
),
),
],
)
def test_call_func_forecast(
tested_func, mocked_func, other_args, called_args, called_kwargs, mocker
):
path_controller = "openbb_terminal.reports.reports_controller"
# MOCK REMOVE
mocker.patch(target=f"{path_controller}.os.remove")
try:
import darts # pyright: reportMissingImports=false # noqa: F401, E501 #pylint: disable=import-outside-toplevel, unused-import
forecast = True
except ImportError:
forecast = False
if forecast:
if mocked_func:
mock = mocker.Mock()
mocker.patch(
target=f"{path_controller}.{mocked_func}",
new=mock,
)
controller = reports_controller.ReportController(queue=None)
getattr(controller, tested_func)(other_args)
if called_args or called_kwargs:
mock.assert_called_once_with(*called_args, **called_kwargs)
else:
mock.assert_called_once()
else:
controller = reports_controller.ReportController(queue=None)
getattr(controller, tested_func)(other_args)

View File

@@ -0,0 +1,14 @@
Build and run your custom reports or try one of our templates:
OpenBB reports:
crypto <symbol>
economy
equity <symbol>
etf <symbol>
forecast <symbol>
forex <symbol>
portfolio <orderbook>
Custom reports:
run Run a Jupyter notebook from OpenBBUserData/reports/custom reports

View File

@@ -1 +1,6 @@
---
geekdocCollapseSection: true
---
**Available commands:**
{{< toc-tree >}}

View File

@@ -0,0 +1,13 @@
```
usage: chains [-j] [-h]
```
Opens a voila dashboard showing option chain information for a given ticker and expiration
```
optional arguments:
-j, --jupyter opens in jupyterlab instead of voila (default: False)
-d, --dark runs voila in dark mode (default: False)
-n, --no-input does not ask the user if it can open a server (default: False)
-h, --help show this help message
```

View File

@@ -0,0 +1,8 @@
```
usage: economy [-h]
Run economy report.
optional arguments:
-h, --help show this help message (default: False)
```

View File

@@ -0,0 +1,9 @@
```
usage: equity [--symbol SYMBOL] [-h]
Run equity report.
optional arguments:
--symbol SYMBOL symbol (default: msft)
-h, --help show this help message (default: False)
```

View File

@@ -0,0 +1,9 @@
```
usage: etf [--symbol SYMBOL] [-h]
Run etf report.
optional arguments:
--symbol SYMBOL symbol (default: VTI)
-h, --help show this help message (default: False)
```

View File

@@ -0,0 +1,9 @@
```
usage: forecast [--symbol SYMBOL] [-h]
Run forecast report.
optional arguments:
--symbol SYMBOL symbol (default: VTI)
-h, --help show this help message (default: False)
```

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,13 @@
```
usage: portfolio
[--orderbook {Public_Equity_Orderbook.xlsx,Public_Equity_Orderbook_No_Categorization.xlsx,market.csv,example.csv,bitcoin.csv}]
[-h]
Run portfolio report.
optional arguments:
--orderbook {Public_Equity_Orderbook.xlsx,Public_Equity_Orderbook_No_Categorization.xlsx,market.csv,example.csv,bitcoin.csv}
orderbook (default:
Public_Equity_Orderbook.xlsx)
-h, --help show this help message (default: False)
```

View File

@@ -0,0 +1,16 @@
```
usage: run [-f {test.ipynb}] [-p PARAMETERS [PARAMETERS ...]] [-h]
Run a notebook from this folder:
'/Users/diogosousa/OpenBBUserData/reports'.
optional arguments:
-f {test.ipynb}, --file {test.ipynb}
The file to be loaded (default: None)
-p PARAMETERS [PARAMETERS ...], --parameters PARAMETERS [PARAMETERS ...]
Report parameters with format 'name:value'.
(default: None)
-h, --help show this help message (default: False)
For more information and examples, use 'about run' to access the related guide.
```

View File

@@ -883,6 +883,23 @@ main:
ref: /terminal/jupyter/dashboards/vsurf
- name: reports
ref: /terminal/jupyter/reports
sub:
- name: crypto
ref: /terminal/jupyter/reports/crypto
- name: economy
ref: /terminal/jupyter/reports/economy
- name: equity
ref: /terminal/jupyter/reports/equity
- name: etf
ref: /terminal/jupyter/reports/etf
- name: forecast
ref: /terminal/jupyter/reports/forecast
- name: forex
ref: /terminal/jupyter/reports/forex
- name: portfolio
ref: /terminal/jupyter/reports/portfolio
- name: run
ref: /terminal/jupyter/reports/run
- name: portfolio
ref: /terminal/portfolio
sub: