Skip to content

Commit

Permalink
Merge branch 'develop' into feature/fix-4223
Browse files Browse the repository at this point in the history
  • Loading branch information
jmaslek authored Feb 24, 2023
2 parents e43c549 + eea170b commit 416f556
Show file tree
Hide file tree
Showing 25 changed files with 1,266 additions and 642 deletions.
3 changes: 3 additions & 0 deletions openbb_terminal/config_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,6 @@

# https://intrinio.com/starter-plan
API_INTRINIO_KEY = os.getenv("OPENBB_API_INTRINIO_KEY") or "REPLACE_ME"

# https://databento.com/
API_DATABENTO_KEY = os.getenv("OPENBB_API_DATABENTO_KEY") or "REPLACE_ME"
118 changes: 118 additions & 0 deletions openbb_terminal/futures/databento_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""DataBento view"""
__docformat__ = "numpy"

import os
from typing import List, Optional

import mplfinance as mpf
from matplotlib import pyplot as plt

from openbb_terminal.config_terminal import theme
from openbb_terminal.helper_funcs import (
export_data,
is_valid_axes_count,
lambda_long_number_format_y_axis,
plot_autoscale,
print_rich_table,
)
from openbb_terminal.rich_config import console
from openbb_terminal.stocks import databento_model


def display_historical(
symbol: str,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
raw: bool = False,
export: str = "",
sheet_name: Optional[str] = None,
external_axes: Optional[List[plt.Axes]] = None,
):
"""Display historical futures [Source: DataBento]
Parameters
----------
symbol: List[str]
Symbol to display
start_date: Optional[str]
Start date of the historical data with format YYYY-MM-DD
end_date: Optional[str]
End date of the historical data with format YYYY-MM-DD
raw: bool
Display futures timeseries in raw format
sheet_name: str
Optionally specify the name of the sheet the data is exported to.
export: str
Type of format to export data
external_axes : Optional[List[plt.Axes]], optional
External axes (1 axis is expected in the list), by default None
"""
data = databento_model.get_historical_futures(
symbol.upper(), start_date=start_date, end_date=end_date
)
if data.empty:
console.print(f"No data found for {symbol}.")
return
# We check if there's Volume data to avoid errors and empty subplots
has_volume = False
if "Volume" in data.columns:
has_volume = bool(data["Volume"].sum() > 0)

candle_chart_kwargs = {
"type": "candle",
"style": theme.mpf_style,
"volume": has_volume,
"addplot": [],
"xrotation": theme.xticks_rotation,
"scale_padding": {"left": 0.3, "right": 1, "top": 0.8, "bottom": 0.8},
"update_width_config": {
"candle_linewidth": 0.6,
"candle_width": 0.8,
"volume_linewidth": 0.8,
"volume_width": 0.8,
},
"warn_too_much_data": 10000,
}
if external_axes is None:
candle_chart_kwargs["returnfig"] = True
candle_chart_kwargs["figratio"] = (10, 7)
candle_chart_kwargs["figscale"] = 1.10
candle_chart_kwargs["figsize"] = plot_autoscale()
candle_chart_kwargs["warn_too_much_data"] = 100_000

fig, ax = mpf.plot(data, **candle_chart_kwargs)

if has_volume:
lambda_long_number_format_y_axis(data, "Volume", ax)

fig.suptitle(
f"{symbol}",
x=0.055,
y=0.965,
horizontalalignment="left",
)
theme.visualize_output(force_tight_layout=False)
elif (has_volume and is_valid_axes_count(external_axes, 2)) or (
not has_volume and is_valid_axes_count(external_axes, 1)
):
candle_chart_kwargs["ax"] = external_axes[0]
if has_volume:
candle_chart_kwargs["volume"] = external_axes[1]
mpf.plot(data, **candle_chart_kwargs)

if raw:
print_rich_table(
data,
headers=list(data.columns),
show_index=True,
title="Futures timeseries",
)
console.print()

export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"historical_db",
data,
sheet_name,
)
40 changes: 26 additions & 14 deletions openbb_terminal/futures/futures_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from openbb_terminal import feature_flags as obbff
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
from openbb_terminal.decorators import log_start_end
from openbb_terminal.futures import yfinance_model, yfinance_view
from openbb_terminal.futures import databento_view, yfinance_model, yfinance_view
from openbb_terminal.helper_funcs import (
EXPORT_BOTH_RAW_DATA_AND_FIGURES,
EXPORT_ONLY_RAW_DATA_ALLOWED,
Expand Down Expand Up @@ -165,8 +165,8 @@ def call_historical(self, other_args: List[str]):
"--start",
dest="start",
type=valid_date,
help="Initial date. Default: 3 years ago",
default=(datetime.now() - timedelta(days=3 * 365)),
help="Initial date. Default: 2 years ago",
default=(datetime.now() - timedelta(days=2 * 365)),
)
parser.add_argument(
"-e",
Expand All @@ -193,17 +193,29 @@ def call_historical(self, other_args: List[str]):
raw=True,
)
if ns_parser:
yfinance_view.display_historical(
symbols=ns_parser.ticker.upper().split(","),
expiry=ns_parser.expiry,
start_date=ns_parser.start.strftime("%Y-%m-%d"),
end_date=ns_parser.end.strftime("%Y-%m-%d"),
raw=ns_parser.raw,
export=ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
if ns_parser.source == "YahooFinance":
yfinance_view.display_historical(
symbols=ns_parser.ticker.upper().split(","),
expiry=ns_parser.expiry,
start_date=ns_parser.start.strftime("%Y-%m-%d"),
end_date=ns_parser.end.strftime("%Y-%m-%d"),
raw=ns_parser.raw,
export=ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
if ns_parser.source == "DataBento":
databento_view.display_historical(
symbol=ns_parser.ticker,
start_date=ns_parser.start.strftime("%Y-%m-%d"),
end_date=ns_parser.end.strftime("%Y-%m-%d"),
raw=ns_parser.raw,
export=ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)

@log_start_end(log=logger)
def call_curve(self, other_args: List[str]):
Expand Down
31 changes: 31 additions & 0 deletions openbb_terminal/futures/sdk_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""SDK Helper Functions"""
__docformat__ = "numpy"

