mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-01 06:51:38 +00:00
* multi-ticker support for yfinance futures historical * raise-missing-from * integration test params * equity_historical adjusted kwargs * pylint unused argument
192 lines
5.7 KiB
Python
192 lines
5.7 KiB
Python
"""Yahoo Finance helpers module."""
|
|
# pylint: disable=unused-argument
|
|
from datetime import (
|
|
date as dateType,
|
|
datetime,
|
|
)
|
|
from pathlib import Path
|
|
from typing import Any, Literal, Optional, Union
|
|
|
|
import pandas as pd
|
|
import yfinance as yf
|
|
from dateutil.relativedelta import relativedelta
|
|
from openbb_core.provider.utils.errors import EmptyDataError
|
|
from openbb_yfinance.utils.references import MONTHS
|
|
|
|
|
|
def get_futures_data() -> pd.DataFrame:
|
|
"""Return the dataframe of the futures csv file."""
|
|
return pd.read_csv(Path(__file__).resolve().parent / "futures.csv")
|
|
|
|
|
|
def get_futures_curve(symbol: str, date: Optional[dateType]) -> pd.DataFrame:
|
|
"""Get the futures curve for a given symbol.
|
|
|
|
Parameters
|
|
----------
|
|
symbol: str
|
|
Symbol to get futures for
|
|
date: Optional[str]
|
|
Optional historical date to get curve for
|
|
|
|
Returns
|
|
-------
|
|
pd.DataFrame
|
|
DataFrame with futures curve
|
|
"""
|
|
futures_data = get_futures_data()
|
|
try:
|
|
exchange = futures_data[futures_data["Ticker"] == symbol]["Exchange"].values[0]
|
|
except IndexError:
|
|
return pd.DataFrame({"Last Price": [], "expiration": []})
|
|
|
|
today = datetime.today()
|
|
futures_index = []
|
|
futures_curve = []
|
|
historical_curve = []
|
|
i = 0
|
|
empty_count = 0
|
|
# Loop through until we find 12 consecutive empty months
|
|
while empty_count < 12:
|
|
future = today + relativedelta(months=i)
|
|
future_symbol = (
|
|
f"{symbol}{MONTHS[future.month]}{str(future.year)[-2:]}.{exchange}"
|
|
)
|
|
data = yf.download(future_symbol, progress=False, ignore_tz=True, threads=False)
|
|
|
|
if data.empty:
|
|
empty_count += 1
|
|
|
|
else:
|
|
empty_count = 0
|
|
futures_index.append(future.strftime("%b-%Y"))
|
|
futures_curve.append(data["Adj Close"].values[-1])
|
|
if date is not None:
|
|
historical_curve.append(
|
|
data["Adj Close"].get(date.strftime("%Y-%m-%d"), None)
|
|
)
|
|
|
|
i += 1
|
|
|
|
if not futures_index:
|
|
return pd.DataFrame({"date": [], "Last Price": []})
|
|
|
|
if historical_curve:
|
|
return pd.DataFrame(
|
|
{"Last Price": historical_curve, "expiration": futures_index}
|
|
)
|
|
return pd.DataFrame({"Last Price": futures_curve, "expiration": futures_index})
|
|
|
|
|
|
# pylint: disable=too-many-arguments,unused-argument
|
|
def yf_download(
|
|
symbol: str,
|
|
start_date: Optional[Union[str, dateType]] = None,
|
|
end_date: Optional[Union[str, dateType]] = None,
|
|
interval: str = "1d",
|
|
period: str = "max",
|
|
prepost: bool = False,
|
|
actions: bool = False,
|
|
progress: bool = False,
|
|
ignore_tz: bool = True,
|
|
keepna: bool = False,
|
|
repair: bool = False,
|
|
rounding: bool = False,
|
|
group_by: Literal["symbol", "column"] = "symbol",
|
|
adjusted: bool = False,
|
|
**kwargs: Any,
|
|
) -> pd.DataFrame:
|
|
"""Get yFinance OHLC data for any ticker and interval available."""
|
|
symbol = symbol.upper()
|
|
_start_date = start_date
|
|
|
|
if interval in ["60m", "1h"]:
|
|
period = "2y" if period in ["5y", "10y", "max"] else period
|
|
_start_date = None
|
|
|
|
if interval in ["2m", "5m", "15m", "30m", "90m"]:
|
|
_start_date = (datetime.now().date() - relativedelta(days=58)).strftime(
|
|
"%Y-%m-%d"
|
|
)
|
|
|
|
if interval == "1m":
|
|
period = "5d"
|
|
_start_date = None
|
|
|
|
if adjusted is False:
|
|
kwargs = dict(auto_adjust=False, back_adjust=False)
|
|
|
|
try:
|
|
data = yf.download(
|
|
tickers=symbol,
|
|
start=_start_date,
|
|
end=None,
|
|
interval=interval,
|
|
period=period,
|
|
prepost=prepost,
|
|
actions=actions,
|
|
progress=progress,
|
|
ignore_tz=ignore_tz,
|
|
keepna=keepna,
|
|
repair=repair,
|
|
rounding=rounding,
|
|
group_by=group_by,
|
|
threads=False,
|
|
**kwargs,
|
|
)
|
|
except ValueError as exc:
|
|
raise EmptyDataError() from exc
|
|
|
|
tickers = symbol.split(",")
|
|
if len(tickers) > 1:
|
|
_data = pd.DataFrame()
|
|
for ticker in tickers:
|
|
temp = data[ticker].copy().dropna(how="all")
|
|
for i in temp.index:
|
|
temp.loc[i, "symbol"] = ticker
|
|
temp = temp.reset_index().rename(
|
|
columns={"Date": "date", "Datetime": "date"}
|
|
)
|
|
_data = pd.concat([_data, temp])
|
|
index_keys = ["date", "symbol"] if "symbol" in _data.columns else ["date"]
|
|
_data = _data.set_index(index_keys).sort_index()
|
|
data = _data
|
|
if not data.empty:
|
|
data = data.reset_index()
|
|
data = data.rename(columns={"Date": "date", "Datetime": "date"})
|
|
data["date"] = pd.to_datetime(data["date"])
|
|
data = data[data["Open"] > 0]
|
|
|
|
if start_date is not None:
|
|
data = data[data["date"] >= pd.to_datetime(start_date)]
|
|
if end_date is not None and pd.to_datetime(end_date) > pd.to_datetime(
|
|
start_date
|
|
):
|
|
data = data[
|
|
data["date"] <= (pd.to_datetime(end_date) + relativedelta(days=1))
|
|
]
|
|
|
|
if period not in [
|
|
"max",
|
|
"1d",
|
|
"5d",
|
|
"1wk",
|
|
"1mo",
|
|
"3mo",
|
|
"6mo",
|
|
"1y",
|
|
"2y",
|
|
"5y",
|
|
"10y",
|
|
]:
|
|
data["date"] = data["date"].dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
if interval not in ["1m", "2m", "5m", "15m", "30m", "90m", "60m", "1h"]:
|
|
data["date"] = data["date"].dt.strftime("%Y-%m-%d")
|
|
|
|
if adjusted is False:
|
|
data = data.drop(columns=["Adj Close"])
|
|
|
|
data.columns = data.columns.str.lower().str.replace(" ", "_").to_list()
|
|
|
|
return data
|