diff --git a/openbb_terminal/fixedincome/ecb_model.py b/openbb_terminal/fixedincome/ecb_model.py new file mode 100644 index 00000000000..cb134fdec2c --- /dev/null +++ b/openbb_terminal/fixedincome/ecb_model.py @@ -0,0 +1,39 @@ +""" ECB model """ +__docformat__ = "numpy" + +import logging +import pandas as pd + +from openbb_terminal.decorators import log_start_end + +logger = logging.getLogger(__name__) + + +@log_start_end(log=logger) +def get_series_data( + series_id: str = "EST.B.EU000A2X2A25.WT", start_date: str = "", end_date: str = "" +): + """Get ECB data + + Parameters + ---------- + series_id: str + ECB ID of data + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + """ + start_date = start_date[0:4] + start_date[4:7] + start_date[8:9] + end_date = end_date[0:4] + end_date[4:7] + end_date[8:9] + + df = pd.read_csv( + f"https://sdw.ecb.europa.eu/quickviewexport.do?trans=N&start={start_date}&end={end_date}&SERIES_KEY={series_id}&type=csv", + header=5, + usecols=[0, 1], + index_col=0, + parse_dates=True, + ) + df = df.iloc[::-1] + + return df diff --git a/openbb_terminal/fixedincome/ecb_view.py b/openbb_terminal/fixedincome/ecb_view.py new file mode 100644 index 00000000000..d2165c2e996 --- /dev/null +++ b/openbb_terminal/fixedincome/ecb_view.py @@ -0,0 +1,90 @@ +""" ECB view """ +__docformat__ = "numpy" + +from typing import Optional, List +from itertools import cycle +import logging +import os + +from matplotlib import pyplot as plt + +from openbb_terminal.config_terminal import theme +from openbb_terminal.config_plot import PLOT_DPI +from openbb_terminal.decorators import log_start_end +from openbb_terminal.helper_funcs import ( + export_data, + plot_autoscale, + is_valid_axes_count, +) +from openbb_terminal.fixedincome.ecb_model import get_series_data + +logger = logging.getLogger(__name__) + +ID_TO_NAME = { + "EST.B.EU000A2X2A25.WT": "Euro Short-Term Rate: Volume-Weighted Trimmed Mean Rate [Percent]", + "EST.B.EU000A2X2A25.TT": "Euro Short-Term Rate: Total Volume [Millions of EUR]", + "EST.B.EU000A2X2A25.NT": "Euro Short-Term Rate: Number of Transactions", + "EST.B.EU000A2X2A25.R75": "Euro Short-Term Rate: Rate at 75th Percentile of Volume [Percent]", + "EST.B.EU000A2X2A25.NB": "Euro Short-Term Rate: Number of Active Banks", + "EST.B.EU000A2X2A25.VL": "Euro Short-Term Rate: Share of Volume of the 5 Largest Active Banks [Percent]", + "EST.B.EU000A2X2A25.R25": "Euro Short-Term Rate: Rate at 25th Percentile of Volume [Percent]", +} + + +@log_start_end(log=logger) +def plot_estr( + series_id: str = "EST.B.EU000A2X2A25.WT", + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Euro Short-Term Rate (ESTR) + + Parameters + ---------- + series_id: str + ECB ID of ESTR data to plot, options: ['EST.B.EU000A2X2A25.WT', 'EST.B.EU000A2X2A25.TT', 'EST.B.EU000A2X2A25.NT', 'EST.B.EU000A2X2A25.R75', 'EST.B.EU000A2X2A25.NB', 'EST.B.EU000A2X2A25.VL', 'EST.B.EU000A2X2A25.R25'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data( + series_id, start_date if start_date else "", end_date if end_date else "" + ) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title(ID_TO_NAME[series_id]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"estr, {series_id}", + df, + ) diff --git a/openbb_terminal/fixedincome/fixedincome_controller.py b/openbb_terminal/fixedincome/fixedincome_controller.py new file mode 100644 index 00000000000..9384c249c12 --- /dev/null +++ b/openbb_terminal/fixedincome/fixedincome_controller.py @@ -0,0 +1,703 @@ +""" Futures Controller """ +__docformat__ = "numpy" + +# pylint:disable=too-many-lines + +import argparse +import logging +from datetime import datetime, timedelta +from typing import List + +from openbb_terminal.custom_prompt_toolkit import NestedCompleter + +from openbb_terminal import feature_flags as obbff +from openbb_terminal.decorators import log_start_end +from openbb_terminal.fixedincome import ecb_view, nyfed_view +from openbb_terminal.fixedincome import fred_view + +from openbb_terminal.helper_funcs import ( + EXPORT_BOTH_RAW_DATA_AND_FIGURES, + valid_date, +) +from openbb_terminal.parent_classes import BaseController +from openbb_terminal.rich_config import console, MenuText +from openbb_terminal.menu import session + +logger = logging.getLogger(__name__) + + +class FixedIncomeController(BaseController): + """Fixed Income Controller class""" + + CHOICES_COMMANDS = [ + "estr", + "sofr", + "sonia", + "ameribor", + "ffer", + "fftr", + "effr", + "obfr", + "iorb", + "projection", + "oldprojection", + "dwpcr", + ] + + PATH = "/fixedincome/" + CHOICES_GENERATION = True + + estr_parameter_to_fred_id = { + "volume_weighted_trimmed_mean_rate": "ECBESTRVOLWGTTRMDMNRT", + "total_volume": "ECBESTRTOTVOL", + "number_of_transactions": "ECBESTRNUMTRANS", + "rate_at_75th_percentile_of_volume": "ECBESTRRT75THPCTVOL", + "number_of_active_banks": "ECBESTRNUMACTBANKS", + "share_of_volume_of_the_5_largest_active_banks": "ECBESTRSHRVOL5LRGACTBNK", + "rate_at_25th_percentile_of_volume": "ECBESTRRT25THPCTVOL", + } + estr_parameter_to_ecb_id = { + "volume_weighted_trimmed_mean_rate": "EST.B.EU000A2X2A25.WT", + "total_volume": "EST.B.EU000A2X2A25.TT", + "number_of_transactions": "EST.B.EU000A2X2A25.NT", + "rate_at_75th_percentile_of_volume": "EST.B.EU000A2X2A25.R75", + "number_of_active_banks": "EST.B.EU000A2X2A25.NB", + "share_of_volume_of_the_5_largest_active_banks": "EST.B.EU000A2X2A25.VL", + "rate_at_25th_percentile_of_volume": "EST.B.EU000A2X2A25.R25", + } + sofr_parameter_to_fred_id = { + "overnight": "SOFR", + "30_day": "SOFR30DAYAVG", + "90_day": "SOFR90DAYAVG", + "180_day": "SOFR180DAYAVG", + "index": "SOFRINDEX", + } + sonia_parameter_to_fred_id = { + "rate": "IUDSOIA", + "index": "IUDZOS2", + "10th_percentile": "IUDZLS6", + "25th_percentile": "IUDZLS7", + "75th_percentile": "IUDZLS8", + "90th_percentile": "IUDZLS9", + "total_nominal_value": "IUDZLT2", + } + ameribor_parameter_to_fred_id = { + "overnight": "AMERIBOR", + "term_30": "AMBOR30T", + "term_90": "AMBOR90T", + "1_week_term_structure": "AMBOR1W", + "1_month_term_structure": "AMBOR1M", + "3_month_term_structure": "AMBOR3M", + "6_month_term_structure": "AMBOR6M", + "1_year_term_structure": "AMBOR1Y", + "2_year_term_structure": "AMBOR2Y", + "30_day_ma": "AMBOR30", + "90_day_ma": "AMBOR90", + } + ffer_parameter_to_fred_id = { + "monthly": "FEDFUNDS", + "daily": "DFF", + "weekly": "FF", + "daily_excl_weekend": "RIFSPFFNB", + "annual": "RIFSPFFNA", + "biweekly": "RIFSPFFNBWAW", + } + effr_parameter_to_fred_id = { + "rate": "EFFR", + "volume": "EFFRVOL", + "1th_percentile": "EFFR1", + "25th_percentile": "EFFR25", + "75th_percentile": "EFFR75", + "99th_percentile": "EFFR99", + } + effr_parameter_to_nyfed_id = { + "rate": "percentRate", + "volume": "volumeInBillions", + "1th_percentile": "percentPercentile1", + "25th_percentile": "percentPercentile25", + "75th_percentile": "percentPercentile75", + "99th_percentile": "percentPercentile99", + } + obfr_parameter_to_fred_id = { + "rate": "OBFR", + "volume": "OBFRVOL", + "1th_percentile": "EFFR1", + "25th_percentile": "EFFR25", + "75th_percentile": "EFFR75", + "99th_percentile": "EFFR99", + } + dwpcr_parameter_to_fred_id = { + "daily_excl_weekend": "DPCREDIT", + "monthly": "MPCREDIT", + "weekly": "WPCREDIT", + "daily": "RIFSRPF02ND", + "annual": "RIFSRPF02NA", + } + + def __init__(self, queue: List[str] = None): + """Constructor""" + super().__init__(queue) + + if session and obbff.USE_PROMPT_TOOLKIT: + self.choices: dict = self.choices_default + self.completer = NestedCompleter.from_nested_dict(self.choices) # type: ignore + + def print_help(self): + """Print help""" + mt = MenuText("fixedincome/") + mt.add_info("_reference_rates_") + mt.add_cmd("estr") + mt.add_cmd("sofr") + mt.add_cmd("sonia") + mt.add_cmd("ameribor") + mt.add_raw("\n") + mt.add_info("_other_interest_rates_") + mt.add_cmd("ffer") + mt.add_cmd("fftr") + mt.add_cmd("effr") + mt.add_cmd("obfr") + mt.add_cmd("iorb") + mt.add_cmd("projection") + mt.add_cmd("oldprojection") + mt.add_cmd("dwpcr") + + console.print(text=mt.menu_text, menu="Fixed Income") + + @log_start_end(log=logger) + def call_estr(self, other_args: List[str]): + """Process estr command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="estr", + description="Get Euro Short-Term Rate data", + ) + parser.add_argument( + "-p", + "--parameter", + dest="parameter", + type=str, + help="Specific Euro Short-Term Rate data to retrieve", + default="volume_weighted_trimmed_mean_rate", + choices=list(self.estr_parameter_to_fred_id.keys()), + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default=None, + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + if ns_parser.source == "FRED": + fred_view.plot_estr( + self.estr_parameter_to_fred_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + elif ns_parser.source == "ECB": + ecb_view.plot_estr( + self.estr_parameter_to_ecb_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_sofr(self, other_args: List[str]): + """Process sofr command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="sofr", + description="Get Secured Overnight Financing Rate (SOFR) data", + ) + parser.add_argument( + "-p", + "--parameter", + dest="parameter", + type=str, + help="Specific SOFR data to retrieve", + default="overnight", + choices=list(self.sofr_parameter_to_fred_id.keys()), + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default="1980-01-01", + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + if ns_parser.source == "FRED": + fred_view.plot_sofr( + self.sofr_parameter_to_fred_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + elif ns_parser.source == "NYFED": + nyfed_view.plot_sofr( + ns_parser.parameter, + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_sonia(self, other_args: List[str]): + """Process sonia command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="sonia", + description="Get Sterling Overnight Index Average (SONIA) data", + ) + parser.add_argument( + "-p", + "--parameter", + dest="parameter", + type=str, + help="Specific SONIA data to retrieve", + default="rate", + choices=list(self.sonia_parameter_to_fred_id.keys()), + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default=None, + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + fred_view.plot_sonia( + self.sonia_parameter_to_fred_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_ameribor(self, other_args: List[str]): + """Process ameribor command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="ameribor", + description="Get American Interbank Offered Rate (AMERIBOR) data", + ) + parser.add_argument( + "-p", + "--parameter", + dest="parameter", + type=str, + help="Specific AMERIBOR data to retrieve", + default="overnight", + choices=list(self.ameribor_parameter_to_fred_id.keys()), + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default=None, + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + fred_view.plot_ameribor( + self.ameribor_parameter_to_fred_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_ffer(self, other_args: List[str]): + """Process ffer command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="ffer", + description="Get Federal Funds Effective Rate data", + ) + parser.add_argument( + "-p", + "--parameter", + dest="parameter", + type=str, + help="Specific Federal Funds Effective Rate data to retrieve", + default="monthly", + choices=list(self.ffer_parameter_to_fred_id.keys()), + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default=None, + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + fred_view.plot_ffer( + self.ffer_parameter_to_fred_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_fftr(self, other_args: List[str]): + """Process fftr command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="fftr", + description="Get Federal Funds Target Range data", + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default="1980-01-01", + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + if ns_parser.source == "FRED": + fred_view.plot_fftr( + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + elif ns_parser.source == "NYFED": + nyfed_view.plot_fftr( + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_effr(self, other_args: List[str]): + """Process effr command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="effr", + description="Get Effective Federal Funds Rate data", + ) + parser.add_argument( + "-p", + "--parameter", + dest="parameter", + type=str, + help="Specific Effective Federal Funds Rate data to retrieve", + default="rate", + choices=list(self.effr_parameter_to_fred_id.keys()), + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default="1980-01-01", + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + if ns_parser.source == "FRED": + fred_view.plot_effr( + self.effr_parameter_to_fred_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + elif ns_parser.source == "NYFED": + nyfed_view.plot_effr( + self.effr_parameter_to_nyfed_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_obfr(self, other_args: List[str]): + """Process obfr command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="obfr", + description="Get Overnight Bank Funding Rate data", + ) + parser.add_argument( + "-p", + "--parameter", + dest="parameter", + type=str, + help="Specific Overnight Bank Funding Rate data to retrieve", + default="rate", + choices=list(self.obfr_parameter_to_fred_id.keys()), + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default="1980-01-01", + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + if ns_parser.source == "FRED": + fred_view.plot_obfr( + self.obfr_parameter_to_fred_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + elif ns_parser.source == "NYFED": + nyfed_view.plot_obfr( + self.effr_parameter_to_nyfed_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_iorb(self, other_args: List[str]): + """Process iorb command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="iorb", + description="Get Interest Rate on Reserve Balances data", + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default=None, + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + fred_view.plot_iorb( + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_projection(self, other_args: List[str]): + """Process projection command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="projection", + description="Get FOMC Summary of Economic Projections for the Fed Funds Rate", + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default=None, + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + fred_view.plot_projection( + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_oldprojection(self, other_args: List[str]): + """Process oldprojection command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="oldprojection", + description="Get Longer Run FOMC Summary of Economic Projections for the Fed Funds Rate", + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default=None, + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + fred_view.plot_oldprojection( + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) + + @log_start_end(log=logger) + def call_dwpcr(self, other_args: List[str]): + """Process dwpcr command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="dwpcr", + description="Get Discount Window Primary Credit Rate data", + ) + parser.add_argument( + "-p", + "--parameter", + dest="parameter", + type=str, + help="Specific Discount Window Primary Credit Rate data to retrieve", + default="daily_excl_weekend", + choices=list(self.dwpcr_parameter_to_fred_id.keys()), + ) + parser.add_argument( + "-s", + "--start", + dest="start_date", + type=valid_date, + help="Starting date (YYYY-MM-DD) of data", + default=None, + ) + parser.add_argument( + "-e", + "--end", + dest="end_date", + type=valid_date, + help="Ending date (YYYY-MM-DD) of data", + default=None, + ) + ns_parser = self.parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + if ns_parser: + fred_view.plot_dwpcr( + self.dwpcr_parameter_to_fred_id[ns_parser.parameter], + ns_parser.start_date, + ns_parser.end_date, + ns_parser.export, + ) diff --git a/openbb_terminal/fixedincome/fred_model.py b/openbb_terminal/fixedincome/fred_model.py new file mode 100644 index 00000000000..9247f885bb4 --- /dev/null +++ b/openbb_terminal/fixedincome/fred_model.py @@ -0,0 +1,57 @@ +""" FRED model """ +__docformat__ = "numpy" + +import os +import logging +from typing import Optional +from requests import HTTPError + +import pandas as pd +from fredapi import Fred +import certifi + +from openbb_terminal import config_terminal as cfg +from openbb_terminal.rich_config import console +from openbb_terminal.decorators import log_start_end, check_api_key + +# pylint: disable=attribute-defined-outside-init + +logger = logging.getLogger(__name__) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def get_series_data( + series_id: str, start_date: Optional[str] = None, end_date: Optional[str] = None +) -> pd.DataFrame: + """Get Series data. [Source: FRED] + + Parameters + ---------- + series_id : str + Series ID to get data from + start_date : Optional[str] + Start date to get data from, format yyyy-mm-dd + end_date : Optional[str] + End data to get from, format yyyy-mm-dd + + Returns + ------- + pd.DataFrame + Series data + """ + df = pd.DataFrame() + + try: + # Necessary for installer so that it can locate the correct certificates for + # API calls and https + # https://stackoverflow.com/questions/27835619/urllib-and-ssl-certificate-verify-failed-error/73270162#73270162 + os.environ["REQUESTS_CA_BUNDLE"] = certifi.where() + os.environ["SSL_CERT_FILE"] = certifi.where() + fredapi_client = Fred(cfg.API_FRED_KEY) + df = fredapi_client.get_series(series_id, start_date, end_date) + # Series does not exist & invalid api keys + except HTTPError as e: + console.print(e) + + return df diff --git a/openbb_terminal/fixedincome/fred_view.py b/openbb_terminal/fixedincome/fred_view.py new file mode 100644 index 00000000000..4a6c5b5797f --- /dev/null +++ b/openbb_terminal/fixedincome/fred_view.py @@ -0,0 +1,855 @@ +""" FRED view """ +__docformat__ = "numpy" + +from typing import Optional, List +from itertools import cycle +import logging +import os + +import pandas as pd +from matplotlib import pyplot as plt + +from openbb_terminal.config_terminal import theme +from openbb_terminal.config_plot import PLOT_DPI +from openbb_terminal.decorators import log_start_end, check_api_key +from openbb_terminal.helper_funcs import ( + export_data, + plot_autoscale, + is_valid_axes_count, +) +from openbb_terminal.fixedincome.fred_model import get_series_data + +logger = logging.getLogger(__name__) + +ID_TO_NAME_ESTR = { + "ECBESTRVOLWGTTRMDMNRT": "Euro Short-Term Rate: Volume-Weighted Trimmed Mean Rate [Percent]", + "ECBESTRTOTVOL": "Euro Short-Term Rate: Total Volume [Millions of EUR]", + "ECBESTRNUMTRANS": "Euro Short-Term Rate: Number of Transactions", + "ECBESTRRT75THPCTVOL": "Euro Short-Term Rate: Rate at 75th Percentile of Volume [Percent]", + "ECBESTRNUMACTBANKS": "Euro Short-Term Rate: Number of Active Banks", + "ECBESTRSHRVOL5LRGACTBNK": "Euro Short-Term Rate: Share of Volume of the 5 Largest Active Banks [Percent]", + "ECBESTRRT25THPCTVOL": "Euro Short-Term Rate: Rate at 25th Percentile of Volume [Percent]", +} +ID_TO_NAME_SOFR = { + "SOFR": "Secured Overnight Financing Rate (SOFR) [Percent]", + "SOFR30DAYAVG": "30-Day Average SOFR [Percent]", + "SOFR90DAYAVG": "90-Day Average SOFR [Percent]", + "SOFR180DAYAVG": "180-Day Average SOFR [Percent]", + "SOFRINDEX": "SOFR Index", +} +ID_TO_NAME_SONIA = { + "IUDSOIA": "Daily Sterling Overnight Index Average (SONIA) Rate [Percent]", + "IUDZOS2": "SONIA Compounded Index", + "IUDZLS6": "SONIA Rate: 10th percentile [Percent]", + "IUDZLS7": "SONIA Rate: 25th percentile [Percent]", + "IUDZLS8": "SONIA Rate: 75th percentile [Percent]", + "IUDZLS9": "SONIA Rate: 90th percentile [Percent]", + "IUDZLT2": "SONIA Rate Total Nominal Value [Millions of GBP]", +} +ID_TO_NAME_AMERIBOR = { + "AMERIBOR": "Overnight Unsecured AMERIBOR Benchmark Interest Rate [Percent]", + "AMBOR30T": "AMERIBOR Term-30 Derived Interest Rate Index [Percent]", + "AMBOR90T": "AMERIBOR Term-90 Derived Interest Rate Index [Percent]", + "AMBOR1W": "1-Week AMERIBOR Term Structure of Interest Rates", + "AMBOR1M": "1-Month AMERIBOR Term Structure of Interest Rates", + "AMBOR3M": "3-Month AMERIBOR Term Structure of Interest Rates", + "AMBOR6M": "6-Month AMERIBOR Term Structure of Interest Rates", + "AMBOR1Y": "1-Year AMERIBOR Term Structure of Interest Rates", + "AMBOR2Y": "2-Year AMERIBOR Term Structure of Interest Rates", + "AMBOR30": "30-Day Moving Average AMERIBOR Benchmark Interest Rate", + "AMBOR90": "90-Day Moving Average AMERIBOR Benchmark Interest Rate", +} +ID_TO_NAME_FFER = { + "FEDFUNDS": "Monthly", + "DFF": "Daily (incl. Weekends)", + "FF": "Weekly", + "RIFSPFFNB": "Daily (excl. Weekends)", + "RIFSPFFNA": "Annual", + "RIFSPFFNBWAW": "Biweekly", +} +ID_TO_NAME_EFFR = { + "EFFR": "Effective Federal Funds Rate [Percent]", + "EFFRVOL": "Effective Federal Funds Volume [Billions of USD]", + "EFFR1": "Effective Federal Funds Rate: 1th Percentile [Percent]", + "EFFR25": "Effective Federal Funds Rate: 25th Percentile [Percent]", + "EFFR75": "Effective Federal Funds Rate: 75th Percentile [Percent]", + "EFFR99": "Effective Federal Funds Rate: 99th Percentile [Percent]", +} +ID_TO_NAME_OBFR = { + "OBFR": "Overnight Bank Funding Rate [Percent]", + "OBFRVOL": "Overnight Bank Funding Volume [Billions of USD]", + "OBFR1": "Overnight Bank Funding Rate: 1th Percentile [Percent]", + "OBFR25": "Overnight Bank Funding Rate: 25th Percentile [Percent]", + "OBFR75": "Overnight Bank Funding Rate: 75th Percentile [Percent]", + "OBFR99": "Overnight Bank Funding Rate: 99th Percentile [Percent]", +} +ID_TO_NAME_DWPCR = { + "MPCREDIT": "Monthly", + "RIFSRPF02ND": "Daily (incl. Weekends)", + "WPCREDIT": "Weekly", + "DPCREDIT": "Daily (excl. Weekends)", + "RIFSRPF02NA": "Annual", +} + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_estr( + series_id: str = "ECBESTRVOLWGTTRMDMNRT", + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Euro Short-Term Rate (ESTR) + + Parameters + ---------- + series_id: str + FRED ID of ESTR data to plot, options: ['ECBESTRVOLWGTTRMDMNRT', 'ECBESTRTOTVOL', 'ECBESTRNUMTRANS', 'ECBESTRRT75THPCTVOL', 'ECBESTRNUMACTBANKS', 'ECBESTRSHRVOL5LRGACTBNK', 'ECBESTRRT25THPCTVOL'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data(series_id=series_id, start_date=start_date, end_date=end_date) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title(ID_TO_NAME_ESTR[series_id]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"estr, {series_id}", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_sofr( + series_id: str = "SOFR", + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Secured Overnight Financing Rate (SOFR) + + Parameters + ---------- + series_id: str + FRED ID of SOFR data to plot, options: ['SOFR', 'SOFR30DAYAVG', 'SOFR90DAYAVG', 'SOFR180DAYAVG', 'SOFRINDEX'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data(series_id=series_id, start_date=start_date, end_date=end_date) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title(ID_TO_NAME_SOFR[series_id]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"sofr, {series_id}", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_sonia( + series_id: str = "IUDSOIA", + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Sterling Overnight Index Average (SONIA) + + Parameters + ---------- + series_id: str + FRED ID of SONIA data to plot, options: ['IUDSOIA', 'IUDZOS2', 'IUDZLS6', 'IUDZLS7', 'IUDZLS8', 'IUDZLS9', 'IUDZLT2'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data(series_id=series_id, start_date=start_date, end_date=end_date) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title(ID_TO_NAME_SONIA[series_id]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"sonia, {series_id}", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_ameribor( + series_id: str = "AMERIBOR", + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot American Interbank Offered Rate (AMERIBOR) + + Parameters + ---------- + series_id: str + FRED ID of AMERIBOR data to plot, options: ['AMERIBOR', 'AMBOR30T', 'AMBOR90T', 'AMBOR1W', 'AMBOR1M', 'AMBOR3M', 'AMBOR6M', 'AMBOR1Y', 'AMBOR2Y', 'AMBOR30', 'AMBOR90'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data(series_id=series_id, start_date=start_date, end_date=end_date) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title(ID_TO_NAME_AMERIBOR[series_id]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"ameribor, {series_id}", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_ffer( + series_id: str = "FEDFUNDS", + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Federal Funds Effective Rate + + Parameters + ---------- + series_id: str + FRED ID of FFER data to plot, options: ['FEDFUNDS', 'DFF', 'FF', 'RIFSPFFNB', 'RIFSPFFNA', 'RIFSPFFNBWAW'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data(series_id=series_id, start_date=start_date, end_date=end_date) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title( + "Federal Funds Effective Rate " + ID_TO_NAME_FFER[series_id] + " [Percent]" + ) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"ffer, {series_id}", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_fftr( + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Federal Funds Target Range + + Parameters + ---------- + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df_upper = get_series_data( + series_id="DFEDTARU", start_date=start_date, end_date=end_date + ) + df_lower = get_series_data( + series_id="DFEDTARL", start_date=start_date, end_date=end_date + ) + df = pd.DataFrame([df_upper, df_lower]).transpose() + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + ax.plot( + df.index, + df.values, + ) + ax.set_title("Federal Funds Target Range [Percent]") + ax.legend(["Upper limit", "Lower limit"]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "fftr", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_effr( + series_id: str = "EFFR", + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Effective Federal Funds Rate + + Parameters + ---------- + series_id: str + FRED ID of EFFER data to plot, options: ['EFFR', 'EFFRVOL', 'EFFR1', 'EFFR25', 'EFFR75', 'EFFR99'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data(series_id=series_id, start_date=start_date, end_date=end_date) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title(ID_TO_NAME_EFFR[series_id]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"effr, {series_id}", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_obfr( + series_id: str = "OBFR", + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Overnight Bank Funding Rate (OBFR) + + Parameters + ---------- + series_id: str + FRED ID of OBFR data to plot, options: ['OBFR', 'OBFRVOL', 'OBFR1', 'OBFR25', 'OBFR75', 'OBFR99'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data(series_id=series_id, start_date=start_date, end_date=end_date) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title(ID_TO_NAME_OBFR[series_id]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"obfr, {series_id}", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_iorb( + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Interest Rate on Reserve Balances + + Parameters + ---------- + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data(series_id="IORB", start_date=start_date, end_date=end_date) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + color=next(colors, "#FCED00"), + ) + ax.set_title("Interest Rate on Reserve Balances [Percent]") + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "iorb", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_projection( + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot FOMC Summary of Economic Projections for the Fed Funds Rate + + Parameters + ---------- + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df_range_high = get_series_data( + series_id="FEDTARRH", start_date=start_date, end_date=end_date + ) + df_central_tendency_high = get_series_data( + series_id="FEDTARCTH", start_date=start_date, end_date=end_date + ) + df_median = get_series_data( + series_id="FEDTARMD", start_date=start_date, end_date=end_date + ) + df_range_midpoint = get_series_data( + series_id="FEDTARRM", start_date=start_date, end_date=end_date + ) + df_central_tendency_midpoint = get_series_data( + series_id="FEDTARCTM", start_date=start_date, end_date=end_date + ) + df_range_low = get_series_data( + series_id="FEDTARRL", start_date=start_date, end_date=end_date + ) + df_central_tendency_low = get_series_data( + series_id="FEDTARCTL", start_date=start_date, end_date=end_date + ) + df = pd.DataFrame( + [ + df_range_high, + df_central_tendency_high, + df_median, + df_range_midpoint, + df_central_tendency_midpoint, + df_range_low, + df_central_tendency_low, + ] + ).transpose() + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + ax.plot( + df.index, + df.values, + ) + ax.set_title( + "FOMC Summary of Economic Projections for the Fed Funds Rate [Percent]" + ) + ax.legend( + [ + "Range High", + "Central tendency High", + "Median", + "Range Midpoint", + "Central tendency Midpoint", + "Range Low", + "Central tendency Low", + ] + ) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "projection", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_oldprojection( + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Longer Run FOMC Summary of Economic Projections for the Fed Funds Rate + + Parameters + ---------- + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df_range_high = get_series_data( + series_id="FEDTARRHLR", start_date=start_date, end_date=end_date + ) + df_central_tendency_high = get_series_data( + series_id="FEDTARCTHLR", start_date=start_date, end_date=end_date + ) + df_median = get_series_data( + series_id="FEDTARMDLR", start_date=start_date, end_date=end_date + ) + df_range_midpoint = get_series_data( + series_id="FEDTARRMLR", start_date=start_date, end_date=end_date + ) + df_central_tendency_midpoint = get_series_data( + series_id="FEDTARCTMLR", start_date=start_date, end_date=end_date + ) + df_range_low = get_series_data( + series_id="FEDTARRLLR", start_date=start_date, end_date=end_date + ) + df_central_tendency_low = get_series_data( + series_id="FEDTARCTLLR", start_date=start_date, end_date=end_date + ) + df = pd.DataFrame( + [ + df_range_high, + df_central_tendency_high, + df_median, + df_range_midpoint, + df_central_tendency_midpoint, + df_range_low, + df_central_tendency_low, + ] + ).transpose() + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + ax.plot( + df.index, + df.values, + ) + ax.set_title( + "Longer Run FOMC Summary of Economic Projections for the Fed Funds Rate [Percent]" + ) + ax.legend( + [ + "Range High", + "Central tendency High", + "Median", + "Range Midpoint", + "Central tendency Midpoint", + "Range Low", + "Central tendency Low", + ] + ) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "oldprojection", + df, + ) + + +@log_start_end(log=logger) +@check_api_key(["API_FRED_KEY"]) +def plot_dwpcr( + series_id: str = "DPCREDIT", + start_date: Optional[str] = None, + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Discount Window Primary Credit Rate + + Parameters + ---------- + series_id: str + FRED ID of DWPCR data to plot, options: ['MPCREDIT', 'RIFSRPF02ND', 'WPCREDIT', 'DPCREDIT', 'RIFSRPF02NA'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = get_series_data(series_id=series_id, start_date=start_date, end_date=end_date) + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title( + "Discount Window Primary Credit Rate " + + ID_TO_NAME_DWPCR[series_id] + + " [Percent]" + ) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"dwpcr, {series_id}", + df, + ) diff --git a/openbb_terminal/fixedincome/nyfed_model.py b/openbb_terminal/fixedincome/nyfed_model.py new file mode 100644 index 00000000000..5e3ac6d18d4 --- /dev/null +++ b/openbb_terminal/fixedincome/nyfed_model.py @@ -0,0 +1,133 @@ +""" NYFED model """ +__docformat__ = "numpy" + +import logging +from datetime import datetime + +import numpy +import pandas as pd +from requests import request + +from openbb_terminal.decorators import log_start_end + +logger = logging.getLogger(__name__) + + +@log_start_end(log=logger) +def get_sofr_data( + series: str = "overnight", start_date: str = "1980-01-01", end_date: str = "" +) -> pd.DataFrame: + """Get Secured Overnight Financing Rate (SOFR) data + + Parameters + ---------- + series: str + Specific data to return, options: ['overnight', '30_day', '90_day', '180_day', 'index', 'all'] + start_date: str + Start date, formatted YYYY-MM-DD + end_date: + End date, formatted YYYY-MM-DD + + Returns + ------- + pd.DataFrame + Dataframe with SOFR data + """ + if isinstance(start_date, datetime): + start_date = datetime.strftime(start_date, "%Y-%m-%d") + if series.lower() == "overnight": + data = request( + "GET", + url=f"https://markets.newyorkfed.org/api/rates/secured/sofr/search.json?startDate={start_date}&endDate={end_date}", + ).json() + df = pd.DataFrame.from_records( + data["refRates"], + index="effectiveDate", + exclude=["type", "revisionIndicator", "footnoteId"], + ) + else: + data = request( + "GET", + url=f"https://markets.newyorkfed.org/api/rates/secured/sofrai/search.json?startDate={start_date}&endDate={end_date}", + ).json() + df = pd.DataFrame.from_records( + data["refRates"], + index="effectiveDate", + exclude=["type", "revisionIndicator"], + ) + df.index = pd.to_datetime(df.index) + df = df.replace("NA", numpy.NaN) + df = df.astype(float) + if series == "index": + df = df["index"] + elif series != "overnight" and series != "all": + df = df["average" + series.replace("_", "")] + return df + + +@log_start_end(log=logger) +def get_effr_data(start_date: str = "1980-01-01", end_date: str = "") -> pd.DataFrame: + """Get Effective Federal Funds Rate (EFFR) data + + Parameters + ---------- + start_date: str + Start date, formatted YYYY-MM-DD + end_date: + End date, formatted YYYY-MM-DD + + Returns + ------- + pd.DataFrame + Dataframe with EFFR data + """ + if isinstance(start_date, datetime): + start_date = datetime.strftime(start_date, "%Y-%m-%d") + data = request( + "GET", + url=f"https://markets.newyorkfed.org/api/rates/unsecured/effr/search.json?startDate={start_date}&endDate={end_date}", + ).json() + df = pd.DataFrame.from_records( + data["refRates"], + index="effectiveDate", + exclude=["type", "revisionIndicator"], + ) + df.index = pd.to_datetime(df.index) + df = df.replace("NA", numpy.NaN) + df = df.astype(float) + + return df + + +@log_start_end(log=logger) +def get_obfr_data(start_date: str = "1980-01-01", end_date: str = "") -> pd.DataFrame: + """Get Overnight Bank Funding Rate (OBFR) data + + Parameters + ---------- + start_date: str + Start date, formatted YYYY-MM-DD + end_date: + End date, formatted YYYY-MM-DD + + Returns + ------- + pd.DataFrame + Dataframe with OBFR data + """ + if isinstance(start_date, datetime): + start_date = datetime.strftime(start_date, "%Y-%m-%d") + data = request( + "GET", + url=f"https://markets.newyorkfed.org/api/rates/unsecured/obfr/search.json?startDate={start_date}&endDate={end_date}", + ).json() + df = pd.DataFrame.from_records( + data["refRates"], + index="effectiveDate", + exclude=["type", "revisionIndicator"], + ) + df.index = pd.to_datetime(df.index) + df = df.replace("NA", numpy.NaN) + df = df.astype(float) + + return df diff --git a/openbb_terminal/fixedincome/nyfed_view.py b/openbb_terminal/fixedincome/nyfed_view.py new file mode 100644 index 00000000000..25529931574 --- /dev/null +++ b/openbb_terminal/fixedincome/nyfed_view.py @@ -0,0 +1,283 @@ +""" NYFED view """ +__docformat__ = "numpy" + +from typing import Optional, List +from itertools import cycle +import logging +import os + +from matplotlib import pyplot as plt + +from openbb_terminal.config_terminal import theme +from openbb_terminal.config_plot import PLOT_DPI +from openbb_terminal.decorators import log_start_end +from openbb_terminal.helper_funcs import ( + export_data, + plot_autoscale, + is_valid_axes_count, +) +from openbb_terminal.fixedincome import nyfed_model + +logger = logging.getLogger(__name__) + +SERIES_TO_NAME_SOFR = { + "overnight": "Secured Overnight Financing Rate (SOFR) [Percent]", + "30_day": "30-Day Average SOFR [Percent]", + "90_day": "90-Day Average SOFR [Percent]", + "180_day": "180-Day Average SOFR [Percent]", + "index": "SOFR Index", +} +SERIES_TO_NAME_EFFR = { + "percentRate": "Effective Federal Funds Rate [Percent]", + "volumeInBillions": "Effective Federal Funds Volume [Billions of USD]", + "percentPercentile1": "Effective Federal Funds Rate: 1th Percentile [Percent]", + "percentPercentile25": "Effective Federal Funds Rate: 25th Percentile [Percent]", + "percentPercentile75": "Effective Federal Funds Rate: 75th Percentile [Percent]", + "percentPercentile99": "Effective Federal Funds Rate: 99th Percentile [Percent]", +} +SERIES_TO_NAME_OBFR = { + "percentRate": "Overnight Bank Funding Rate [Percent]", + "volumeInBillions": "Overnight Bank Funding Volume [Billions of USD]", + "percentPercentile1": "Overnight Bank Funding Rate: 1th Percentile [Percent]", + "percentPercentile25": "Overnight Bank Funding Rate: 25th Percentile [Percent]", + "percentPercentile75": "Overnight Bank Funding Rate: 75th Percentile [Percent]", + "percentPercentile99": "Overnight Bank Funding Rate: 99th Percentile [Percent]", +} + + +@log_start_end(log=logger) +def plot_sofr( + series: str = "overnight", + start_date: Optional[str] = "1980-01-01", + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Secured Overnight Financing Rate (SOFR) + + Parameters + ---------- + series: str + Specific data to plot, options: ['overnight', '30_day', '90_day', '180_day', 'index'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = nyfed_model.get_sofr_data(series, start_date, end_date if end_date else "") + if series == "overnight": + del df["volumeInBillions"] + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + ) + ax.set_title(SERIES_TO_NAME_SOFR[series]) + theme.style_primary_axis(ax) + + if series.lower() == "overnight": + ax.legend( + [ + "Rate", + "1th percentile", + "25th percentile", + "75th percentile", + "99th percentile", + ] + ) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"sofr, {series}", + df, + ) + + +@log_start_end(log=logger) +def plot_fftr( + start_date: Optional[str] = "1980-01-01", + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Federal Funding Target Range (upper and lower) + + Parameters + ---------- + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = nyfed_model.get_effr_data(start_date, end_date if end_date else "") + df = df[["targetRateTo", "targetRateFrom"]] + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + ) + ax.set_title("Federal Funds Target Range [Percent]") + ax.legend(["Upper limit", "Lower limit"]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"fftr", + df, + ) + + +@log_start_end(log=logger) +def plot_effr( + series: str = "percentRate", + start_date: Optional[str] = "1980-01-01", + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Effective Federal Funds Rate + + Parameters + ---------- + series: str + Specific data to plot, options: ['percentRate', 'volumeInBillions', 'percentPercentile1', 'percentPercentile25', 'percentPercentile75', 'percentPercentile99'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = nyfed_model.get_effr_data(start_date, end_date if end_date else "") + df = df[series] + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title(SERIES_TO_NAME_EFFR[series]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"effr, {series}", + df, + ) + + +@log_start_end(log=logger) +def plot_obfr( + series: str = "percentRate", + start_date: Optional[str] = "1980-01-01", + end_date: Optional[str] = None, + export: str = "", + external_axes: Optional[List[plt.Axes]] = None, +): + """Plot Overnight Bank Funding Rate + + Parameters + ---------- + series: str + Specific data to plot, options: ['percentRate', 'volumeInBillions', 'percentPercentile1', 'percentPercentile25', 'percentPercentile75', 'percentPercentile99'] + start_date: Optional[str] + Start date, formatted YYYY-MM-DD + end_date: Optional[str] + End date, formatted YYYY-MM-DD + export: str + Export data to csv or excel file + external_axes: Optional[List[plt.Axes]] + External axes (1 axis is expected in the list) + """ + df = nyfed_model.get_obfr_data(start_date, end_date if end_date else "") + df = df[series] + + # This plot has 1 axis + if not external_axes: + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + elif is_valid_axes_count(external_axes, 1): + (ax,) = external_axes + else: + return + + colors = cycle(theme.get_colors()) + ax.plot( + df.index, + df.values, + marker="o", + linestyle="dashed", + linewidth=2, + markersize=4, + color=next(colors, "#FCED00"), + ) + ax.set_title(SERIES_TO_NAME_OBFR[series]) + theme.style_primary_axis(ax) + + if external_axes is None: + theme.visualize_output() + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + f"obfr, {series}", + df, + ) diff --git a/openbb_terminal/miscellaneous/data_sources_default.json b/openbb_terminal/miscellaneous/data_sources_default.json index f63c9e83673..7fa4c4a82d3 100644 --- a/openbb_terminal/miscellaneous/data_sources_default.json +++ b/openbb_terminal/miscellaneous/data_sources_default.json @@ -504,6 +504,20 @@ "historical": ["YahooFinance"], "curve": ["YahooFinance"] }, + "fixedincome": { + "estr": ["FRED", "ECB"], + "sofr": ["FRED", "NYFED"], + "sonia": ["FRED"], + "ameribor": ["FRED"], + "ffer": ["FRED"], + "fftr": ["FRED", "NYFED"], + "effr": ["FRED", "NYFED"], + "obfr": ["FRED", "NYFED"], + "iorb": ["FRED"], + "projection": ["FRED"], + "oldprojetion": ["FRED"], + "dwpcr": ["FRED"] + }, "alternative": { "oss": { "rossidx": ["RunaCapital"], diff --git a/openbb_terminal/miscellaneous/i18n/en.yml b/openbb_terminal/miscellaneous/i18n/en.yml index d6c7947fd3b..3b9aac2eea1 100644 --- a/openbb_terminal/miscellaneous/i18n/en.yml +++ b/openbb_terminal/miscellaneous/i18n/en.yml @@ -771,6 +771,20 @@ en: economy/qa/raw: print raw data economy/qa/decompose: decomposition in cyclic-trend, season, and residuals of prices economy/qa/cusum: detects abrupt changes using cumulative sum algorithm of prices + fixedincome/_reference_rates_: Reference rates + fixedincome/estr: euro short-term rate (estr) + fixedincome/sofr: secured overnight financing rate (sofr) + fixedincome/sonia: sterling overnight index average (sonia) + fixedincome/ameribor: american interbank offered rate (ameribor) + fixedincome/_other_interest_rates_: Other interest rates + fixedincome/ffer: federal funds effective rate + fixedincome/fftr: federal funds target range + fixedincome/effr: effective federal funds rate (effr) + fixedincome/obfr: overnight bank funding rate (obfr) + fixedincome/iorb: interest rate on reserve balances + fixedincome/projection: FOMC summary of economic projections for the fed funds rate + fixedincome/oldprojection: longer run FOMC summary of economic projections for the fed funds rate + fixedincome/dwpcr: discount window primary credit rate forex/_ticker: Ticker forex/_from: From forex/_to: To diff --git a/openbb_terminal/terminal_controller.py b/openbb_terminal/terminal_controller.py index 75e15dc479f..12841acf8a0 100644 --- a/openbb_terminal/terminal_controller.py +++ b/openbb_terminal/terminal_controller.py @@ -103,6 +103,7 @@ class TerminalController(BaseController): "sources", "forecast", "futures", + "fixedincome", ] PATH = "/" @@ -178,6 +179,7 @@ class TerminalController(BaseController): mt.add_menu("economy") mt.add_menu("forex") mt.add_menu("futures") + mt.add_menu("fixedincome") mt.add_menu("alternative") mt.add_raw("\n") mt.add_info("_others_") @@ -465,6 +467,14 @@ class TerminalController(BaseController): self.queue = self.load_class(FuturesController, self.queue) + def call_fixedincome(self, _): + """Process fixedincome command.""" + from openbb_terminal.fixedincome.fixedincome_controller import ( + FixedIncomeController, + ) + + self.queue = self.load_class(FixedIncomeController, self.queue) + def call_intro(self, _): """Process intro command.""" console.print(panel.Panel("[purple]Welcome to the OpenBB Terminal.[/purple]"))