from typing import List, Optional, Union

import pandas as pd

from openbb_terminal.futures import yfinance_model
from openbb_terminal.stocks import databento_model


def get_historical(
symbols: Union[str, List[str]],
start_date: str,
end_date: str,
source: Optional[str] = "YahooFinance",
expiry: Optional[str] = "",
) -> pd.DataFrame:
"""Get historical futures data"""
if source == "YahooFinance":
if isinstance(symbols, str):
symbols = [symbols]
return yfinance_model.get_historical_futures(
symbols, expiry, start_date, end_date
)
if source == "DataBento":
if isinstance(symbols, list):
print("DataBento only supports one symbol at a time. Using first symbol.")
symbols = symbols[0]
return databento_model.get_historical_futures(symbols, start_date, end_date)
return pd.DataFrame()
2 changes: 1 addition & 1 deletion openbb_terminal/futures/yfinance_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def get_historical_futures(
Returns
-------
pd.DataFrame
Dictionary with sector weightings allocation
Historical futures data
"""

if start_date is None:
Expand Down
19 changes: 9 additions & 10 deletions openbb_terminal/helper_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@
config_terminal as cfg,
feature_flags as obbff,
)
from openbb_terminal.core.config.paths import (
HOME_DIRECTORY,
USER_EXPORTS_DIRECTORY,
)
from openbb_terminal.core.config.paths import HOME_DIRECTORY, USER_EXPORTS_DIRECTORY
from openbb_terminal.rich_config import console

try:
Expand Down Expand Up @@ -1967,7 +1964,9 @@ def check_start_less_than_end(start_date: str, end_date: str) -> bool:


# Write an abstract helper to make requests from a url with potential headers and params
def request(url: str, method="GET", **kwargs) -> requests.Response:
def request(
url: str, method: str = "GET", timeout: int = 0, **kwargs
) -> requests.Response:
"""Abstract helper to make requests from a url with potential headers and params.
Parameters
Expand All @@ -1990,15 +1989,15 @@ def request(url: str, method="GET", **kwargs) -> requests.Response:
# We want to add a user agent to the request, so check if there are any headers
# If there are headers, check if there is a user agent, if not add one.
# Some requests seem to work only with a specific user agent, so we want to be able to override it.
headers = kwargs.pop("headers") if "headers" in kwargs else {}
headers = kwargs.pop("headers", {})
timeout = timeout or cfg.REQUEST_TIMEOUT

if "User-Agent" not in headers:
headers["User-Agent"] = get_user_agent()
if method.upper() == "GET":
return requests.get(url, headers=headers, timeout=cfg.REQUEST_TIMEOUT, **kwargs)
return requests.get(url, headers=headers, timeout=timeout, **kwargs)
if method.upper() == "POST":
return requests.post(
url, headers=headers, timeout=cfg.REQUEST_TIMEOUT, **kwargs
)
return requests.post(url, headers=headers, timeout=timeout, **kwargs)
raise ValueError("Method must be GET or POST")


Expand Down
28 changes: 28 additions & 0 deletions openbb_terminal/keys_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,34 @@ def call_stocksera(self, other_args: List[str]):
key=ns_parser.key, persist=True, show_output=True
)

