From c1928f768f4a0280d8afaaf6b9fee15543a27be6 Mon Sep 17 00:00:00 2001 From: James Maslek Date: Mon, 14 Aug 2023 16:13:18 -0400 Subject: [PATCH] Edit futures eod and add futures curve --- .../futures/openbb_futures/futures_router.py | 11 ++++ .../yfinance/openbb_yfinance/__init__.py | 4 +- .../openbb_yfinance/models/futures_eod.py | 18 ++++-- .../yfinance/openbb_yfinance/utils/helpers.py | 61 +++++++++++++++++++ .../app/static/package/module_map.json | 1 + .../standard_models/futures_eod.py | 16 +++-- 6 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 openbb_sdk/providers/yfinance/openbb_yfinance/utils/helpers.py diff --git a/openbb_sdk/extensions/futures/openbb_futures/futures_router.py b/openbb_sdk/extensions/futures/openbb_futures/futures_router.py index 237f80c5982..2896377d8ac 100644 --- a/openbb_sdk/extensions/futures/openbb_futures/futures_router.py +++ b/openbb_sdk/extensions/futures/openbb_futures/futures_router.py @@ -22,3 +22,14 @@ def load( ) -> OBBject[BaseModel]: """Futures EOD Price.""" return OBBject(results=Query(**locals()).execute()) + + +@router.command(model="FuturesCurve") +def curve( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject[BaseModel]: + """Futures EOD Price.""" + return OBBject(results=Query(**locals()).execute()) \ No newline at end of file diff --git a/openbb_sdk/providers/yfinance/openbb_yfinance/__init__.py b/openbb_sdk/providers/yfinance/openbb_yfinance/__init__.py index 9b272c888bb..23cba1550be 100644 --- a/openbb_sdk/providers/yfinance/openbb_yfinance/__init__.py +++ b/openbb_sdk/providers/yfinance/openbb_yfinance/__init__.py @@ -6,6 +6,7 @@ from openbb_yfinance.models.forex_eod import YFinanceForexEODFetcher from openbb_yfinance.models.major_indices_eod import YFinanceMajorIndicesEODFetcher from openbb_yfinance.models.stock_eod import YFinanceStockEODFetcher from openbb_yfinance.models.futures_eod import YFinanceFuturesEODFetcher +from openbb_yfinance.models.futures_curve import YFinanceFuturesCurveFetcher yfinance_provider = Provider( name="yfinance", @@ -19,6 +20,7 @@ yfinance_provider = Provider( "ForexEOD": YFinanceForexEODFetcher, "MajorIndicesEOD": YFinanceMajorIndicesEODFetcher, "StockEOD": YFinanceStockEODFetcher, - "FuturesEOD": YFinanceFuturesEODFetcher + "FuturesEOD": YFinanceFuturesEODFetcher, + "FuturesCurve": YFinanceFuturesCurveFetcher }, ) diff --git a/openbb_sdk/providers/yfinance/openbb_yfinance/models/futures_eod.py b/openbb_sdk/providers/yfinance/openbb_yfinance/models/futures_eod.py index b497df47e0d..8dcafbb1ac0 100644 --- a/openbb_sdk/providers/yfinance/openbb_yfinance/models/futures_eod.py +++ b/openbb_sdk/providers/yfinance/openbb_yfinance/models/futures_eod.py @@ -14,7 +14,7 @@ from pydantic import Field, validator from yfinance import Ticker from openbb_yfinance.utils.types import INTERVALS, PERIODS - +from openbb_yfinance.utils.futures_reference import futures_data, MONTHS class YFinanceFuturesEODQueryParams(FuturesEODQueryParams): """YFinance Futures end of day Query. @@ -28,6 +28,7 @@ class YFinanceFuturesEODQueryParams(FuturesEODQueryParams): ) + class YFinanceFuturesEODData(FuturesEODData): """YFinance Futures end of day Data.""" @@ -70,18 +71,27 @@ class YFinanceFuturesEODFetcher( **kwargs: Any, ) -> List[YFinanceFuturesEODData]: now = datetime.now().date() - query.start_date = query.start_date or (now - timedelta(days=8)) + query.start_date = query.start_date or (now - timedelta(days=365)) query.end_date = query.end_date or (now - timedelta(days=1)) + symbol = "" + if query.expiration: + expiry_date = datetime.strptime(query.expiration, "%Y-%m") + 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 query.symbol+"=F" if query.period: - data = Ticker(query.symbol+"=F").history( + data = Ticker(query_symbol).history( interval=query.interval, period=query.period, actions=False, raise_errors=True, ) else: - data = Ticker(query.symbol+"=F").history( + data = Ticker(query_symbol).history( interval=query.interval, start=query.start_date, end=query.end_date, diff --git a/openbb_sdk/providers/yfinance/openbb_yfinance/utils/helpers.py b/openbb_sdk/providers/yfinance/openbb_yfinance/utils/helpers.py new file mode 100644 index 00000000000..a10142a7f61 --- /dev/null +++ b/openbb_sdk/providers/yfinance/openbb_yfinance/utils/helpers.py @@ -0,0 +1,61 @@ +import pandas as pd +import yfinance as yf +from typing import Optional +from datetime import datetime +from openbb_yfinance.utils.futures_reference import futures_data, MONTHS +from dateutil.relativedelta import relativedelta + + +def get_futures_curve(symbol: str, date: Optional[str]) -> pd.DataFrame: + """Helper to get the futures curve + + 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 + """ + exchange = futures_data[futures_data["Ticker"] == symbol]["Exchange"].values[0] + today = datetime.today() + futures_index = list() + futures_curve = list() + historical_curve = list() + 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) + + 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: + print(historical_curve) + return pd.DataFrame( + {"Last Price": historical_curve, "expiration": futures_index} + ) + return pd.DataFrame({"Last Price": futures_curve, "expiration": futures_index}) diff --git a/openbb_sdk/sdk/core/openbb_core/app/static/package/module_map.json b/openbb_sdk/sdk/core/openbb_core/app/static/package/module_map.json index 2b84b5ad441..db022676fe8 100644 --- a/openbb_sdk/sdk/core/openbb_core/app/static/package/module_map.json +++ b/openbb_sdk/sdk/core/openbb_core/app/static/package/module_map.json @@ -36,6 +36,7 @@ "news_sectornews": "/news/sectornews", "futures_load": "/futures/load", "futures": "/futures", + "futures_curve": "/futures/curve", "stocks_fa_analysis": "/stocks/fa/analysis", "stocks": "/stocks", "stocks_fa": "/stocks/fa", diff --git a/openbb_sdk/sdk/provider/openbb_provider/standard_models/futures_eod.py b/openbb_sdk/sdk/provider/openbb_provider/standard_models/futures_eod.py index 929d6dc02d6..da839358bf9 100644 --- a/openbb_sdk/sdk/provider/openbb_provider/standard_models/futures_eod.py +++ b/openbb_sdk/sdk/provider/openbb_provider/standard_models/futures_eod.py @@ -4,7 +4,7 @@ from datetime import date, datetime from typing import Optional -from pydantic import Field, PositiveFloat +from pydantic import Field from openbb_provider.abstract.data import Data from openbb_provider.abstract.query_params import QueryParams @@ -23,14 +23,18 @@ class FuturesEODQueryParams(QueryParams, BaseSymbol): default=None, description=QUERY_DESCRIPTIONS.get("end_date", ""), ) + expiration: Optional[str] = Field( + default=None, + description="Future expiry date with format YYYY-MM", + ) class FuturesEODData(Data): """Futures end of day price Data.""" date: datetime = Field(description=DATA_DESCRIPTIONS.get("date", "")) - open: PositiveFloat = Field(description=DATA_DESCRIPTIONS.get("open", "")) - high: PositiveFloat = Field(description=DATA_DESCRIPTIONS.get("high", "")) - low: PositiveFloat = Field(description=DATA_DESCRIPTIONS.get("low", "")) - close: PositiveFloat = Field(description=DATA_DESCRIPTIONS.get("close", "")) - volume: PositiveFloat = Field(description=DATA_DESCRIPTIONS.get("volume", "")) \ No newline at end of file + open: float = Field(description=DATA_DESCRIPTIONS.get("open", "")) + high: float = Field(description=DATA_DESCRIPTIONS.get("high", "")) + low: float = Field(description=DATA_DESCRIPTIONS.get("low", "")) + close: float = Field(description=DATA_DESCRIPTIONS.get("close", "")) + volume: float = Field(description=DATA_DESCRIPTIONS.get("volume", "")) \ No newline at end of file