mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-29 13:32:08 +00:00
[Feature] Options Chains From YFinance (#6468)
* add yfinance to options chains * Explicit None --------- Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
This commit is contained in:
@@ -27,6 +27,7 @@ def headers():
|
||||
({"provider": "intrinio", "symbol": "AAPL", "date": "2023-01-25"}),
|
||||
({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
|
||||
({"provider": "tradier", "symbol": "AAPL"}),
|
||||
({"provider": "yfinance", "symbol": "AAPL"}),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
|
||||
@@ -23,6 +23,7 @@ def obb(pytestconfig):
|
||||
({"provider": "intrinio", "symbol": "AAPL", "date": "2023-01-25"}),
|
||||
({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
|
||||
({"provider": "tradier", "symbol": "AAPL"}),
|
||||
({"provider": "yfinance", "symbol": "AAPL"}),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
|
||||
33
openbb_platform/openbb/assets/reference.json
vendored
33
openbb_platform/openbb/assets/reference.json
vendored
@@ -1199,7 +1199,7 @@
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"type": "Literal['intrinio']",
|
||||
"type": "Literal['intrinio', 'yfinance']",
|
||||
"description": "The provider to use for the query, by default None. If None, the provider specified in defaults is selected or 'intrinio' if there is no default.",
|
||||
"default": "intrinio",
|
||||
"optional": true
|
||||
@@ -1214,7 +1214,8 @@
|
||||
"optional": true,
|
||||
"choices": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"yfinance": []
|
||||
},
|
||||
"returns": {
|
||||
"OBBject": [
|
||||
@@ -1225,7 +1226,7 @@
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"type": "Optional[Literal['intrinio']]",
|
||||
"type": "Optional[Literal['intrinio', 'yfinance']]",
|
||||
"description": "Provider name."
|
||||
},
|
||||
{
|
||||
@@ -1601,6 +1602,32 @@
|
||||
"optional": true,
|
||||
"choices": null
|
||||
}
|
||||
],
|
||||
"yfinance": [
|
||||
{
|
||||
"name": "dte",
|
||||
"type": "int",
|
||||
"description": "Days to expiration.",
|
||||
"default": null,
|
||||
"optional": true,
|
||||
"choices": null
|
||||
},
|
||||
{
|
||||
"name": "in_the_money",
|
||||
"type": "bool",
|
||||
"description": "Whether the option is in the money.",
|
||||
"default": null,
|
||||
"optional": true,
|
||||
"choices": null
|
||||
},
|
||||
{
|
||||
"name": "last_trade_timestamp",
|
||||
"type": "datetime",
|
||||
"description": "Timestamp for when the option was last traded.",
|
||||
"default": null,
|
||||
"optional": true,
|
||||
"choices": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"model": "OptionsChains"
|
||||
|
||||
@@ -25,7 +25,7 @@ class ROUTER_derivatives_options(Container):
|
||||
self,
|
||||
symbol: Annotated[str, OpenBBField(description="Symbol to get data for.")],
|
||||
provider: Annotated[
|
||||
Optional[Literal["intrinio"]],
|
||||
Optional[Literal["intrinio", "yfinance"]],
|
||||
OpenBBField(
|
||||
description="The provider to use for the query, by default None.\n If None, the provider specified in defaults is selected or 'intrinio' if there is\n no default."
|
||||
),
|
||||
@@ -38,7 +38,7 @@ class ROUTER_derivatives_options(Container):
|
||||
----------
|
||||
symbol : str
|
||||
Symbol to get data for.
|
||||
provider : Optional[Literal['intrinio']]
|
||||
provider : Optional[Literal['intrinio', 'yfinance']]
|
||||
The provider to use for the query, by default None.
|
||||
If None, the provider specified in defaults is selected or 'intrinio' if there is
|
||||
no default.
|
||||
@@ -50,7 +50,7 @@ class ROUTER_derivatives_options(Container):
|
||||
OBBject
|
||||
results : List[OptionsChains]
|
||||
Serializable results.
|
||||
provider : Optional[Literal['intrinio']]
|
||||
provider : Optional[Literal['intrinio', 'yfinance']]
|
||||
Provider name.
|
||||
warnings : Optional[List[Warning_]]
|
||||
List of warnings.
|
||||
@@ -149,6 +149,12 @@ class ROUTER_derivatives_options(Container):
|
||||
Rho of the option.
|
||||
exercise_style : Optional[str]
|
||||
The exercise style of the option, American or European. (provider: intrinio)
|
||||
dte : Optional[int]
|
||||
Days to expiration. (provider: yfinance)
|
||||
in_the_money : Optional[bool]
|
||||
Whether the option is in the money. (provider: yfinance)
|
||||
last_trade_timestamp : Optional[datetime]
|
||||
Timestamp for when the option was last traded. (provider: yfinance)
|
||||
|
||||
Examples
|
||||
--------
|
||||
@@ -165,7 +171,7 @@ class ROUTER_derivatives_options(Container):
|
||||
"provider": self._get_provider(
|
||||
provider,
|
||||
"/derivatives/options/chains",
|
||||
("intrinio",),
|
||||
("intrinio", "yfinance"),
|
||||
)
|
||||
},
|
||||
standard_params={
|
||||
|
||||
@@ -27,6 +27,7 @@ from openbb_yfinance.models.index_historical import (
|
||||
from openbb_yfinance.models.key_executives import YFinanceKeyExecutivesFetcher
|
||||
from openbb_yfinance.models.key_metrics import YFinanceKeyMetricsFetcher
|
||||
from openbb_yfinance.models.losers import YFLosersFetcher
|
||||
from openbb_yfinance.models.options_chains import YFinanceOptionsChainsFetcher
|
||||
from openbb_yfinance.models.price_target_consensus import (
|
||||
YFinancePriceTargetConsensusFetcher,
|
||||
)
|
||||
@@ -69,6 +70,7 @@ financial markets and assets.""",
|
||||
"KeyExecutives": YFinanceKeyExecutivesFetcher,
|
||||
"KeyMetrics": YFinanceKeyMetricsFetcher,
|
||||
"MarketIndices": YFinanceIndexHistoricalFetcher,
|
||||
"OptionsChains": YFinanceOptionsChainsFetcher,
|
||||
"PriceTargetConsensus": YFinancePriceTargetConsensusFetcher,
|
||||
"ShareStatistics": YFinanceShareStatisticsFetcher,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
"""YFinance Options Chains Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import yfinance as yf
|
||||
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.options_chains import (
|
||||
OptionsChainsData,
|
||||
OptionsChainsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from pandas import concat
|
||||
from pydantic import Field
|
||||
from pytz import timezone
|
||||
|
||||
|
||||
class YFinanceOptionsChainsQueryParams(OptionsChainsQueryParams):
|
||||
"""YFinance Options Chains Query Parameters."""
|
||||
|
||||
|
||||
class YFinanceOptionsChainsData(OptionsChainsData):
|
||||
"""YFinance Options Chains Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"contract_symbol": "contractSymbol",
|
||||
"last_trade_timestamp": "lastTradeDate",
|
||||
"last_trade_price": "lastPrice",
|
||||
"change_percent": "percentChange",
|
||||
"open_interest": "openInterest",
|
||||
"implied_volatility": "impliedVolatility",
|
||||
"in_the_money": "inTheMoney",
|
||||
}
|
||||
dte: Optional[int] = Field(
|
||||
default=None,
|
||||
description="Days to expiration.",
|
||||
)
|
||||
in_the_money: Optional[bool] = Field(
|
||||
default=None,
|
||||
description="Whether the option is in the money.",
|
||||
)
|
||||
last_trade_timestamp: Optional[datetime] = Field(
|
||||
default=None,
|
||||
description="Timestamp for when the option was last traded.",
|
||||
)
|
||||
|
||||
|
||||
class YFinanceOptionsChainsFetcher(
|
||||
Fetcher[YFinanceOptionsChainsQueryParams, List[YFinanceOptionsChainsData]]
|
||||
):
|
||||
"""YFinance Options Chains Fetcher."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> YFinanceOptionsChainsQueryParams:
|
||||
"""Transform the query."""
|
||||
return YFinanceOptionsChainsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: YFinanceOptionsChainsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> Dict:
|
||||
"""Extract the raw data from YFinance."""
|
||||
symbol = query.symbol.upper()
|
||||
ticker = yf.Ticker(symbol)
|
||||
expirations = list(ticker.options)
|
||||
if not expirations or len(expirations) == 0:
|
||||
raise ValueError(f"No options found for {symbol}")
|
||||
chains_output: List = []
|
||||
underlying = ticker.option_chain(expirations[0])[2]
|
||||
underlying_output: Dict = {
|
||||
"symbol": symbol,
|
||||
"name": underlying.get("longName"),
|
||||
"exchange": underlying.get("fullExchangeName"),
|
||||
"exchange_tz": underlying.get("exchangeTimezoneName"),
|
||||
"currency": underlying.get("currency"),
|
||||
"bid": underlying.get("bid"),
|
||||
"bid_size": underlying.get("bidSize"),
|
||||
"ask": underlying.get("ask"),
|
||||
"ask_size": underlying.get("askSize"),
|
||||
"last_price": underlying.get(
|
||||
"postMarketPrice", underlying.get("regularMarketPrice")
|
||||
),
|
||||
"open": underlying.get("regularMarketOpen", None),
|
||||
"high": underlying.get("regularMarketDayHigh", None),
|
||||
"low": underlying.get("regularMarketDayLow", None),
|
||||
"close": underlying.get("regularMarketPrice", None),
|
||||
"prev_close": underlying.get("regularMarketPreviousClose", None),
|
||||
"change": underlying.get("regularMarketChange", None),
|
||||
"change_percent": underlying.get("regularMarketChangePercent", None),
|
||||
"volume": underlying.get("regularMarketVolume", None),
|
||||
"dividend_yield": float(underlying.get("dividendYield", 0)) / 100,
|
||||
"dividend_yield_ttm": underlying.get("trailingAnnualDividendYield", None),
|
||||
"year_high": underlying.get("fiftyTwoWeekHigh", None),
|
||||
"year_low": underlying.get("fiftyTwoWeekLow", None),
|
||||
"ma_50": underlying.get("fiftyDayAverage", None),
|
||||
"ma_200": underlying.get("twoHundredDayAverage", None),
|
||||
"volume_avg_10d": underlying.get("averageDailyVolume10Day", None),
|
||||
"volume_avg_3m": underlying.get("averageDailyVolume3Month", None),
|
||||
"market_cap": underlying.get("marketCap", None),
|
||||
"shares_outstanding": underlying.get("sharesOutstanding", None),
|
||||
}
|
||||
tz = timezone(underlying_output.get("exchange_tz", "UTC"))
|
||||
|
||||
async def get_chain(ticker, expiration, tz):
|
||||
"""Get the data for one expiration."""
|
||||
exp = datetime.strptime(expiration, "%Y-%m-%d").date()
|
||||
now = datetime.now().date()
|
||||
dte = (exp - now).days
|
||||
calls = ticker.option_chain(expiration, tz=tz)[0]
|
||||
calls["option_type"] = "call"
|
||||
calls["expiration"] = expiration
|
||||
puts = ticker.option_chain(expiration, tz=tz)[1]
|
||||
puts["option_type"] = "put"
|
||||
puts["expiration"] = expiration
|
||||
chain = concat([calls, puts])
|
||||
chain = (
|
||||
chain.set_index(["strike", "option_type", "contractSymbol"])
|
||||
.sort_index()
|
||||
.reset_index()
|
||||
)
|
||||
chain["dte"] = dte
|
||||
chain["percentChange"] = chain["percentChange"] / 100
|
||||
for col in ["currency", "contractSize"]:
|
||||
if col in chain.columns:
|
||||
chain = chain.drop(col, axis=1)
|
||||
if len(chain) > 0:
|
||||
chains_output.extend(
|
||||
chain.fillna("N/A").replace("N/A", None).to_dict("records")
|
||||
)
|
||||
|
||||
await asyncio.gather(
|
||||
*[get_chain(ticker, expiration, tz) for expiration in expirations]
|
||||
)
|
||||
|
||||
if not chains_output:
|
||||
raise EmptyDataError(f"No data was returned for {symbol}")
|
||||
return {"underlying": underlying_output, "chains": chains_output}
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: YFinanceOptionsChainsQueryParams,
|
||||
data: Dict,
|
||||
**kwargs: Any,
|
||||
) -> List[YFinanceOptionsChainsData]:
|
||||
"""Transform the data."""
|
||||
if not data:
|
||||
raise EmptyDataError()
|
||||
metadata = data.get("underlying", {})
|
||||
records = data.get("chains", [])
|
||||
return AnnotatedResult(
|
||||
result=[YFinanceOptionsChainsData.model_validate(r) for r in records],
|
||||
metadata=metadata,
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@ from openbb_yfinance.models.losers import YFLosersFetcher
|
||||
from openbb_yfinance.models.market_indices import (
|
||||
YFinanceMarketIndicesFetcher,
|
||||
)
|
||||
from openbb_yfinance.models.options_chains import YFinanceOptionsChainsFetcher
|
||||
from openbb_yfinance.models.price_target_consensus import (
|
||||
YFinancePriceTargetConsensusFetcher,
|
||||
)
|
||||
@@ -61,10 +62,21 @@ def vcr_config():
|
||||
("period1", "MOCK_PERIOD_1"),
|
||||
("period2", "MOCK_PERIOD_2"),
|
||||
("crumb", "MOCK_CRUMB"),
|
||||
("date", "MOCK_DATE"),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_y_finance_options_chains_fetcher(credentials=test_credentials):
|
||||
"""Test YFinanceOptionsChainsFetcher."""
|
||||
params = {"symbol": "OXY"}
|
||||
|
||||
fetcher = YFinanceOptionsChainsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_y_finance_crypto_historical_fetcher(credentials=test_credentials):
|
||||
"""Test YFinanceCryptoHistoricalFetcher."""
|
||||
|
||||
Reference in New Issue
Block a user