@log_start_end(log=logger)
def call_databento(self, other_args: List[str]):
"""Process databento command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="databento",
description="Set DataBento API key.",
)
parser.add_argument(
"-k",
"--key",
type=str,
dest="key",
help="key",
)
if not other_args:
console.print("For your API Key, https://databento.com")
return

if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-k")
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
self.status_dict["databento"] = keys_model.set_databento_key(
key=ns_parser.key, persist=True, show_output=True
)

@log_start_end(log=logger)
def call_openbb(self, other_args: List[str]):
"""Process openbb command"""
Expand Down
72 changes: 71 additions & 1 deletion openbb_terminal/keys_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"quandl": "QUANDL",
"polygon": "POLYGON",
"intrinio": "INTRINIO",
"databento": "DATABENTO",
"fred": "FRED",
"news": "NEWSAPI",
"tradier": "TRADIER",
Expand Down Expand Up @@ -2592,7 +2593,7 @@ def set_intrinio_key(key: str, persist: bool = False, show_output: bool = False)


def check_intrinio_key(show_output: bool = False) -> str:
"""Check Polygon key
"""Check Intrinio key
Parameters
----------
Expand Down Expand Up @@ -2626,3 +2627,72 @@ def check_intrinio_key(show_output: bool = False) -> str:
console.print(status.colorize())

return str(status)


def set_databento_key(
key: str, persist: bool = False, show_output: bool = False
) -> str:
"""Set DataBento key
Parameters
----------
key: str
API key
persist: bool, optional
If False, api key change will be contained to where it was changed. For example, a Jupyter notebook session.
If True, api key change will be global, i.e. it will affect terminal environment variables.
By default, False.
show_output: bool, optional
Display status string or not. By default, False.
Returns
-------
str
Status of key set
Examples
--------
>>> from openbb_terminal.sdk import openbb
>>> openbb.keys.databento(key="example_key")
"""

set_key("OPENBB_API_DATABENTO_KEY", key, persist)
return check_databento_key(show_output)


def check_databento_key(show_output: bool = False) -> str:
"""Check DataBento key
Parameters
----------
show_output: bool
Display status string or not. By default, False.
Returns
-------
str
Status of key set
"""

if cfg.API_DATABENTO_KEY == "REPLACE_ME":
logger.info("DataBento key not defined")
status = KeyStatus.NOT_DEFINED
else:
r = request(
"https://hist.databento.com/v0/metadata.list_datasets",
auth=(f"{cfg.API_DATABENTO_KEY}", ""),
)
if r.status_code in [403, 401, 429]:
logger.warning("DataBento key defined, test failed")
status = KeyStatus.DEFINED_TEST_FAILED
elif r.status_code == 200:
logger.info("DataBento key defined, test passed")
status = KeyStatus.DEFINED_TEST_PASSED
else:
logger.warning("DataBento key defined, test inconclusive")
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE

if show_output:
console.print(status.colorize())

return str(status)
Loading

0 comments on commit 416f556

Please sign in to comment.