Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hotfix/yf-futures-historical: yfinance historical futures multi-ticker support and symbol parsing #5923

Merged
merged 5 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,27 +60,14 @@ def test_derivatives_options_unusual(params, headers):
@parametrize(
"params",
[
(
{
"provider": "yfinance",
"symbol": "ES",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"expiration": "2024-06",
}
),
(
{
"provider": "yfinance",
"interval": "1d",
"period": "max",
"prepost": True,
"adjust": True,
"back_adjust": True,
"symbol": "ES",
"symbol": "CL,BZ",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"expiration": "2024-06",
"expiration": "2025-12",
}
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,14 @@ def test_derivatives_options_unusual(params, obb):
@parametrize(
"params",
[
(
{
"symbol": "ES",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"expiration": "2024-06",
}
),
(
{
"provider": "yfinance",
"interval": "1d",
"period": "max",
"prepost": True,
"adjust": True,
"back_adjust": True,
"symbol": "ES",
"start_date": "2023-05-05",
"symbol": "CL,BZ",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"expiration": "2024-06",
"expiration": "2025-12",
}
),
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Yahoo Finance Equity Historical Price Model."""
# pylint: disable=unused-argument
# ruff: noqa: SIM105

from datetime import datetime, timedelta
Expand Down Expand Up @@ -112,7 +113,9 @@ def extract_data(
query.interval = "1mo"
elif query.interval == "3M":
query.interval = "3mo"

kwargs = (
{"auto_adjust": True, "back_adjust": True} if query.adjusted is True else {}
)
# pylint: disable=protected-access
data = yf_download(
symbol=query.symbol,
Expand All @@ -129,6 +132,7 @@ def extract_data(
rounding=query._rounding,
group_by=query._group_by,
adjusted=query.adjusted,
**kwargs,
)

if data.empty:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Yahoo Finance Futures Historical Price Model."""
# pylint: disable=unused-argument
# ruff: noqa: SIM105


from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional

Expand All @@ -13,11 +13,10 @@
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_yfinance.utils.helpers import get_futures_data
from openbb_yfinance.utils.helpers import get_futures_data, yf_download
from openbb_yfinance.utils.references import INTERVALS, MONTHS, PERIODS
from pandas import Timestamp, to_datetime
from pydantic import Field, field_validator
from yfinance import Ticker


class YFinanceFuturesHistoricalQueryParams(FuturesHistoricalQueryParams):
Expand All @@ -30,13 +29,6 @@ class YFinanceFuturesHistoricalQueryParams(FuturesHistoricalQueryParams):
period: Optional[PERIODS] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("period", "")
)
prepost: bool = Field(
default=False, description="Include Pre and Post market data."
)
adjust: bool = Field(default=True, description="Adjust all the data automatically.")
back_adjust: bool = Field(
default=False, description="Back-adjusted data to mimic true historical prices."
)


class YFinanceFuturesHistoricalData(FuturesHistoricalData):
Expand All @@ -62,94 +54,88 @@ class YFinanceFuturesHistoricalFetcher(
@staticmethod
def transform_query(params: Dict[str, Any]) -> YFinanceFuturesHistoricalQueryParams:
"""Transform the query. Setting the start and end dates for a 1 year period."""
if params.get("period") is None:
transformed_params = params

now = datetime.now().date()
if params.get("start_date") is None:
transformed_params["start_date"] = now - relativedelta(years=1)

if params.get("end_date") is None:
transformed_params["end_date"] = now
transformed_params = params.copy()

symbols = params["symbol"].split(",")
new_symbols = []
futures_data = get_futures_data()
for symbol in symbols:
if params.get("expiration"):
expiry_date = datetime.strptime(
transformed_params["expiration"], "%Y-%m"
)
if "." not in symbol:
exchange = futures_data[futures_data["Ticker"] == symbol][
"Exchange"
].values[0]
new_symbol = (
f"{symbol}{MONTHS[expiry_date.month]}{str(expiry_date.year)[-2:]}.{exchange}"
if "." not in symbol
else symbol
)
new_symbols.append(new_symbol)
else:
new_symbols.append(symbol)
formatted_symbols = [
f"{s.upper()}=F" if "." not in s.upper() else s.upper() for s in new_symbols
]
transformed_params["symbol"] = ",".join(formatted_symbols)

now = datetime.now()

if params.get("start_date") is None:
transformed_params["start_date"] = (now - relativedelta(years=1)).strftime(
"%Y-%m-%d"
)

else:
transformed_params = params
if params.get("end_date") is None:
transformed_params["end_date"] = now.strftime("%Y-%m-%d")

return YFinanceFuturesHistoricalQueryParams(**transformed_params)

@staticmethod
def extract_data(
query: YFinanceFuturesHistoricalQueryParams, # pylint: disable=unused-argument
query: YFinanceFuturesHistoricalQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> dict:
) -> List[Dict]:
"""Return the raw data from the Yahoo Finance endpoint."""
symbol = ""

if query.expiration:
expiry_date = datetime.strptime(query.expiration, "%Y-%m")
futures_data = get_futures_data()
exchange = futures_data[futures_data["Ticker"] == query.symbol][
"Exchange"
].values[0]
symbol = f"{query.symbol}{MONTHS[expiry_date.month]}{str(expiry_date.year)[-2:]}.{exchange}"

query_symbol = symbol if symbol else f"{query.symbol}=F"

if query.period:
data = Ticker(query_symbol).history(
interval=query.interval,
period=query.period,
prepost=query.prepost,
auto_adjust=query.adjust,
back_adjust=query.back_adjust,
actions=False,
raise_errors=True,
)
else:
data = Ticker(query_symbol).history(
interval=query.interval,
start=query.start_date,
end=query.end_date,
prepost=query.prepost,
auto_adjust=query.adjust,
back_adjust=query.back_adjust,
actions=False,
raise_errors=True,
)
data = yf_download(
query.symbol,
start=query.start_date,
end=query.end_date,
interval=query.interval, # type: ignore
prepost=True,
auto_adjust=False,
actions=False,
)

if data.empty:
raise EmptyDataError()

query.end_date = (
datetime.now().date() if query.end_date is None else query.end_date
)
days = (
1
if query.interval in ["1m", "2m", "5m", "15m", "30m", "60m", "1h", "90m"]
else 0
)
if "date" in data.columns:
data.set_index("date", inplace=True)
data.index = to_datetime(data.index)
if query.start_date:
data.index = to_datetime(data.index).tz_convert(None)

start_date_dt = datetime.combine(query.start_date, datetime.min.time())
end_date_dt = datetime.combine(query.end_date, datetime.min.time())

data = data[
(data.index >= start_date_dt + timedelta(days=days))
& (data.index <= end_date_dt)
(data.index >= to_datetime(query.start_date))
& (data.index <= to_datetime(query.end_date + timedelta(days=days)))
]

data = data.reset_index().rename(
columns={"index": "Date", "Datetime": "Date"}, errors="ignore"
)
data.columns = data.columns.str.lower()
data.reset_index(inplace=True)
data.rename(columns={"index": "date"}, inplace=True)

return data.to_dict("records")

@staticmethod
def transform_data(
query: YFinanceFuturesHistoricalQueryParams, # pylint: disable=unused-argument
data: dict,
query: YFinanceFuturesHistoricalQueryParams,
data: List[Dict],
**kwargs: Any,
) -> List[YFinanceFuturesHistoricalData]:
"""Transform the data to the standard format."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ SF,Sugar No. 16 Futures,NYB,agriculture
SB,Sugar No. 11 Futures,NYB,agriculture
OJ,Orange Juice Futures,NYB,agriculture
NG,Natural Gas Futures,NYM,hydrocarbon
HO,Heating Oil Futures,NYM,hydrocarbon
RB,RBOB Gasoline Futures,NYM,hydrocarbon
CL,WTI Crude Oil Futures,NYM,hydrocarbon
TRM,ICE TRY/USD Futures,NYB,currency
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Yahoo Finance helpers module."""

# pylint: disable=unused-argument
from datetime import (
date as dateType,
datetime,
Expand Down Expand Up @@ -134,8 +134,8 @@ def yf_download(
threads=False,
**kwargs,
)
except ValueError:
raise EmptyDataError()
except ValueError as exc:
raise EmptyDataError() from exc

tickers = symbol.split(",")
if len(tickers) > 1:
Expand All @@ -148,7 +148,8 @@ def yf_download(
columns={"Date": "date", "Datetime": "date"}
)
_data = pd.concat([_data, temp])
_data = _data.set_index(["date", "symbol"]).sort_index()
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()
Expand Down
Loading