feature/cboe-async: Refactor Cboe for Async (#5900)

* refactor cboe for async

* codespell

* unused imports

* pylint

* more pylint

* platform metadata

* classmethod decorator

* test params

* intrinio quote

* test param names

* date in test needs to be a datetime.date not string

* make fmp unix timestamp tz-aware and normalize percent change

* different way of parsing datetime string

* recapture cboe equity historical test

* unused import

* index snapshot update

* black

* extension map

* pylint

* cboe pyproject.toml

* proposal to standardize equity_quote

* one more file

* removing changes on static assets

* index_historical

* index_historical again

* last_time -> last_timestamp

* obsolete router path

* fix tests

* test param

* restore market index deprecation that I forgot about

* fixing tests

---------

Co-authored-by: hjoaquim <h.joaquim@campus.fct.unl.pt>
This commit is contained in:
Danglewood
2024-01-22 07:17:42 -08:00
committed by GitHub
parent a9b4d2070f
commit e2f7e597fa
54 changed files with 18481 additions and 19318 deletions

View File

@@ -8,7 +8,7 @@ from datetime import (
from typing import List, Optional, Set, Union
from dateutil import parser
from pydantic import Field, PositiveFloat, field_validator
from pydantic import Field, field_validator
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -47,17 +47,24 @@ class EquityHistoricalQueryParams(QueryParams):
class EquityHistoricalData(Data):
"""Equity Historical 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: Union[float, int] = Field(description=DATA_DESCRIPTIONS.get("volume", ""))
vwap: Optional[PositiveFloat] = Field(
date: Union[dateType, datetime] = Field(
description=DATA_DESCRIPTIONS.get("date", "")
)
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: Optional[Union[float, int]] = Field(
default=None, description=DATA_DESCRIPTIONS.get("volume", "")
)
vwap: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("vwap", "")
)
@field_validator("date", mode="before", check_fields=False)
def date_validate(cls, v): # pylint: disable=E0213
"""Return formatted datetime."""
return parser.isoparse(str(v))
v = parser.isoparse(str(v))
if v.hour == 0 and v.minute == 0:
return v.date()
return v

View File

@@ -1,50 +0,0 @@
"""European Index Constituents Standard Model."""
from typing import List, Set, Union
from pydantic import Field, field_validator
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
class EuropeanIndexConstituentsQueryParams(QueryParams):
"""European Index Constituents Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
@field_validator("symbol", mode="before", check_fields=False)
def upper_symbol(cls, v: Union[str, List[str], Set[str]]):
"""Convert symbol to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])
class EuropeanIndexConstituentsData(Data):
"""European Index Constituents Data."""
symbol: str = Field(
description=DATA_DESCRIPTIONS.get("symbol", "")
+ " The symbol is the constituent company in the index."
)
price: float = Field(
description="Current price of the constituent company in the index."
)
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", ""))
@field_validator("symbol", mode="before", check_fields=False)
def upper_symbol(cls, v: Union[str, List[str], Set[str]]):
"""Convert symbol to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])

View File

@@ -1,44 +0,0 @@
"""European Indices Standard Model."""
from datetime import (
date as dateType,
datetime,
)
from typing import List, Optional, Set, Union
from pydantic import Field, field_validator
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
class EuropeanIndicesQueryParams(QueryParams):
"""European Indices Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
start_date: Optional[dateType] = Field(
description=QUERY_DESCRIPTIONS.get("start_date", ""), default=None
)
end_date: Optional[dateType] = Field(
description=QUERY_DESCRIPTIONS.get("end_date", ""), default=None
)
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def upper_symbol(cls, v: Union[str, List[str], Set[str]]):
"""Convert symbol to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])
class EuropeanIndicesData(Data):
"""European Indices Data."""
date: datetime = Field(description=DATA_DESCRIPTIONS.get("date", ""))
close: float = Field(description=DATA_DESCRIPTIONS.get("close", ""))

View File

@@ -1,8 +1,7 @@
"""Index Constituents Standard Model."""
from datetime import date
from typing import List, Literal, Optional, Set, Union
from typing import List, Optional, Set, Union
from pydantic import Field, field_validator
@@ -14,9 +13,8 @@ from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
class IndexConstituentsQueryParams(QueryParams):
"""Index Constituents Query."""
index: Literal["nasdaq", "sp500", "dowjones"] = Field(
default="dowjones",
description="Index for which we want to fetch the constituents.",
index: str = Field(
description="Index to fetch the constituents of.",
)
@@ -24,27 +22,8 @@ class IndexConstituentsData(Data):
"""Index Constituents Data."""
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
name: str = Field(description="Name of the constituent company in the index.")
sector: str = Field(
description="Sector the constituent company in the index belongs to."
)
sub_sector: Optional[str] = Field(
default=None,
description="Sub-sector the constituent company in the index belongs to.",
)
headquarter: Optional[str] = Field(
default=None,
description="Location of the headquarter of the constituent company in the index.",
)
date_first_added: Optional[Union[date, str]] = Field(
default=None, description="Date the constituent company was added to the index."
)
cik: int = Field(
description=DATA_DESCRIPTIONS.get("cik", ""),
)
founded: Optional[Union[date, str]] = Field(
default=None,
description="Founding year of the constituent company in the index.",
name: Optional[str] = Field(
default=None, description="Name of the constituent company in the index."
)
@field_validator("symbol", mode="before", check_fields=False)

View File

@@ -1,6 +1,6 @@
"""Index Snapshots Standard Model."""
from typing import Literal, Optional
from typing import Optional
from pydantic import Field
@@ -12,8 +12,8 @@ from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
class IndexSnapshotsQueryParams(QueryParams):
"""Index Snapshots Query."""
region: Optional[Literal["US", "EU"]] = Field(
description="The region to return. Currently supports US and EU.", default="US"
region: Optional[str] = Field(
default=None, description="The region to return data for - i.e. 'us' or 'eu'."
)
@@ -38,10 +38,16 @@ class IndexSnapshotsData(Data):
close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("close", "")
)
volume: Optional[int] = Field(
default=None, description=DATA_DESCRIPTIONS.get("volume", "")
)
prev_close: Optional[float] = Field(
default=None, description="Previous closing price of the index."
default=None, description=DATA_DESCRIPTIONS.get("prev_close", "")
)
change: Optional[float] = Field(
default=None, description="Change in value of the index."
)
change: Optional[float] = Field(default=None, description="Change of the index.")
change_percent: Optional[float] = Field(
default=None, description="Change percent of the index."
default=None,
description="Change, in normalized percentage points, of the index.",
)

View File

@@ -1,6 +1,9 @@
"""Options Chains Standard Model."""
from datetime import date as dateType
from datetime import (
date as dateType,
datetime,
)
from typing import List, Optional, Set, Union
from pydantic import Field, field_validator
@@ -18,6 +21,7 @@ class OptionsChainsQueryParams(QueryParams):
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
@classmethod
@field_validator("symbol", mode="before", check_fields=False)
def upper_symbol(cls, v: Union[str, List[str], Set[str]]):
"""Convert symbol to uppercase."""
@@ -29,30 +33,48 @@ class OptionsChainsQueryParams(QueryParams):
class OptionsChainsData(Data):
"""Options Chains Data."""
contract_symbol: str = Field(description="Contract symbol for the option.")
symbol: Optional[str] = Field(
description=DATA_DESCRIPTIONS.get("symbol", "")
+ " Here its the underlying symbol for the option.",
+ " Here, it is the underlying symbol for the option.",
default=None,
)
contract_symbol: str = Field(description="Contract symbol for the option.")
eod_date: Optional[dateType] = Field(
default=None, description="Date for which the options chains are returned."
)
expiration: dateType = Field(description="Expiration date of the contract.")
strike: float = Field(description="Strike price of the contract.")
option_type: str = Field(description="Call or Put.")
eod_date: Optional[dateType] = Field(
default=None, description="Date for which the options chains are returned."
open_interest: Optional[int] = Field(
default=None, description="Open interest on the contract."
)
close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("close", "")
)
close_bid: Optional[float] = Field(
default=None, description="The closing bid price for the option that day."
)
close_ask: Optional[float] = Field(
default=None, description="The closing ask price for the option that day."
)
volume: Optional[float] = Field(
volume: Optional[int] = Field(
default=None, description=DATA_DESCRIPTIONS.get("volume", "")
)
theoretical_price: Optional[float] = Field(
default=None, description="Theoretical value of the option."
)
last_trade_price: Optional[float] = Field(
default=None, description="Last trade price of the option."
)
tick: Optional[str] = Field(
default=None, description="Whether the last tick was up or down in price."
)
bid: Optional[float] = Field(
default=None, description="Current bid price for the option."
)
bid_size: Optional[int] = Field(
default=None, description="Bid size for the option."
)
ask: Optional[float] = Field(
default=None, description="Current ask price for the option."
)
ask_size: Optional[int] = Field(
default=None, description="Ask size for the option."
)
mark: Optional[float] = Field(
default=None, description="The mid-price between the latest bid and ask."
)
open: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("open", "")
)
@@ -62,30 +84,64 @@ class OptionsChainsData(Data):
open_ask: Optional[float] = Field(
default=None, description="The opening ask price for the option that day."
)
open_interest: Optional[float] = Field(
default=None, description="Open interest on the contract."
)
high: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("high", "")
)
low: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("low", "")
)
mark: Optional[float] = Field(
default=None, description="The mid-price between the latest bid-ask spread."
)
ask_high: Optional[float] = Field(
default=None, description="The highest ask price for the option that day."
)
ask_low: Optional[float] = Field(
default=None, description="The lowest ask price for the option that day."
)
bid_high: Optional[float] = Field(
default=None, description="The highest bid price for the option that day."
)
ask_high: Optional[float] = Field(
default=None, description="The highest ask price for the option that day."
)
low: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("low", "")
)
bid_low: Optional[float] = Field(
default=None, description="The lowest bid price for the option that day."
)
ask_low: Optional[float] = Field(
default=None, description="The lowest ask price for the option that day."
)
close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("close", "")
)
close_size: Optional[int] = Field(
default=None, description="The closing trade size for the option that day."
)
close_time: Optional[datetime] = Field(
default=None,
description="The time of the closing price for the option that day.",
)
close_bid: Optional[float] = Field(
default=None, description="The closing bid price for the option that day."
)
close_bid_size: Optional[int] = Field(
default=None, description="The closing bid size for the option that day."
)
close_bid_time: Optional[datetime] = Field(
default=None,
description="The time of the bid closing price for the option that day.",
)
close_ask: Optional[float] = Field(
default=None, description="The closing ask price for the option that day."
)
close_ask_size: Optional[int] = Field(
default=None, description="The closing ask size for the option that day."
)
close_ask_time: Optional[datetime] = Field(
default=None,
description="The time of the ask closing price for the option that day.",
)
prev_close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("prev_close", "")
)
change: Optional[float] = Field(
default=None, description="The change in the price of the option."
)
change_percent: Optional[float] = Field(
default=None,
description="Change, in normalizezd percentage points, of the option.",
)
implied_volatility: Optional[float] = Field(
default=None, description="Implied volatility of the option."
)
@@ -93,10 +149,14 @@ class OptionsChainsData(Data):
gamma: Optional[float] = Field(default=None, description="Gamma of the option.")
theta: Optional[float] = Field(default=None, description="Theta of the option.")
vega: Optional[float] = Field(default=None, description="Vega of the option.")
rho: Optional[float] = Field(default=None, description="Rho of the option.")
@field_validator("date", mode="before", check_fields=False)
def upper_symbol(cls, v: Union[str, List[str], Set[str]]):
"""Convert symbol to uppercase."""
@field_validator("expiration", mode="before", check_fields=False)
@classmethod
def date_validate(cls, v): # pylint: disable=E0213
"""Return the datetime object from the date string"""
if isinstance(v, datetime):
return datetime.strftime(v, "%Y-%m-%d")
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])
return datetime.strptime(v, "%Y-%m-%d")
return v

View File

@@ -24,7 +24,7 @@ def headers():
"params",
[
({"provider": "intrinio", "symbol": "AAPL", "date": "2023-01-25"}),
({"provider": "cboe", "symbol": "AAPL"}),
({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
],
)
@pytest.mark.integration
@@ -86,7 +86,7 @@ def test_derivatives_futures_historical(params, headers):
@parametrize(
"params",
[
({"provider": "cboe", "symbol": "VX", "date": "2023-01-25"}),
({"provider": "cboe", "symbol": "VX", "date": None}),
({"provider": "yfinance", "symbol": "ES", "date": "2023-08-01"}),
],
)

View File

@@ -20,9 +20,8 @@ def obb(pytestconfig):
@parametrize(
"params",
[
({"symbol": "AAPL"}),
({"provider": "intrinio", "symbol": "AAPL", "date": "2023-01-25"}),
({"provider": "cboe", "symbol": "AAPL"}),
({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
],
)
@pytest.mark.integration
@@ -75,7 +74,7 @@ def test_derivatives_futures_historical(params, obb):
@parametrize(
"params",
[
({"symbol": "VX", "date": "2023-01-25", "provider": "cboe"}),
({"symbol": "VX", "provider": "cboe", "date": None}),
({"provider": "yfinance", "symbol": "ES", "date": "2023-08-01"}),
],
)

View File

@@ -852,6 +852,7 @@ def test_equity_compare_peers(params, headers):
"start_date": "2023-01-01",
"end_date": "2023-01-02",
"interval": "1m",
"use_cache": False,
}
),
(
@@ -861,6 +862,7 @@ def test_equity_compare_peers(params, headers):
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": "1d",
"use_cache": False,
}
),
(
@@ -1087,7 +1089,7 @@ def test_equity_fundamental_latest_attributes(params, headers):
@parametrize(
"params",
[
({"query": "AAPl", "is_symbol": True, "provider": "cboe"}),
({"query": "AAPl", "is_symbol": True, "provider": "cboe", "use_cache": False}),
({"query": "Apple", "provider": "sec", "use_cache": False, "is_fund": False}),
({"query": "", "provider": "nasdaq", "use_cache": False, "is_etf": True}),
({"query": "gold", "provider": "intrinio", "active": True, "limit": 100}),
@@ -1147,6 +1149,7 @@ def test_equity_screener(params, headers):
[
({"source": "iex", "provider": "intrinio", "symbol": "AAPL"}),
({"symbol": "AAPL", "provider": "fmp"}),
({"symbol": "AAPL", "provider": "cboe", "use_cache": False}),
({"symbol": "AAPL", "provider": "yfinance"}),
],
)

View File

@@ -806,6 +806,7 @@ def test_equity_compare_peers(params, obb):
"start_date": "2023-01-01",
"end_date": "2023-01-02",
"interval": "1m",
"use_cache": False,
}
),
(
@@ -815,6 +816,7 @@ def test_equity_compare_peers(params, obb):
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": "1d",
"use_cache": False,
}
),
(
@@ -1037,7 +1039,7 @@ def test_equity_fundamental_latest_attributes(params, obb):
@parametrize(
"params",
[
({"query": "AAPL", "is_symbol": True, "provider": "cboe"}),
({"query": "AAPL", "is_symbol": True, "provider": "cboe", "use_cache": False}),
({"query": "Apple", "provider": "sec", "use_cache": False, "is_fund": False}),
({"query": "", "provider": "nasdaq", "use_cache": False, "is_etf": True}),
({"query": "gold", "provider": "intrinio", "active": True, "limit": 100}),
@@ -1092,6 +1094,7 @@ def test_equity_screener(params, obb):
({"symbol": "AAPL"}),
({"source": "iex", "provider": "intrinio", "symbol": "AAPL"}),
({"symbol": "AAPL", "provider": "fmp"}),
({"symbol": "AAPL", "provider": "cboe", "use_cache": False}),
({"symbol": "AAPL", "provider": "yfinance"}),
],
)

View File

@@ -21,7 +21,10 @@ def headers():
@parametrize(
"params",
[({"index": "dowjones", "provider": "fmp"})],
[
({"index": "dowjones", "provider": "fmp"}),
({"index": "BUKBUS", "provider": "cboe"}),
],
)
@pytest.mark.integration
def test_index_constituents(params, headers):
@@ -53,6 +56,7 @@ def test_index_constituents(params, headers):
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"use_cache": False,
}
),
(
@@ -62,121 +66,7 @@ def test_index_constituents(params, headers):
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"interval": "1min",
"provider": "fmp",
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"timeseries": 1,
"sort": "desc",
}
),
(
{
"interval": "1day",
"provider": "fmp",
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"timeseries": 1,
"sort": "desc",
}
),
(
{
"timespan": "minute",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"timespan": "day",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"interval": "1d",
"period": "max",
"prepost": True,
"rounding": True,
"provider": "yfinance",
"symbol": "DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"provider": "intrinio",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"symbol": "$DJI",
"tag": "level",
"sort": "desc",
"limit": 100,
"type": None,
}
),
],
)
@pytest.mark.integration
def test_index_market(params, headers):
params = {p: v for p, v in params.items() if v}
query_str = get_querystring(params, [])
url = f"http://0.0.0.0:8000/api/v1/index/market?{query_str}"
result = requests.get(url, headers=headers, timeout=20)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@parametrize(
"params",
[
(
{
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"provider": "fmp",
"sort": "desc",
}
),
(
{
"interval": "1m",
"provider": "cboe",
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"interval": "1d",
"provider": "cboe",
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"use_cache": False,
}
),
(
@@ -269,17 +159,77 @@ def test_index_price_historical(params, headers):
[
(
{
"symbol": "BUKBUS",
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"provider": "cboe",
"provider": "fmp",
"sort": "desc",
}
),
(
{
"interval": "1m",
"provider": "cboe",
"symbol": "BUKBUS",
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"use_cache": True,
}
),
(
{
"interval": "1d",
"provider": "cboe",
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"use_cache": False,
}
),
(
{
"interval": "1min",
"provider": "fmp",
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"timeseries": 1,
"sort": "desc",
}
),
(
{
"interval": "1day",
"provider": "fmp",
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"timeseries": 1,
"sort": "desc",
}
),
(
{
"timespan": "minute",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"timespan": "day",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
@@ -287,36 +237,36 @@ def test_index_price_historical(params, headers):
(
{
"interval": "1d",
"provider": "cboe",
"symbol": "BUKBUS",
"period": "max",
"prepost": True,
"rounding": True,
"provider": "yfinance",
"symbol": "DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"provider": "intrinio",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"symbol": "$DJI",
"tag": "level",
"sort": "desc",
"limit": 100,
"type": None,
}
),
],
)
@pytest.mark.integration
def test_index_european(params, headers):
def test_index_market(params, headers):
params = {p: v for p, v in params.items() if v}
query_str = get_querystring(params, [])
url = f"http://0.0.0.0:8000/api/v1/index/european?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@parametrize(
"params",
[({"symbol": "BUKBUS", "provider": "cboe"})],
)
@pytest.mark.integration
def test_index_european_constituents(params, headers):
params = {p: v for p, v in params.items() if v}
query_str = get_querystring(params, [])
url = f"http://0.0.0.0:8000/api/v1/index/european_constituents?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
url = f"http://0.0.0.0:8000/api/v1/index/market?{query_str}"
result = requests.get(url, headers=headers, timeout=20)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@@ -324,7 +274,7 @@ def test_index_european_constituents(params, headers):
@parametrize(
"params",
[
({"europe": True, "provider": "cboe"}),
({"provider": "cboe", "use_cache": False}),
({"provider": "fmp"}),
({"provider": "yfinance"}),
],
@@ -343,8 +293,7 @@ def test_index_available(params, headers):
@parametrize(
"params",
[
({"query": "D", "is_symbol": True, "provider": "cboe"}),
({"europe": True, "provider": "cboe", "query": "A", "is_symbol": False}),
({"query": "D", "is_symbol": True, "provider": "cboe", "use_cache": False}),
],
)
@pytest.mark.integration
@@ -360,7 +309,7 @@ def test_index_search(params, headers):
@parametrize(
"params",
[({"provider": "cboe", "region": "US"})],
[({"provider": "cboe", "region": "us"})],
)
@pytest.mark.integration
def test_index_snapshots(params, headers):

View File

@@ -21,7 +21,8 @@ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
@parametrize(
"params",
[
({"index": "dowjones"}),
({"index": "dowjones", "provider": "fmp"}),
({"index": "BUKBUS", "provider": "cboe"}),
],
)
@pytest.mark.integration
@@ -35,123 +36,6 @@ def test_index_constituents(params, obb):
@parametrize(
"params",
[
(
{
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"interval": "1m",
"provider": "cboe",
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"interval": "1d",
"provider": "cboe",
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"interval": "1min",
"provider": "fmp",
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"timeseries": 1,
"sort": "desc",
}
),
(
{
"interval": "1day",
"provider": "fmp",
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"timeseries": 1,
"sort": "desc",
}
),
(
{
"timespan": "minute",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"timespan": "day",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"interval": "1d",
"period": "max",
"prepost": True,
"rounding": True,
"provider": "yfinance",
"symbol": "DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"provider": "intrinio",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"symbol": "$DJI",
"tag": "level",
"sort": "desc",
"limit": 100,
"type": None,
}
),
],
)
@pytest.mark.integration
def test_index_market(params, obb):
result = obb.index.market(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@parametrize(
"params",
[
(
{
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"interval": "1m",
@@ -159,6 +43,7 @@ def test_index_market(params, obb):
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"use_cache": False,
}
),
(
@@ -168,6 +53,7 @@ def test_index_market(params, obb):
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"use_cache": False,
}
),
(
@@ -257,7 +143,7 @@ def test_index_price_historical(params, obb):
[
(
{
"symbol": "BUKBUS",
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
@@ -266,7 +152,66 @@ def test_index_price_historical(params, obb):
{
"interval": "1m",
"provider": "cboe",
"symbol": "BUKBUS",
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"use_cache": True,
}
),
(
{
"interval": "1d",
"provider": "cboe",
"symbol": "AAVE100",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"use_cache": False,
}
),
(
{
"interval": "1min",
"provider": "fmp",
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"timeseries": 1,
"sort": "desc",
}
),
(
{
"interval": "1day",
"provider": "fmp",
"symbol": "^DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"timeseries": 1,
"sort": "desc",
}
),
(
{
"timespan": "minute",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"timespan": "day",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
@@ -274,31 +219,32 @@ def test_index_price_historical(params, obb):
(
{
"interval": "1d",
"provider": "cboe",
"symbol": "BUKBUS",
"period": "max",
"prepost": True,
"rounding": True,
"provider": "yfinance",
"symbol": "DJI",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
}
),
(
{
"provider": "intrinio",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"symbol": "$DJI",
"tag": "level",
"sort": "desc",
"limit": 100,
"type": None,
}
),
],
)
@pytest.mark.integration
def test_index_european(params, obb):
result = obb.index.european(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@parametrize(
"params",
[
({"symbol": "BUKBUS", "provider": "cboe"}),
],
)
@pytest.mark.integration
def test_index_european_constituents(params, obb):
result = obb.index.european_constituents(**params)
def test_index_market(params, obb):
result = obb.index.market(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@@ -308,7 +254,7 @@ def test_index_european_constituents(params, obb):
"params",
[
({}),
({"europe": True, "provider": "cboe"}),
({"provider": "cboe", "use_cache": False}),
({"provider": "fmp"}),
({"provider": "yfinance"}),
],
@@ -324,8 +270,7 @@ def test_index_available(params, obb):
@parametrize(
"params",
[
({"query": "D", "is_symbol": True}),
({"europe": True, "provider": "cboe", "query": "A", "is_symbol": False}),
({"query": "D", "is_symbol": True, "use_cache": False, "provider": "cboe"}),
],
)
@pytest.mark.integration
@@ -339,7 +284,7 @@ def test_index_search(params, obb):
@parametrize(
"params",
[
({"region": "US"}),
({"region": "us", "provider": "cboe"}),
],
)
@pytest.mark.integration

View File

@@ -33,17 +33,6 @@ async def market(
return await OBBject.from_query(Query(**locals()))
@router.command(model="EuropeanIndices")
async def european(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject[BaseModel]:
"""Historical European Indices."""
return await OBBject.from_query(Query(**locals()))
@router.command(model="IndexConstituents")
async def constituents(
cc: CommandContext,
@@ -55,17 +44,6 @@ async def constituents(
return await OBBject.from_query(Query(**locals()))
@router.command(model="EuropeanIndexConstituents")
async def european_constituents(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject[BaseModel]:
"""European Index Constituents. Constituents of select european indices."""
return await OBBject.from_query(Query(**locals()))
@router.command(model="IndexSnapshots")
async def snapshots(
cc: CommandContext,

48
openbb_platform/poetry.lock generated vendored
View File

@@ -898,13 +898,13 @@ files = [
[[package]]
name = "jsonschema"
version = "4.21.0"
version = "4.21.1"
description = "An implementation of JSON Schema validation for Python"
optional = true
python-versions = ">=3.8"
files = [
{file = "jsonschema-4.21.0-py3-none-any.whl", hash = "sha256:70a09719d375c0a2874571b363c8a24be7df8071b80c9aa76bc4551e7297c63c"},
{file = "jsonschema-4.21.0.tar.gz", hash = "sha256:3ba18e27f7491ea4a1b22edce00fb820eec968d397feb3f9cb61d5894bb38167"},
{file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"},
{file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"},
]
[package.dependencies]
@@ -2111,13 +2111,13 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "posthog"
version = "3.3.1"
version = "3.3.2"
description = "Integrate PostHog into any python application."
optional = false
python-versions = "*"
files = [
{file = "posthog-3.3.1-py2.py3-none-any.whl", hash = "sha256:5f53b232acb680a0389e372db5f786061a18386b8b5324bddcc64eff9fdb319b"},
{file = "posthog-3.3.1.tar.gz", hash = "sha256:252cb6ab5cbe7ff002753f34fb647721b3af75034b4a5a631317ebf3db58fe59"},
{file = "posthog-3.3.2-py2.py3-none-any.whl", hash = "sha256:14fb43ea95c40b353db59c49af2c09ff15188aa2963f48091fc7912fa9375263"},
{file = "posthog-3.3.2.tar.gz", hash = "sha256:734bf89f3c372605a8bbf2b07f600885287209145d747b09ccd004c59834750e"},
]
[package.dependencies]
@@ -2683,28 +2683,28 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.1.13"
version = "0.1.14"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e3fd36e0d48aeac672aa850045e784673449ce619afc12823ea7868fcc41d8ba"},
{file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9fb6b3b86450d4ec6a6732f9f60c4406061b6851c4b29f944f8c9d91c3611c7a"},
{file = "ruff-0.1.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b13ba5d7156daaf3fd08b6b993360a96060500aca7e307d95ecbc5bb47a69296"},
{file = "ruff-0.1.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ebb40442f7b531e136d334ef0851412410061e65d61ca8ce90d894a094feb22"},
{file = "ruff-0.1.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226b517f42d59a543d6383cfe03cccf0091e3e0ed1b856c6824be03d2a75d3b6"},
{file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f0312ba1061e9b8c724e9a702d3c8621e3c6e6c2c9bd862550ab2951ac75c16"},
{file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f59bcf5217c661254bd6bc42d65a6fd1a8b80c48763cb5c2293295babd945dd"},
{file = "ruff-0.1.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6894b00495e00c27b6ba61af1fc666f17de6140345e5ef27dd6e08fb987259d"},
{file = "ruff-0.1.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1600942485c6e66119da294c6294856b5c86fd6df591ce293e4a4cc8e72989"},
{file = "ruff-0.1.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ee3febce7863e231a467f90e681d3d89210b900d49ce88723ce052c8761be8c7"},
{file = "ruff-0.1.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dcaab50e278ff497ee4d1fe69b29ca0a9a47cd954bb17963628fa417933c6eb1"},
{file = "ruff-0.1.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f57de973de4edef3ad3044d6a50c02ad9fc2dff0d88587f25f1a48e3f72edf5e"},
{file = "ruff-0.1.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a36fa90eb12208272a858475ec43ac811ac37e91ef868759770b71bdabe27b6"},
{file = "ruff-0.1.13-py3-none-win32.whl", hash = "sha256:a623349a505ff768dad6bd57087e2461be8db58305ebd5577bd0e98631f9ae69"},
{file = "ruff-0.1.13-py3-none-win_amd64.whl", hash = "sha256:f988746e3c3982bea7f824c8fa318ce7f538c4dfefec99cd09c8770bd33e6539"},
{file = "ruff-0.1.13-py3-none-win_arm64.whl", hash = "sha256:6bbbc3042075871ec17f28864808540a26f0f79a4478c357d3e3d2284e832998"},
{file = "ruff-0.1.13.tar.gz", hash = "sha256:e261f1baed6291f434ffb1d5c6bd8051d1c2a26958072d38dfbec39b3dda7352"},
{file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:96f76536df9b26622755c12ed8680f159817be2f725c17ed9305b472a757cdbb"},
{file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab3f71f64498c7241123bb5a768544cf42821d2a537f894b22457a543d3ca7a9"},
{file = "ruff-0.1.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060156ecc572b8f984fd20fd8b0fcb692dd5d837b7606e968334ab7ff0090ab"},
{file = "ruff-0.1.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a53d8e35313d7b67eb3db15a66c08434809107659226a90dcd7acb2afa55faea"},
{file = "ruff-0.1.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bea9be712b8f5b4ebed40e1949379cfb2a7d907f42921cf9ab3aae07e6fba9eb"},
{file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2270504d629a0b064247983cbc495bed277f372fb9eaba41e5cf51f7ba705a6a"},
{file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80258bb3b8909b1700610dfabef7876423eed1bc930fe177c71c414921898efa"},
{file = "ruff-0.1.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:653230dd00aaf449eb5ff25d10a6e03bc3006813e2cb99799e568f55482e5cae"},
{file = "ruff-0.1.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b3acc6c4e6928459ba9eb7459dd4f0c4bf266a053c863d72a44c33246bfdbf"},
{file = "ruff-0.1.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b3dadc9522d0eccc060699a9816e8127b27addbb4697fc0c08611e4e6aeb8b5"},
{file = "ruff-0.1.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1c8eca1a47b4150dc0fbec7fe68fc91c695aed798532a18dbb1424e61e9b721f"},
{file = "ruff-0.1.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:62ce2ae46303ee896fc6811f63d6dabf8d9c389da0f3e3f2bce8bc7f15ef5488"},
{file = "ruff-0.1.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2027dde79d217b211d725fc833e8965dc90a16d0d3213f1298f97465956661b"},
{file = "ruff-0.1.14-py3-none-win32.whl", hash = "sha256:722bafc299145575a63bbd6b5069cb643eaa62546a5b6398f82b3e4403329cab"},
{file = "ruff-0.1.14-py3-none-win_amd64.whl", hash = "sha256:e3d241aa61f92b0805a7082bd89a9990826448e4d0398f0e2bc8f05c75c63d99"},
{file = "ruff-0.1.14-py3-none-win_arm64.whl", hash = "sha256:269302b31ade4cde6cf6f9dd58ea593773a37ed3f7b97e793c8594b262466b67"},
{file = "ruff-0.1.14.tar.gz", hash = "sha256:ad3f8088b2dfd884820289a06ab718cde7d38b94972212cc4ba90d5fbc9955f3"},
]
[[package]]

View File

@@ -1,25 +1,19 @@
"""CBOE provider module."""
"""Cboe provider module."""
from openbb_cboe.models.available_indices import CboeAvailableIndicesFetcher
from openbb_cboe.models.equity_historical import CboeEquityHistoricalFetcher
from openbb_cboe.models.equity_info import CboeEquityInfoFetcher
from openbb_cboe.models.equity_quote import CboeEquityQuoteFetcher
from openbb_cboe.models.equity_search import CboeEquitySearchFetcher
from openbb_cboe.models.european_index_constituents import (
CboeEuropeanIndexConstituentsFetcher,
)
from openbb_cboe.models.european_indices import (
CboeEuropeanIndicesFetcher,
)
from openbb_cboe.models.futures_curve import CboeFuturesCurveFetcher
from openbb_cboe.models.index_constituents import (
CboeIndexConstituentsFetcher,
)
from openbb_cboe.models.index_historical import (
CboeIndexHistoricalFetcher,
)
from openbb_cboe.models.index_search import CboeIndexSearchFetcher
from openbb_cboe.models.index_snapshots import CboeIndexSnapshotsFetcher
from openbb_cboe.models.market_indices import (
CboeMarketIndicesFetcher,
)
from openbb_cboe.models.options_chains import CboeOptionsChainsFetcher
from openbb_core.provider.abstract.provider import Provider
@@ -33,15 +27,14 @@ cboe_provider = Provider(
fetcher_dict={
"AvailableIndices": CboeAvailableIndicesFetcher,
"EquityHistorical": CboeEquityHistoricalFetcher,
"EquityInfo": CboeEquityInfoFetcher,
"EquityQuote": CboeEquityQuoteFetcher,
"EquitySearch": CboeEquitySearchFetcher,
"EuropeanIndexConstituents": CboeEuropeanIndexConstituentsFetcher,
"EuropeanIndices": CboeEuropeanIndicesFetcher,
"IndexConstituents": CboeIndexConstituentsFetcher,
"FuturesCurve": CboeFuturesCurveFetcher,
"IndexHistorical": CboeIndexHistoricalFetcher,
"IndexSearch": CboeIndexSearchFetcher,
"IndexSnapshots": CboeIndexSnapshotsFetcher,
"MarketIndices": CboeMarketIndicesFetcher,
"MarketIndices": CboeIndexHistoricalFetcher,
"OptionsChains": CboeOptionsChainsFetcher,
},
)

View File

@@ -1,9 +1,9 @@
"""CBOE Available Indices Model."""
"""Cboe Available Indices Model."""
from datetime import time
from typing import Any, Dict, List, Optional
from openbb_cboe.utils.helpers import Europe, get_cboe_index_directory
from openbb_cboe.utils.helpers import get_index_directory
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.available_indices import (
AvailableIndicesData,
@@ -13,31 +13,30 @@ from pydantic import Field
class CboeAvailableIndicesQueryParams(AvailableIndicesQueryParams):
"""CBOE Available Indices Query.
"""Cboe Available Indices Query.
Source: https://www.cboe.com/europe/indices/
Source: https://www.cboe.com/
"""
europe: bool = Field(
description="Filter for European indices. False for US indices.", default=False
use_cache: bool = Field(
default=True,
description="When True, the Cboe Index directory will be cached for 24 hours."
+ " Set as False to bypass.",
)
class CboeAvailableIndicesData(AvailableIndicesData):
"""CBOE Available Indices Data.
"""Cboe Available Indices Data.
Source: https://www.cboe.com/europe/indices/
Source: https://www.cboe.com/
"""
isin: Optional[str] = Field(
default=None,
description="ISIN code for the index. Valid only for European indices.",
)
region: Optional[str] = Field(
default=None,
description="Region for the index. Valid only for European indices",
)
__alias_dict__ = {
"symbol": "index_symbol",
"data_delay": "mkt_data_delay",
"open_time": "calc_start_time",
"close_time": "calc_end_time",
}
symbol: Optional[str] = Field(description="Symbol for the index.")
@@ -94,15 +93,15 @@ class CboeAvailableIndicesFetcher(
return CboeAvailableIndicesQueryParams(**params)
@staticmethod
def extract_data(
async def aextract_data(
query: CboeAvailableIndicesQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint."""
if query.europe is True:
return Europe.list_indices()
return get_cboe_index_directory().sort_index().reset_index().to_dict("records")
data = await get_index_directory(use_cache=query.use_cache, **kwargs)
return data.to_dict("records")
@staticmethod
def transform_data(

View File

@@ -1,15 +1,13 @@
"""CBOE Equity Historical Price Model."""
"""Cboe Equity Historical Price Model."""
import contextlib
import warnings
from datetime import datetime, timedelta
from typing import Any, Dict, List, Literal, Optional
import pandas as pd
from openbb_cboe.utils.helpers import (
TICKER_EXCEPTIONS,
get_cboe_directory,
get_cboe_index_directory,
get_ticker_info,
get_index_directory,
)
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_historical import (
@@ -17,33 +15,52 @@ from openbb_core.provider.standard_models.equity_historical import (
EquityHistoricalQueryParams,
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_core.provider.utils.helpers import make_request
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_requests
from pandas import DataFrame, Series, concat, to_datetime
from pydantic import Field
_warn = warnings.warn
class CboeEquityHistoricalQueryParams(EquityHistoricalQueryParams):
"""CBOE Equity Historical Price Query.
"""
Cboe Equity Historical Price Query
Source: https://www.cboe.com/
"""
interval: Literal["1m", "1d"] = Field(
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
default="1d",
description=(
QUERY_DESCRIPTIONS.get("interval", "")
+ " The most recent trading day is not including in daily historical data."
+ " Intraday data is only available for the most recent trading day at 1 minute intervals."
),
)
use_cache: bool = Field(
default=True,
description="When True, the company directories will be cached for 24 hours and are used to validate symbols."
+ " The results of the function are not cached. Set as False to bypass.",
)
class CboeEquityHistoricalData(EquityHistoricalData):
"""CBOE Equity Historical Price Data."""
"""Cboe Equity Historical Price Data."""
calls_volume: Optional[float] = Field(
__alias_dict__ = {
"volume": "stock_volume",
}
calls_volume: Optional[int] = Field(
default=None,
description="Number of calls traded during the most recent trading period. Only valid if interval is 1m.",
)
puts_volume: Optional[float] = Field(
puts_volume: Optional[int] = Field(
default=None,
description="Number of puts traded during the most recent trading period. Only valid if interval is 1m.",
)
total_options_volume: Optional[float] = Field(
total_options_volume: Optional[int] = Field(
default=None,
description="Total number of options traded during the most recent trading period. Only valid if interval is 1m.",
)
@@ -59,179 +76,131 @@ class CboeEquityHistoricalFetcher(
@staticmethod
def transform_query(params: Dict[str, Any]) -> CboeEquityHistoricalQueryParams:
"""Transform the query. Setting the start and end dates for a 1 year period."""
return CboeEquityHistoricalQueryParams(**params)
"""Transform the query."""
transformed_params = params.copy()
now = datetime.now()
if (
len(params.get("symbol", "").split(",")) > 1
and params.get("start_date") is None
):
transformed_params["start_date"] = (
transformed_params["start_date"]
if transformed_params["start_date"]
else (now - timedelta(days=720)).strftime("%Y-%m-%d")
)
if transformed_params.get("start_date") is None:
transformed_params["start_date"] = (
transformed_params["start_date"]
if transformed_params.get("start_date")
else "1950-01-01"
)
if params.get("end_date") is None:
transformed_params["end_date"] = (
transformed_params["end_date"]
if transformed_params.get("end_date")
else now.strftime("%Y-%m-%d")
)
return CboeEquityHistoricalQueryParams(**transformed_params)
@staticmethod
def extract_data(
query: CboeEquityHistoricalQueryParams, # pylint: disable=unused-argument
async def aextract_data(
query: CboeEquityHistoricalQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint."""
# Symbol directories are cached for seven days and are used for error handling and URL generation.
SYMBOLS = get_cboe_directory()
INDEXES = get_cboe_index_directory().index.to_list()
multi = pd.DataFrame()
now = datetime.now()
tickers = (
query.symbol.split(",") if "," in query.symbol else [query.symbol.upper()]
)
if len(tickers) > 1:
query.start_date = (
query.start_date if query.start_date else now - timedelta(days=720)
"""Return the raw data from the Cboe endpoint."""
symbols = query.symbol.split(",")
INDEXES = await get_index_directory(use_cache=query.use_cache)
INDEXES = INDEXES.set_index("index_symbol")
INTERVAL_DICT = {"1m": "intraday", "1d": "historical"}
def _generate_historical_prices_url(
symbol,
interval_type: Literal["intraday", "historical"] = "historical",
) -> str:
"""Generate the URL for the data."""
if symbol.replace("^", "") in TICKER_EXCEPTIONS:
interval_type = "intraday" if len(symbols) == 1 else "historical"
_warn(
"Only the most recent trading day is available for this ticker, "
+ symbol
)
base_url: str = (
f"https://cdn.cboe.com/api/global/delayed_quotes/charts/{interval_type}"
)
if len(tickers) == 1:
query.start_date = (
query.start_date if query.start_date else now - timedelta(days=50000)
)
query.end_date = query.end_date if query.end_date else now
def get_one(symbol, start_date, end_date, interval):
symbol = symbol.upper()
if "^" in symbol:
symbol = symbol.replace("^", "")
if len(tickers) == 1 and symbol == "NDX":
raise RuntimeError("NDX is not supported by this function.")
data = pd.DataFrame()
if symbol not in SYMBOLS.index and symbol not in INDEXES:
raise RuntimeError(
f"The symbol, {symbol}, was not found in the CBOE directory. Use `search()`."
)
def __generate_historical_prices_url(
symbol,
data_type: Optional[Literal["intraday", "historical"]] = "historical",
) -> str:
"""Generate the final URL for historical prices data."""
url: str = (
f"https://cdn.cboe.com/api/global/delayed_quotes/charts/{data_type}"
)
url = (
url + f"/_{symbol}.json"
if symbol in TICKER_EXCEPTIONS or symbol in INDEXES
else url + f"/{symbol}.json"
)
return url
url = (
__generate_historical_prices_url(symbol, "intraday")
if interval == "1m"
else __generate_historical_prices_url(symbol)
base_url + f"/_{symbol.replace('^', '')}.json"
if symbol.replace("^", "") in TICKER_EXCEPTIONS
or symbol.replace("^", "") in INDEXES.index
else base_url + f"/{symbol.replace('^', '')}.json"
)
r = make_request(url)
return url
if r.status_code != 200:
raise RuntimeError(r.status_code)
if interval == "1d":
data = (
pd.DataFrame(r.json()["data"])[
["date", "open", "high", "low", "close", "volume"]
]
).set_index("date")
# Fill in missing data from current or most recent trading session.
today = pd.to_datetime(datetime.now().date())
if today.weekday() > 4:
day_minus = today.weekday() - 4
today = pd.to_datetime(today - timedelta(days=day_minus))
if today != data.index[-1]:
_today = pd.Series(get_ticker_info(symbol))
today_df = pd.Series(dtype="object")
today_df["open"] = round(_today["open"], 2)
today_df["high"] = round(_today["high"], 2)
today_df["low"] = round(_today["low"], 2)
today_df["close"] = round(_today["close"], 2)
if symbol not in INDEXES and symbol not in TICKER_EXCEPTIONS:
data = data[data["volume"] > 0]
today_df["volume"] = _today["volume"]
today_df["date"] = today.date()
today_df = pd.DataFrame(today_df).transpose().set_index("date")
data = pd.concat([data, today_df], axis=0)
# If ticker is an index there is no volume data and the types must be set.
if symbol in INDEXES or symbol in TICKER_EXCEPTIONS:
data = data[["open", "high", "low", "close", "volume"]]
data["open"] = round(data.open.astype(float), 2)
data["high"] = round(data.high.astype(float), 2)
data["low"] = round(data.low.astype(float), 2)
data["close"] = round(data.close.astype(float), 2)
data["volume"] = 0
data.index = pd.to_datetime(data.index)
data = data[data["open"] > 0]
data = data[
(data.index >= pd.to_datetime(start_date))
& (data.index <= pd.to_datetime(end_date))
]
if query.interval == "1m":
data_list = r.json()["data"]
date: list[datetime] = []
open_: list[float] = []
high: list[float] = []
low: list[float] = []
close: list[float] = []
volume: list[float] = []
calls_volume: list[float] = []
puts_volume: list[float] = []
total_options_volume: list[float] = []
for data in data_list:
date.append(data["datetime"])
open_.append(data["price"]["open"])
high.append(data["price"]["high"])
low.append(data["price"]["low"])
close.append(data["price"]["close"])
volume.append(data["volume"]["stock_volume"])
calls_volume.append(data["volume"]["calls_volume"])
puts_volume.append(data["volume"]["puts_volume"])
total_options_volume.append(data["volume"]["total_options_volume"])
data = pd.DataFrame()
date = [d.replace("T", " ") for d in date]
date = [datetime.strptime(d, "%Y-%m-%d %H:%M:%S") for d in date]
data["date"] = date
data["open"] = open_
data["high"] = high
data["low"] = low
data["close"] = close
data["volume"] = volume
data["calls_volume"] = calls_volume
data["puts_volume"] = puts_volume
data["total_options_volume"] = total_options_volume
data = data.set_index("date").sort_index()
data.index = data.index.strftime("%Y-%m-%d %H:%M:%S")
data = data[data["open"] > 0]
data["volume"] = round(data["volume"].astype("int64"))
data["symbol"] = symbol
return data.drop_duplicates().reset_index()
for ticker in tickers:
if ticker != "NDX":
data = get_one(
ticker,
start_date=query.start_date,
end_date=query.end_date,
interval=query.interval,
).set_index(["date", "symbol"])
multi = pd.concat([multi, data], axis=0)
multi = multi.sort_index().reset_index()
if len(tickers) == 1:
multi = multi.drop(columns=["symbol"])
return multi.to_dict("records")
urls = [
_generate_historical_prices_url(symbol, INTERVAL_DICT[query.interval])
for symbol in symbols
]
return await amake_requests(urls, **kwargs)
@staticmethod
def transform_data(
query: CboeEquityHistoricalQueryParams, data: List[Dict], **kwargs: Any
) -> List[CboeEquityHistoricalData]:
"""Transform the data to the standard format."""
return [CboeEquityHistoricalData.model_validate(d) for d in data]
if not data:
raise EmptyDataError()
results = DataFrame()
# Results will be different depending on the interval.
# We will also parse the output from multiple symbols.
for item in data:
result = DataFrame()
_symbol = item["symbol"]
_temp = item["data"]
if query.interval == "1d":
result = DataFrame(_temp)
result["symbol"] = _symbol.replace("_", "").replace("^", "")
result = result.set_index("date")
results = concat([results, result])
if query.interval == "1m":
_datetime = Series([d["datetime"] for d in _temp]).rename("date")
_price = DataFrame(d["price"] for d in _temp)
_volume = DataFrame(d["volume"] for d in _temp)
result = _price.join([_volume, _datetime])
result["symbol"] = _symbol.replace("_", "").replace("^", "")
result = result.set_index("date")
results = concat([results, result])
results = results.set_index("symbol", append=True).sort_index()
# There are some bad data points in the open/high/low results that will break things.
for c in results.columns:
# Some symbols do not have volume data, and some intraday symbols don't have options.
if c in ["volume", "puts_volume", "calls_volume", "total_options_volume"]:
results[c] = results[c].astype(float).astype("int64")
results = (
results.drop(columns=c)
if results[c].sum() == 0 and c != "volume"
else results
)
# Sub-penny prices are not warranted for any of the assets returned.
if c in ["open", "high", "low", "close"]:
with contextlib.suppress(Exception):
results[c] = results[c].astype(float)
results[c] = round(results[c], 2)
output = results.dropna(how="all", axis=1).reset_index()
output = output[output["open"] > 0]
# When there is only one ticker symbol, the symbol column is redundant.
if len(query.symbol.split(",")) == 1:
output = output.drop(columns="symbol")
# Finally, we apply the user-specified date range because it is not filtered at the source.
output = output[
(to_datetime(output["date"]) >= to_datetime(query.start_date))
& (
to_datetime(output["date"])
<= to_datetime(query.end_date + timedelta(days=1))
)
]
return [
CboeEquityHistoricalData.model_validate(d)
for d in output.to_dict("records")
]

View File

@@ -1,140 +0,0 @@
"""CBOE Equity Info Model."""
import concurrent.futures
from datetime import datetime
from typing import Any, Dict, List, Optional
import pandas as pd
from openbb_cboe.utils.helpers import (
get_cboe_directory,
get_cboe_index_directory,
get_ticker_info,
get_ticker_iv,
)
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_info import (
EquityInfoData,
EquityInfoQueryParams,
)
from pydantic import Field
class CboeEquityInfoQueryParams(EquityInfoQueryParams):
"""CBOE Equity Info Query.
Source: https://www.cboe.com/
"""
class CboeEquityInfoData(EquityInfoData):
"""CBOE Equity Info Data."""
type: Optional[str] = Field(default=None, description="Type of asset.")
exchange_id: Optional[int] = Field(
default=None, description="The Exchange ID number."
)
tick: Optional[str] = Field(
default=None, description="Whether the last sale was an up or down tick."
)
bid: Optional[float] = Field(default=None, description="Current bid price.")
bid_size: Optional[float] = Field(default=None, description="Bid lot size.")
ask: Optional[float] = Field(default=None, description="Current ask price.")
ask_size: Optional[float] = Field(default=None, description="Ask lot size.")
volume: Optional[float] = Field(
default=None, description="Stock volume for the current trading day."
)
iv30: Optional[float] = Field(
default=None, description="The 30-day implied volatility of the stock."
)
iv30_change: Optional[float] = Field(
default=None, description="Change in 30-day implied volatility of the stock."
)
last_trade_timestamp: Optional[datetime] = Field(
default=None, description="Last trade timestamp for the stock."
)
iv30_annual_high: Optional[float] = Field(
default=None, description="The 1-year high of implied volatility."
)
hv30_annual_high: Optional[float] = Field(
default=None, description="The 1-year high of realized volatility."
)
iv30_annual_low: Optional[float] = Field(
default=None, description="The 1-year low of implied volatility."
)
hv30_annual_low: Optional[float] = Field(
default=None, description="The 1-year low of realized volatility."
)
iv60_annual_high: Optional[float] = Field(
default=None, description="The 60-day high of implied volatility."
)
hv60_annual_high: Optional[float] = Field(
default=None, description="The 60-day high of realized volatility."
)
iv60_annual_low: Optional[float] = Field(
default=None, description="The 60-day low of implied volatility."
)
hv60_annual_low: Optional[float] = Field(
default=None, description="The 60-day low of realized volatility."
)
iv90_annual_high: Optional[float] = Field(
default=None, description="The 90-day high of implied volatility."
)
hv90_annual_high: Optional[float] = Field(
default=None, description="The 90-day high of realized volatility."
)
class CboeEquityInfoFetcher(
Fetcher[
CboeEquityInfoQueryParams,
List[CboeEquityInfoData],
]
):
"""Transform the query, extract and transform the data from the CBOE endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> CboeEquityInfoQueryParams:
"""Transform the query."""
return CboeEquityInfoQueryParams(**params)
@staticmethod
def extract_data(
query: CboeEquityInfoQueryParams, # pylint: disable=unused-argument
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint."""
results = []
INDEXES = get_cboe_index_directory().index.to_list()
SYMBOLS = get_cboe_directory()
def get_one(symbol):
data = pd.Series(dtype="object")
if symbol in SYMBOLS.index or symbol in INDEXES:
_info = pd.Series(get_ticker_info(symbol))
_iv = pd.Series(get_ticker_iv(symbol))
data = (
pd.DataFrame(pd.concat([_info, _iv]))
.transpose()
.drop(columns="seqno")
.iloc[0]
)
results.append(data.to_dict())
with concurrent.futures.ThreadPoolExecutor() as executor:
executor.map(get_one, query.symbol.split(","))
return (
pd.DataFrame.from_records(results)
.sort_values(by="symbol")
.to_dict("records")
)
@staticmethod
def transform_data(
query: CboeEquityInfoQueryParams, data: List[Dict], **kwargs: Any
) -> List[CboeEquityInfoData]:
"""Transform the data to the standard format."""
return [CboeEquityInfoData.model_validate(d) for d in data]

View File

@@ -0,0 +1,244 @@
"""Cboe Equity Info Model."""
# pylint: disable=invalid-name,too-many-locals, expression-not-assigned
from typing import Any, Dict, List, Optional
from openbb_cboe.utils.helpers import (
TICKER_EXCEPTIONS,
get_company_directory,
get_index_directory,
)
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_quote import (
EquityQuoteData,
EquityQuoteQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_requests
from pandas import DataFrame, concat
from pydantic import Field
class CboeEquityQuoteQueryParams(EquityQuoteQueryParams):
"""CBOE Equity Quote Query.
Source: https://www.cboe.com/
"""
use_cache: bool = Field(
default=True,
description="When True, the company directories will be cached for"
+ " 24 hours and are used to validate symbols."
+ " The results of the function are not cached. Set as False to bypass.",
)
class CboeEquityQuoteData(EquityQuoteData):
"""CBOE Equity Quote Data."""
__alias_dict__ = {
"last_timestamp": "last_trade_time",
"prev_close": "prev_day_close",
"asset_type": "security_type",
"last_price": "current_price",
"year_high": "annual_high",
"year_low": "annual_low",
"last_tick": "tick",
"change": "price_change",
"change_percent": "price_change_percent",
}
iv30: Optional[float] = Field(
default=None, description="The 30-day implied volatility of the stock."
)
iv30_change: Optional[float] = Field(
default=None, description="Change in 30-day implied volatility of the stock."
)
iv30_change_percent: Optional[float] = Field(
default=None,
description="Change in 30-day implied volatility of the"
+ " stock as a normalized percentage value.",
)
iv30_annual_high: Optional[float] = Field(
default=None, description="The 1-year high of 30-day implied volatility."
)
hv30_annual_high: Optional[float] = Field(
default=None, description="The 1-year high of 30-day realized volatility."
)
iv30_annual_low: Optional[float] = Field(
default=None, description="The 1-year low of 30-day implied volatility."
)
hv30_annual_low: Optional[float] = Field(
default=None, description="The 1-year low of 30-dayrealized volatility."
)
iv60_annual_high: Optional[float] = Field(
default=None, description="The 1-year high of 60-day implied volatility."
)
hv60_annual_high: Optional[float] = Field(
default=None, description="The 1-year high of 60-day realized volatility."
)
iv60_annual_low: Optional[float] = Field(
default=None, description="The 1-year low of 60-day implied volatility."
)
hv60_annual_low: Optional[float] = Field(
default=None, description="The 1-year low of 60-day realized volatility."
)
iv90_annual_high: Optional[float] = Field(
default=None, description="The 1-year high of 90-day implied volatility."
)
hv90_annual_high: Optional[float] = Field(
default=None, description="The 1-year high of 90-day realized volatility."
)
iv90_annual_low: Optional[float] = Field(
default=None, description="The 1-year low of 90-day implied volatility."
)
hv90_annual_low: Optional[float] = Field(
default=None, description="The 1-year low of 90-day realized volatility."
)
class CboeEquityQuoteFetcher(
Fetcher[
CboeEquityQuoteQueryParams,
List[CboeEquityQuoteData],
]
):
"""Transform the query, extract and transform the data from the CBOE endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> CboeEquityQuoteQueryParams:
"""Transform the query."""
return CboeEquityQuoteQueryParams(**params)
@staticmethod
async def aextract_data(
query: CboeEquityQuoteQueryParams, # pylint: disable=unused-argument
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> DataFrame:
"""Return the raw data from the Cboe endpoint."""
symbols = query.symbol.split(",")
# First get the index and company directories so we know how to handle the ticker symbols.
# Using cache for faster response times.
SYMBOLS = await get_company_directory(use_cache=query.use_cache, **kwargs)
INDEXES = await get_index_directory(use_cache=query.use_cache, **kwargs)
INDEXES = INDEXES.set_index("index_symbol")
# Create a list of European indices.
EU_INDEXES = INDEXES[INDEXES["source"] == "eu_proprietary_index"]
# Check all symbols and create a list of URLs to request.
urls = []
for symbol in symbols:
base_url = "https://cdn.cboe.com/api/global/delayed_quotes/quotes/"
url = (
f"{base_url}_{symbol.replace('^', '')}.json"
if symbol.replace("^", "") in INDEXES.index
or symbol.replace("^", "") in TICKER_EXCEPTIONS
else f"{base_url}{symbol.replace('^', '')}.json"
)
# European Indices require a different endpoint.
if symbol in EU_INDEXES.index:
eu_name = EU_INDEXES.at[symbol, "name"]
_symbol = EU_INDEXES[EU_INDEXES["name"].str.contains(eu_name)].index[0]
url = (
"https://cdn.cboe.com/api/global/european_indices/"
+ f"index_quotes/{_symbol.replace('^', '')}.json"
)
urls.append(url)
# Now make the requests.
responses = await amake_requests(urls)
if not responses:
raise EmptyDataError()
quotes_data = [d["data"] for d in responses]
# There is no context for this data so we'll remove it.
[d.pop("seqno") for d in quotes_data if "seqno" in d]
[d.pop("exchange_id") for d in quotes_data if "exchange_id" in d]
quotes = DataFrame(quotes_data)
quotes.symbol = quotes.symbol.str.replace("^", "")
quotes.symbol = [s.split("-")[0] for s in quotes.symbol]
# Drop an additional symbol column from EU Indices.
if "index" in quotes.columns:
quotes = quotes.drop(columns="index")
quotes = DataFrame(quotes).set_index("symbol")
# Now get the URLs for the IV data.
base_url = "https://cdn.cboe.com/api/global/delayed_quotes/historical_data/"
iv_urls = []
for symbol in symbols:
iv_url = (
base_url + f"_{symbol.replace('^', '')}.json"
if symbol.replace("^", "") in TICKER_EXCEPTIONS
or symbol.replace("^", "") in INDEXES.index
else base_url + f"{symbol.replace('^', '')}.json"
)
# There is no IV data for the EU Indices, so we'll skip those symbols.
if symbol not in EU_INDEXES.index:
iv_urls.append(iv_url)
# While iterating through the symbols, grab the name belonging to the ticker.
if symbol.replace("^", "") in SYMBOLS.index:
quotes.loc[symbol.replace("^", ""), "name"] = SYMBOLS.loc[
symbol.replace("^", ""), "name"
]
if symbol.replace("^", "") in INDEXES.index:
quotes.loc[symbol.replace("^", ""), "name"] = INDEXES.loc[
symbol.replace("^", ""), "name"
]
if symbol.replace("^", "") in EU_INDEXES.index:
quotes.loc[symbol.replace("^", ""), "name"] = EU_INDEXES.loc[
symbol.replace("^", ""), "name"
]
# Now get the IV data.
iv = DataFrame()
iv_responses = await amake_requests(iv_urls)
if iv_responses:
iv_data = [d["data"] for d in iv_responses]
iv = DataFrame(iv_data)
iv["symbol"] = iv["symbol"].astype(str).str.replace("^", "")
iv = iv.set_index("symbol")
if not iv_responses:
iv = DataFrame()
# Merge the IV data with the quotes data.
results = concat([quotes, iv], axis=1)
if len(results) == 0:
raise EmptyDataError()
return results
@staticmethod
def transform_data(
query: CboeEquityQuoteQueryParams, data: DataFrame, **kwargs: Any
) -> List[CboeEquityQuoteData]:
"""Transform the data to the standard format."""
data = data.replace(0, None).dropna(how="all", axis=1)
# We need to convert the percent columns to normalized values.
percent_cols = [
"price_change_percent",
"iv30",
"iv30_change",
"iv30_change_percent",
"iv30_annual_high",
"iv30_annual_low",
"iv60_annual_high",
"iv60_annual_low",
"iv90_annual_high",
"iv90_annual_low",
"hv30_annual_high",
"hv30_annual_low",
"hv60_annual_high",
"hv60_annual_low",
"hv90_annual_high",
"hv90_annual_low",
]
for col in percent_cols:
if col in data.columns:
data[col] = data[col] / 100
data = data.dropna(how="all", axis=1).fillna("N/A").replace("N/A", None)
return [
CboeEquityQuoteData.model_validate(d)
for d in data.reset_index().to_dict("records")
]

View File

@@ -2,7 +2,7 @@
from typing import Any, Dict, List, Optional
from openbb_cboe.utils.helpers import get_cboe_directory
from openbb_cboe.utils.helpers import get_company_directory
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_search import (
EquitySearchData,
@@ -17,12 +17,16 @@ class CboeEquitySearchQueryParams(EquitySearchQueryParams):
Source: https://www.cboe.com/
"""
use_cache: bool = Field(
default=True,
description="When True, the company directory of optionable tickers will be cached for 24 hours."
+ " Set as False to bypass.",
)
class CboeEquitySearchData(EquitySearchData):
"""CBOE Equity Search Data."""
__alias_dict__ = {"name": "Company Name"}
dpm_name: Optional[str] = Field(
default=None,
description="Name of the primary market maker.",
@@ -47,14 +51,15 @@ class CboeEquitySearchFetcher(
return CboeEquitySearchQueryParams(**params)
@staticmethod
def extract_data( # pylint: disable=unused-argument
async def aextract_data(
query: CboeEquitySearchQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> Dict:
"""Return the raw data from the CBOE endpoint."""
data = {}
symbols = get_cboe_directory().reset_index().replace("nan", None)
symbols = await get_company_directory(query.use_cache, **kwargs)
symbols = symbols.reset_index()
target = "name" if query.is_symbol is False else "symbol"
idx = symbols[target].str.contains(query.query, case=False)
result = symbols[idx].to_dict("records")

View File

@@ -1,116 +0,0 @@
"""CBOE European Index Constituents Model."""
from datetime import datetime
from typing import Any, Dict, List, Optional
import pandas as pd
from openbb_cboe.utils.helpers import EUR_INDEX_CONSTITUENTS_COLUMNS, Europe
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.european_index_constituents import (
EuropeanIndexConstituentsData,
EuropeanIndexConstituentsQueryParams,
)
from openbb_core.provider.utils.helpers import make_request
from pydantic import Field, field_validator
class CboeEuropeanIndexConstituentsQueryParams(EuropeanIndexConstituentsQueryParams):
"""CBOE European Index Constituents Query.
Source: https://www.cboe.com/
Gets the current price data for all constituents of the CBOE European Index.
"""
class CboeEuropeanIndexConstituentsData(EuropeanIndexConstituentsData):
"""CBOE European Index Constituents Data.
Current trading day price data for all constituents of the CBOE European Index.
"""
prev_close: Optional[float] = Field(
default=None, description="Previous closing price."
)
change: Optional[float] = Field(default=None, description="Change in price.")
change_percent: Optional[float] = Field(
default=None, description="Change in price as a percentage."
)
tick: Optional[str] = Field(
default=None, description="Whether the last sale was an up or down tick."
)
last_trade_timestamp: Optional[datetime] = Field(
default=None, description="Last trade timestamp for the symbol."
)
exchange_id: Optional[int] = Field(
default=None, description="The Exchange ID number."
)
seqno: Optional[int] = Field(
default=None, description="Sequence number of the last trade on the tape."
)
asset_type: Optional[str] = Field(
default=None, description="Type of asset.", alias="type"
)
@field_validator("last_trade_timestamp", mode="before", check_fields=False)
def date_validate(cls, v): # pylint: disable=E0213
"""Return the datetime object from the date string."""
return datetime.strftime(v, "%Y-%m-%d %H:%M:%S")
class CboeEuropeanIndexConstituentsFetcher(
Fetcher[
CboeEuropeanIndexConstituentsQueryParams,
List[CboeEuropeanIndexConstituentsData],
]
):
"""Transform the query, extract and transform the data from the CBOE endpoints."""
@staticmethod
def transform_query(
params: Dict[str, Any]
) -> CboeEuropeanIndexConstituentsQueryParams:
"""Transform the query."""
return CboeEuropeanIndexConstituentsQueryParams(**params)
@staticmethod
def extract_data(
query: CboeEuropeanIndexConstituentsQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint."""
SYMBOLS = pd.DataFrame(Europe.list_indices())["symbol"].to_list()
query.symbol = query.symbol.upper()
if query.symbol not in SYMBOLS:
raise RuntimeError(
f"The symbol, {query.symbol},"
"was not found in the CBOE European Index directory. "
"Use `available_indices(europe=True)` to see the full list of indices."
)
url = f"https://cdn.cboe.com/api/global/european_indices/constituent_quotes/{query.symbol}.json"
r = make_request(url)
if r.status_code != 200:
raise RuntimeError(r.status_code)
r_json = r.json()
data = (
pd.DataFrame.from_records(r_json["data"])[
list(EUR_INDEX_CONSTITUENTS_COLUMNS.keys())
]
.rename(columns=EUR_INDEX_CONSTITUENTS_COLUMNS)
.round(2)
)
data["last_trade_timestamp"] = pd.to_datetime(data["last_trade_timestamp"])
return data.to_dict("records")
@staticmethod
def transform_data(
query: CboeEuropeanIndexConstituentsQueryParams, data: List[Dict], **kwargs: Any
) -> List[CboeEuropeanIndexConstituentsData]:
"""Transform the data to the standard format."""
return [CboeEuropeanIndexConstituentsData.model_validate(d) for d in data]

View File

@@ -1,142 +0,0 @@
"""CBOE European Indices Model."""
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
import pandas as pd
from dateutil.relativedelta import relativedelta
from openbb_cboe.utils.helpers import Europe
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.european_indices import (
EuropeanIndicesData,
EuropeanIndicesQueryParams,
)
from openbb_core.provider.utils.helpers import make_request
from pydantic import Field, field_validator
class CboeEuropeanIndicesQueryParams(EuropeanIndicesQueryParams):
"""CBOE European Indices Query.
Source: https://www.cboe.com/europe/indices/
"""
interval: Literal["1d", "1m"] = Field(description="Data granularity.", default="1d")
class CboeEuropeanIndicesData(EuropeanIndicesData):
"""CBOE European Indices Data."""
open: Optional[float] = Field(
default=None,
description="Opening price for the interval. Only valid when interval is 1m.",
)
high: Optional[float] = Field(
default=None,
description="High price for the interval. Only valid when interval is 1m.",
)
low: Optional[float] = Field(
default=None,
description="Low price for the interval. Only valid when interval is 1m.",
)
utc_datetime: Optional[datetime] = Field(
default=None, description="UTC datetime. Only valid when interval is 1m."
)
@field_validator("date", mode="before", check_fields=False)
def date_validate(cls, v): # pylint: disable=E0213
"""Return datetime object from string."""
try:
return datetime.strptime(v, "%Y-%m-%d")
except Exception:
return datetime.strptime(v, "%Y-%m-%dT%H:%M:%S")
class CboeEuropeanIndicesFetcher(
Fetcher[
CboeEuropeanIndicesQueryParams,
List[CboeEuropeanIndicesData],
]
):
"""Transform the query, extract and transform the data from the CBOE endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> CboeEuropeanIndicesQueryParams:
"""Transform the query."""
now = datetime.now().date()
transformed_params = params
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
return CboeEuropeanIndicesQueryParams(**transformed_params)
@staticmethod
def extract_data(
query: CboeEuropeanIndicesQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint."""
data = pd.DataFrame()
query.symbol = query.symbol.upper()
SYMBOLS = pd.DataFrame(Europe.list_indices())["symbol"].to_list()
if query.symbol not in SYMBOLS:
raise RuntimeError(
f"The symbol, {query.symbol},"
"was not found in the CBOE European Indices directory. "
"Use `available_indices(europe=True)` to see the full list of indices."
)
if query.interval == "1d":
url = f"https://cdn.cboe.com/api/global/european_indices/index_history/{query.symbol}.json"
r = make_request(url)
if r.status_code != 200:
raise RuntimeError(r.status_code)
r_json = r.json()
data = pd.DataFrame.from_records(r_json["data"]).set_index("date")
data.index = pd.to_datetime(data.index, format="%Y-%m-%d")
if query.start_date is not None:
query.end_date = (
query.end_date if query.end_date else datetime.now().date()
)
data = data[
(data.index >= pd.to_datetime(query.start_date, format="%Y-%m-%d"))
& (data.index <= pd.to_datetime(query.end_date, format="%Y-%m-%d"))
]
data.index = data.index.astype(str)
data = data.reset_index()
if query.interval == "1m":
url = f"https://cdn.cboe.com/api/global/european_indices/intraday_chart_data/{query.symbol}.json"
r = make_request(url)
if r.status_code != 200:
raise RuntimeError(r.status_code)
r_json = r.json()["data"]
data = pd.DataFrame.from_records(pd.DataFrame(r_json)["price"])
data["date"] = pd.DataFrame(r_json)["datetime"]
data["utc_datetime"] = pd.to_datetime(pd.DataFrame(r_json)["utc_datetime"])
data = data[["utc_datetime", "date", "open", "high", "low", "close"]].round(
2
)
return data.to_dict("records")
@staticmethod
def transform_data(
query: CboeEuropeanIndicesQueryParams, data: List[Dict], **kwargs: Any
) -> List[CboeEuropeanIndicesData]:
"""Transform the data to the standard format."""
return [CboeEuropeanIndicesData.model_validate(d) for d in data]

View File

@@ -9,6 +9,7 @@ from openbb_core.provider.standard_models.futures_curve import (
FuturesCurveData,
FuturesCurveQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from pydantic import Field
@@ -38,30 +39,28 @@ class CboeFuturesCurveFetcher(
return CboeFuturesCurveQueryParams(**params)
@staticmethod
def extract_data(
async def aextract_data(
query: CboeFuturesCurveQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint."""
query.symbol = query.symbol.upper()
FUTURES = get_settlement_prices(settlement_date=query.date)
symbol = query.symbol.upper().split(",")[0]
FUTURES = await get_settlement_prices(**kwargs)
if len(FUTURES) == 0:
return []
raise EmptyDataError()
if query.symbol not in FUTURES["product"].unique().tolist():
if symbol not in FUTURES["product"].unique().tolist():
raise RuntimeError(
"The symbol, "
f"{query.symbol}"
f"{symbol}"
", is not valid. Chose from: "
f"{FUTURES['product'].unique().tolist()}"
)
data = FUTURES[FUTURES["product"] == symbol][["expiration", "symbol", "price"]]
data = get_settlement_prices(settlement_date=query.date)
data = data[data["product"] == query.symbol]
return data[["expiration", "symbol", "price"]].to_dict("records")
return data.to_dict("records")
@staticmethod
def transform_data(

View File

@@ -0,0 +1,130 @@
"""Cboe Index Constituents Model."""
# pylint: disable=unused-argument
from datetime import datetime
from typing import Any, Dict, List, Optional
from openbb_cboe.utils.helpers import CONSTITUENTS_EU
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.index_constituents import (
IndexConstituentsData,
IndexConstituentsQueryParams,
)
from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request
from pandas import DataFrame
from pydantic import Field, field_validator
class CboeIndexConstituentsQueryParams(IndexConstituentsQueryParams):
"""Cboe Index Constituents Query.
Source: https://www.cboe.com/
Gets the current price data for all constituents of the CBOE European Index.
"""
index: CONSTITUENTS_EU = Field(default="BUK100P")
class CboeIndexConstituentsData(IndexConstituentsData):
"""Cboe Index Constituents Data.
Current trading day price data for all constituents of the Cboe proprietary index.
"""
__alias_dict__ = {
"prev_close": "prev_day_close",
"change": "price_change",
"change_percent": "price_change_percent",
"last_price": "current_price",
}
security_type: Optional[str] = Field(
default=None, description="The type of security represented."
)
last_price: Optional[float] = Field(
default=None, description="Last price for the symbol."
)
open: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("open", "")
)
high: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("high", "")
)
low: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("low", "")
)
close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("close", "")
)
volume: Optional[int] = Field(
default=None, description=DATA_DESCRIPTIONS.get("volume", "")
)
prev_close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("prev_close", "")
)
change: Optional[float] = Field(default=None, description="Change in price.")
change_percent: Optional[float] = Field(
default=None, description="Change in price as a normalized percentage."
)
tick: Optional[str] = Field(
default=None, description="Whether the last sale was an up or down tick."
)
last_trade_time: Optional[datetime] = Field(
default=None, description="Last trade timestamp for the symbol."
)
asset_type: Optional[str] = Field(
default=None, description="Type of asset.", alias="type"
)
@field_validator("last_trade_time", mode="before", check_fields=False)
@classmethod
def date_validate(cls, v): # pylint: disable=E0213
"""Return the datetime object from the date string."""
return datetime.strptime(v, "%Y-%m-%dT%H:%M:%S")
class CboeIndexConstituentsFetcher(
Fetcher[
CboeIndexConstituentsQueryParams,
List[CboeIndexConstituentsData],
]
):
"""Transform the query, extract and transform the data from the CBOE endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> CboeIndexConstituentsQueryParams:
"""Transform the query."""
return CboeIndexConstituentsQueryParams(**params)
@staticmethod
async def aextract_data(
query: CboeIndexConstituentsQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the Cboe endpoint."""
url = (
"https://cdn.cboe.com/api/global/european_indices"
f"/constituent_quotes/{query.index}.json"
)
data = await amake_request(url)
return data.get("data")
@staticmethod
def transform_data(
query: CboeIndexConstituentsQueryParams, data: List[Dict], **kwargs: Any
) -> List[CboeIndexConstituentsData]:
"""Transform the data to the standard format."""
if not data:
raise EmptyDataError()
data = DataFrame(data)
data["price_change_percent"] = data["price_change_percent"] / 100
data = data.replace(0, None).dropna(how="all", axis=1)
data = data.drop(columns=["exchange_id"])
return [
CboeIndexConstituentsData.model_validate(d)
for d in data.to_dict(orient="records")
]

View File

@@ -1,37 +1,54 @@
"""CBOE Index Historical Model."""
"""Cboe Market Indices Model."""
import warnings
from datetime import datetime, timedelta
from typing import Any, Dict, List, Literal, Optional
import pandas as pd
from openbb_cboe.utils.helpers import (
TICKER_EXCEPTIONS,
get_cboe_index_directory,
get_ticker_info,
get_index_directory,
)
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.index_historical import (
IndexHistoricalData,
IndexHistoricalQueryParams,
)
from openbb_core.provider.utils.helpers import make_request
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_requests
from pandas import DataFrame, Series, concat, to_datetime
from pydantic import Field
_warn = warnings.warn
class CboeIndexHistoricalQueryParams(IndexHistoricalQueryParams):
"""CBOE Index Historical Query.
"""CBOE Market Indices Query.
Source: https://www.cboe.com/
"""
interval: Literal["1d", "1m"] = Field(
description="Use interval, 1m, for intraday prices during the most recent trading period.",
interval: Literal["1m", "1d"] = Field(
default="1d",
description=(
QUERY_DESCRIPTIONS.get("interval", "")
+ " The most recent trading day is not including in daily historical data."
+ " Intraday data is only available for the most recent trading day at 1 minute intervals."
),
)
use_cache: bool = Field(
default=True,
description="When True, the company directories will be cached for 24 hours and are used to validate symbols."
+ " The results of the function are not cached. Set as False to bypass.",
)
class CboeIndexHistoricalData(IndexHistoricalData):
"""CBOE Index Historical Data."""
"""CBOE Market Indices Data."""
__alias_dict__ = {
"volume": "stock_volume",
}
calls_volume: Optional[float] = Field(
default=None,
@@ -57,156 +74,135 @@ class CboeIndexHistoricalFetcher(
@staticmethod
def transform_query(params: Dict[str, Any]) -> CboeIndexHistoricalQueryParams:
"""Transform the query. Setting the start and end dates for a 1 year period."""
return CboeIndexHistoricalQueryParams(**params)
"""Transform the query."""
transformed_params = params.copy()
now = datetime.now()
if (
len(params.get("symbol", "").split(",")) > 1
and params.get("start_date") is None
):
transformed_params["start_date"] = (
transformed_params["start_date"]
if transformed_params["start_date"]
else (now - timedelta(days=720)).strftime("%Y-%m-%d")
)
if transformed_params.get("start_date") is None:
transformed_params["start_date"] = (
transformed_params["start_date"]
if transformed_params.get("start_date")
else "1950-01-01"
)
if params.get("end_date") is None:
transformed_params["end_date"] = (
transformed_params["end_date"]
if transformed_params.get("end_date")
else now.strftime("%Y-%m-%d")
)
return CboeIndexHistoricalQueryParams(**transformed_params)
@staticmethod
def extract_data(
query: CboeIndexHistoricalQueryParams, # pylint: disable=unused-argument
async def aextract_data(
query: CboeIndexHistoricalQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint."""
# Symbol directories are cached for seven days and are used for error handling and URL generation.
INDEXES = get_cboe_index_directory().index.to_list()
query.symbol = query.symbol.upper()
data = pd.DataFrame()
if "^" in query.symbol:
query.symbol = query.symbol.replace("^", "")
query.interval = (
"1m" if query.symbol == "NDX" and query.interval == "1d" else query.interval
)
"""Return the raw data from the Cboe endpoint."""
now = datetime.now()
query.start_date = (
query.start_date if query.start_date else now - timedelta(days=50000)
)
query.end_date = query.end_date if query.end_date else now
symbols = query.symbol.split(",")
INDEXES = await get_index_directory(use_cache=query.use_cache)
INDEXES = INDEXES.set_index("index_symbol")
# Create a list of European indices.
EU_INDEXES = INDEXES[INDEXES["source"] == "eu_proprietary_index"]
if query.symbol not in INDEXES and query.symbol not in TICKER_EXCEPTIONS:
raise RuntimeError(
f"The symbol, {query.symbol}, was not found in the CBOE index directory. "
"Use `index_search()` to find supported indices. If the index is European, try `european_index()`."
)
INTERVAL_DICT = {"1m": "intraday", "1d": "historical"}
def __generate_historical_prices_url(
def _generate_historical_prices_url(
symbol,
data_type: Optional[Literal["intraday", "historical"]] = "historical",
interval_type: Literal["intraday", "historical"] = "historical",
) -> str:
"""Generate the final URL for historical prices data."""
url: str = (
f"https://cdn.cboe.com/api/global/delayed_quotes/charts/{data_type}"
)
url = (
url + f"/_{symbol}.json"
if symbol in TICKER_EXCEPTIONS or symbol in INDEXES
else url + f"/{symbol}.json"
)
"""Generate the URL for the data."""
if symbol.replace("^", "") in TICKER_EXCEPTIONS:
interval_type = "intraday" if len(symbols) == 1 else "historical"
_warn(
"Only the most recent trading day is available for this ticker, "
+ symbol
)
if symbol.replace("^", "") in EU_INDEXES.index:
base_url = "https://cdn.cboe.com/api/global/european_indices/"
url = (
base_url + "index_history/"
if interval_type == "historical"
else base_url + "intraday_chart_data/"
)
url += f"{symbol.replace('^', '')}.json"
else:
base_url: str = f"https://cdn.cboe.com/api/global/delayed_quotes/charts/{interval_type}"
url = (
base_url + f"/_{symbol.replace('^', '')}.json"
if symbol.replace("^", "") in TICKER_EXCEPTIONS
or symbol.replace("^", "") in INDEXES.index
else base_url + f"/{symbol.replace('^', '')}.json"
)
return url
url = (
__generate_historical_prices_url(query.symbol, "intraday")
if query.interval == "1m"
else __generate_historical_prices_url(query.symbol)
)
r = make_request(url)
urls = [
_generate_historical_prices_url(symbol, INTERVAL_DICT[query.interval])
for symbol in symbols
]
if r.status_code != 200:
raise RuntimeError(r.status_code)
if query.interval == "1d":
data = (
pd.DataFrame(r.json()["data"])[
["date", "open", "high", "low", "close", "volume"]
]
).set_index("date")
# Fill in missing data from current or most recent trading session.
today = pd.to_datetime(datetime.now().date())
if today.weekday() > 4:
day_minus = today.weekday() - 4
today = pd.to_datetime(today - timedelta(days=day_minus))
if today != data.index[-1]:
_today = pd.Series(get_ticker_info(query.symbol))
today_df = pd.Series(dtype="object")
today_df["open"] = round(_today["open"], 2)
today_df["high"] = round(_today["high"], 2)
today_df["low"] = round(_today["low"], 2)
today_df["close"] = round(_today["close"], 2)
if (
query.symbol not in INDEXES
and query.symbol not in TICKER_EXCEPTIONS
):
data = data[data["volume"] > 0]
today_df["volume"] = _today["volume"]
today_df["date"] = today.date()
today_df = pd.DataFrame(today_df).transpose().set_index("date")
data = pd.concat([data, today_df], axis=0)
# If ticker is an index there is no volume data and the types must be set.
if query.symbol in INDEXES or query.symbol in TICKER_EXCEPTIONS:
data = data[["open", "high", "low", "close", "volume"]]
data["open"] = round(data.open.astype(float), 2)
data["high"] = round(data.high.astype(float), 2)
data["low"] = round(data.low.astype(float), 2)
data["close"] = round(data.close.astype(float), 2)
data["volume"] = 0
data.index = pd.to_datetime(data.index)
data = data[data["open"] > 0]
data = data[
(data.index >= pd.to_datetime(query.start_date))
& (data.index <= pd.to_datetime(query.end_date))
]
if query.interval == "1m":
data_list = r.json()["data"]
date: List[datetime] = []
open_: List[float] = []
high: List[float] = []
low: List[float] = []
close: List[float] = []
volume: List[float] = []
calls_volume: List[float] = []
puts_volume: List[float] = []
total_options_volume: List[float] = []
for data in data_list:
date.append(data["datetime"])
price = data["price"]
volume_data = data["volume"]
open_.append(price["open"])
high.append(price["high"])
low.append(price["low"])
close.append(price["close"])
volume.append(volume_data["stock_volume"])
calls_volume.append(volume_data["calls_volume"])
puts_volume.append(volume_data["puts_volume"])
total_options_volume.append(volume_data["total_options_volume"])
data = pd.DataFrame()
data["date"] = pd.to_datetime(date)
data["open"] = open_
data["high"] = high
data["low"] = low
data["close"] = close
data["volume"] = volume
data["calls_volume"] = calls_volume
data["puts_volume"] = puts_volume
data["total_options_volume"] = total_options_volume
data = data.set_index("date").sort_index()
data.index = data.index.astype(str)
data = data[data["open"] > 0]
return data.reset_index().to_dict("records")
return await amake_requests(urls, **kwargs)
@staticmethod
def transform_data(
query: CboeIndexHistoricalQueryParams, data: List[Dict], **kwargs: Any
) -> List[CboeIndexHistoricalData]:
"""Transform the data to the standard format."""
return [CboeIndexHistoricalData.model_validate(d) for d in data]
if not data:
raise EmptyDataError()
results = DataFrame()
symbols = query.symbol.split(",")
# Results will be different depending on the interval.
# We will also parse the output from multiple symbols.
for i, item in enumerate(data):
result = DataFrame()
_symbol = symbols[i]
_temp = item["data"]
if query.interval == "1d":
result = DataFrame(_temp)
result["symbol"] = _symbol.replace("_", "").replace("^", "")
result = result.set_index("date")
# Remove the volume column if it exists because volume will a string 0.
if "volume" in result.columns:
result = result.drop(columns="volume")
results = concat([results, result])
if query.interval == "1m":
_datetime = Series([d["datetime"] for d in _temp]).rename("date")
_price = DataFrame(d["price"] for d in _temp)
result = _price.join(_datetime)
result["symbol"] = _symbol.replace("_", "").replace("^", "")
result = result.set_index("date")
results = concat([results, result])
results = results.set_index("symbol", append=True).sort_index()
for c in ["open", "high", "low", "close"]:
if c in results.columns:
results[c] = results[c].astype(float).replace(0, None)
output = results.dropna(how="all", axis=1).reset_index()
# When there is only one ticker symbol, the symbol column is redundant.
if len(query.symbol.split(",")) == 1:
output = output.drop(columns="symbol")
# Finally, we apply the user-specified date range because it is not filtered at the source.
output = output[
(to_datetime(output["date"]) >= to_datetime(query.start_date))
& (
to_datetime(output["date"])
<= to_datetime(query.end_date + timedelta(days=1))
)
]
return [
CboeIndexHistoricalData.model_validate(d) for d in output.to_dict("records")
]

View File

@@ -3,8 +3,7 @@
from datetime import time
from typing import Any, Dict, List, Optional
import pandas as pd
from openbb_cboe.utils.helpers import Europe, get_cboe_index_directory
from openbb_cboe.utils.helpers import get_index_directory
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.index_search import (
IndexSearchData,
@@ -19,22 +18,23 @@ class CboeIndexSearchQueryParams(IndexSearchQueryParams):
Source: https://www.cboe.com/
"""
europe: bool = Field(
description="Filter for European indices. False for US indices.", default=False
use_cache: bool = Field(
default=True,
description="When True, the Cboe Index directory will be cached for 24 hours."
+ " Set as False to bypass.",
)
class CboeIndexSearchData(IndexSearchData):
"""CBOE Index Search Data."""
isin: Optional[str] = Field(
description="ISIN code for the index. Valid only for European indices.",
default=None,
)
region: Optional[str] = Field(
description="Region for the index. Valid only for European indices",
default=None,
)
__alias_dict__ = {
"symbol": "index_symbol",
"data_delay": "mkt_data_delay",
"open_time": "calc_start_time",
"close_time": "calc_end_time",
}
description: Optional[str] = Field(
description="Description for the index.", default=None
)
@@ -81,21 +81,25 @@ class CboeIndexSearchFetcher(
return CboeIndexSearchQueryParams(**params)
@staticmethod
def extract_data(
async def aextract_data(
query: CboeIndexSearchQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint."""
symbols = pd.DataFrame()
if query.europe is True:
symbols = pd.DataFrame(Europe.list_indices())
if query.europe is False:
symbols = get_cboe_index_directory().reset_index()
target = "name" if not query.is_symbol else "symbol"
idx = symbols[target].str.contains(query.query, case=False)
result = symbols[idx]
symbols = await get_index_directory(use_cache=query.use_cache, **kwargs)
symbols.drop(columns=["source"], inplace=True)
if query.is_symbol is True:
result = symbols[
symbols["index_symbol"].str.contains(query.query, case=False)
]
else:
result = symbols[
symbols["name"].str.contains(query.query, case=False)
| symbols["index_symbol"].str.contains(query.query, case=False)
| symbols["description"].str.contains(query.query, case=False)
]
return result.to_dict("records")

View File

@@ -1,23 +1,18 @@
"""CBOE Index Snapshots Model."""
from datetime import datetime
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Literal, Optional
import pandas as pd
from openbb_cboe.utils.helpers import (
EUR_INDEX_COLUMNS,
US_INDEX_COLUMNS,
Europe,
get_cboe_directory,
get_cboe_index_directory,
)
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.index_snapshots import (
IndexSnapshotsData,
IndexSnapshotsQueryParams,
)
from openbb_core.provider.utils.helpers import make_request
from pydantic import Field
from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request
from pandas import DataFrame
from pydantic import Field, field_validator
class CboeIndexSnapshotsQueryParams(IndexSnapshotsQueryParams):
@@ -26,16 +21,55 @@ class CboeIndexSnapshotsQueryParams(IndexSnapshotsQueryParams):
Source: https://www.cboe.com/
"""
region: Literal[None, "us", "eu"] = Field(
default="us",
)
@field_validator("region", mode="after", check_fields=False)
@classmethod
def validate_region(cls, v: str):
"""Validate region."""
return "us" if v is None else v
class CboeIndexSnapshotsData(IndexSnapshotsData):
"""CBOE Index Snapshots Data."""
isin: Optional[str] = Field(
default=None,
description="ISIN code for the index. Valid only for European indices.",
__alias_dict__ = {
"prev_close": "prev_day_close",
"change": "price_change",
"change_percent": "price_change_percent",
"price": "current_price",
}
bid: Optional[float] = Field(default=None, description="Current bid price.")
ask: Optional[float] = Field(default=None, description="Current ask price.")
open: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("open", "")
)
last_trade_timestamp: Optional[datetime] = Field(
default=None, description="Last trade timestamp for the index."
high: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("high", "")
)
low: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("low", "")
)
close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("close", "")
)
volume: Optional[int] = Field(
default=None, description=DATA_DESCRIPTIONS.get("volume", "")
)
prev_close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("prev_close", "")
)
change: Optional[float] = Field(default=None, description="Change in price.")
change_percent: Optional[float] = Field(
default=None, description="Change in price as a normalized percentage."
)
last_trade_time: Optional[datetime] = Field(
default=None, description="Last trade timestamp for the symbol."
)
status: Optional[str] = Field(
default=None, description="Status of the market, open or closed."
)
@@ -53,89 +87,57 @@ class CboeIndexSnapshotsFetcher(
return CboeIndexSnapshotsQueryParams(**params)
@staticmethod
def extract_data(
async def aextract_data(
query: CboeIndexSnapshotsQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint"""
data = pd.DataFrame()
"""Return the raw data from the Cboe endpoint"""
if query.region == "US":
r = make_request(
"https://cdn.cboe.com/api/global/delayed_quotes/quotes/all_us_indices.json"
)
if query.region == "us":
url = "https://cdn.cboe.com/api/global/delayed_quotes/quotes/all_us_indices.json"
if query.region == "eu":
url = "https://cdn.cboe.com/api/global/european_indices/index_quotes/all-indices.json"
if r.status_code != 200:
raise RuntimeError(r.status_code)
INDEXES = pd.concat(
[get_cboe_index_directory(), get_cboe_directory()], axis=0
)
data = pd.DataFrame.from_records(r.json()["data"])
data.rename(columns=US_INDEX_COLUMNS, inplace=True)
data["symbol"] = data["symbol"].str.replace("^", "")
data["name"] = ""
data = data.set_index("symbol")
for i in data.index:
if i in INDEXES.index:
data.at[i, "name"] = INDEXES.at[i, "name"]
data["name"] = data["name"].astype(str)
data["currency"] = "USD"
data = data[
[
"name",
"price",
"prev_close",
"change",
"change_percent",
"open",
"high",
"low",
"close",
"currency",
"last_trade_timestamp",
]
]
if query.region == "EU":
r = make_request(
"https://cdn.cboe.com/api/global/european_indices/index_quotes/all-indices.json"
)
if r.status_code != 200:
raise RuntimeError(r.status_code)
data = (
pd.DataFrame.from_records(r.json()["data"])
.drop(columns=["symbol"])
.rename(columns={"index": "symbol"})
.set_index("symbol")
.round(2)
)
INDEXES = pd.DataFrame(Europe.list_indices()).set_index("symbol")
for i in data.index:
data.loc[i, ("isin")] = INDEXES.at[i, "isin"]
data.loc[i, ("name")] = INDEXES.at[i, "name"]
data.loc[i, ("currency")] = INDEXES.at[i, "currency"]
data = data[list(EUR_INDEX_COLUMNS.keys())]
data.columns = list(EUR_INDEX_COLUMNS.values())
data["change_percent"] = data["change_percent"].fillna(0.0)
return data.reset_index().to_dict("records")
data = await amake_request(url, **kwargs)
return data.get("data")
@staticmethod
def transform_data(
query: CboeIndexSnapshotsQueryParams, data: dict, **kwargs: Any
) -> List[CboeIndexSnapshotsData]:
"""Transform the data to the standard format"""
for item in data:
item["name"] = item["name"].replace("/", "-")
return [CboeIndexSnapshotsData.model_validate(d) for d in data]
if not data:
raise EmptyDataError()
data = DataFrame(data)
percent_cols = [
"price_change_percent",
"iv30",
"iv30_change",
"iv30_change_percent",
]
for col in percent_cols:
if col in data.columns:
data[col] = round(data[col] / 100, 6)
data = (
data.replace(0, None)
.replace("", None)
.dropna(how="all", axis=1)
.fillna("N/A")
.replace("N/A", None)
)
drop_cols = [
"exchange_id",
"seqno",
"index",
"security_type",
"ask_size",
"bid_size",
]
for col in drop_cols:
if col in data.columns:
data = data.drop(columns=col)
return [
CboeIndexSnapshotsData.model_validate(d)
for d in data.to_dict(orient="records")
]

View File

@@ -1,211 +0,0 @@
"""CBOE Market Indices Model."""
from datetime import datetime, timedelta
from typing import Any, Dict, List, Literal, Optional
import pandas as pd
from openbb_cboe.utils.helpers import (
TICKER_EXCEPTIONS,
get_cboe_index_directory,
get_ticker_info,
)
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.market_indices import (
MarketIndicesData,
MarketIndicesQueryParams,
)
from openbb_core.provider.utils.helpers import make_request
from pydantic import Field
class CboeMarketIndicesQueryParams(MarketIndicesQueryParams):
"""CBOE Market Indices Query.
Source: https://www.cboe.com/
"""
interval: Literal["1d", "1m"] = Field(
description="Use interval, 1m, for intraday prices during the most recent trading period.",
default="1d",
)
class CboeMarketIndicesData(MarketIndicesData):
"""CBOE Market Indices Data."""
calls_volume: Optional[float] = Field(
default=None,
description="Number of calls traded during the most recent trading period. Only valid if interval is 1m.",
)
puts_volume: Optional[float] = Field(
default=None,
description="Number of puts traded during the most recent trading period. Only valid if interval is 1m.",
)
total_options_volume: Optional[float] = Field(
default=None,
description="Total number of options traded during the most recent trading period. Only valid if interval is 1m.",
)
class CboeMarketIndicesFetcher(
Fetcher[
CboeMarketIndicesQueryParams,
List[CboeMarketIndicesData],
]
):
"""Transform the query, extract and transform the data from the CBOE endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> CboeMarketIndicesQueryParams:
"""Transform the query. Setting the start and end dates for a 1 year period."""
return CboeMarketIndicesQueryParams(**params)
@staticmethod
def extract_data(
query: CboeMarketIndicesQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint."""
# Synbol directories are cached for seven days and are used for error handling and URL generation.
INDEXES = get_cboe_index_directory().index.to_list()
query.symbol = query.symbol.upper()
data = pd.DataFrame()
if "^" in query.symbol:
query.symbol = query.symbol.replace("^", "")
query.interval = (
"1m" if query.symbol == "NDX" and query.interval == "1d" else query.interval
)
now = datetime.now()
query.start_date = (
query.start_date if query.start_date else now - timedelta(days=50000)
)
query.end_date = query.end_date if query.end_date else now
if query.symbol not in INDEXES and query.symbol not in TICKER_EXCEPTIONS:
raise RuntimeError(
f"The symbol, {query.symbol}, was not found in the CBOE index directory. "
"Use `index_search()` to find supported indices. If the index is European, try `european_index()`."
)
def __generate_historical_prices_url(
symbol,
data_type: Optional[Literal["intraday", "historical"]] = "historical",
) -> str:
"""Generate the final URL for historical prices data."""
url: str = (
f"https://cdn.cboe.com/api/global/delayed_quotes/charts/{data_type}"
)
url = (
url + f"/_{symbol}.json"
if symbol in TICKER_EXCEPTIONS or symbol in INDEXES
else url + f"/{symbol}.json"
)
return url
url = (
__generate_historical_prices_url(query.symbol, "intraday")
if query.interval == "1m"
else __generate_historical_prices_url(query.symbol)
)
r = make_request(url)
if r.status_code != 200:
raise RuntimeError(r.status_code)
if query.interval == "1d":
data = (
pd.DataFrame(r.json()["data"])[
["date", "open", "high", "low", "close", "volume"]
]
).set_index("date")
# Fill in missing data from current or most recent trading session.
today = pd.to_datetime(datetime.now().date())
if today.weekday() > 4:
day_minus = today.weekday() - 4
today = pd.to_datetime(today - timedelta(days=day_minus))
if today != data.index[-1]:
_today = pd.Series(get_ticker_info(query.symbol))
today_df = pd.Series(dtype="object")
today_df["open"] = round(_today["open"], 2)
today_df["high"] = round(_today["high"], 2)
today_df["low"] = round(_today["low"], 2)
today_df["close"] = round(_today["close"], 2)
if (
query.symbol not in INDEXES
and query.symbol not in TICKER_EXCEPTIONS
):
data = data[data["volume"] > 0]
today_df["volume"] = _today["volume"]
today_df["date"] = today.date()
today_df = pd.DataFrame(today_df).transpose().set_index("date")
data = pd.concat([data, today_df], axis=0)
# If ticker is an index there is no volume data and the types must be set.
if query.symbol in INDEXES or query.symbol in TICKER_EXCEPTIONS:
data = data[["open", "high", "low", "close", "volume"]]
data["open"] = round(data.open.astype(float), 2)
data["high"] = round(data.high.astype(float), 2)
data["low"] = round(data.low.astype(float), 2)
data["close"] = round(data.close.astype(float), 2)
data["volume"] = 0
data.index = pd.to_datetime(data.index)
data = data[data["open"] > 0]
data = data[
(data.index >= pd.to_datetime(query.start_date))
& (data.index <= pd.to_datetime(query.end_date))
]
if query.interval == "1m":
data_list = r.json()["data"]
date: List[datetime] = []
open_: List[float] = []
high: List[float] = []
low: List[float] = []
close: List[float] = []
volume: List[float] = []
calls_volume: List[float] = []
puts_volume: List[float] = []
total_options_volume: List[float] = []
for i in range(0, len(data_list)):
date.append(data_list[i]["datetime"])
open_.append(data_list[i]["price"]["open"])
high.append(data_list[i]["price"]["high"])
low.append(data_list[i]["price"]["low"])
close.append(data_list[i]["price"]["close"])
volume.append(data_list[i]["volume"]["stock_volume"])
calls_volume.append(data_list[i]["volume"]["calls_volume"])
puts_volume.append(data_list[i]["volume"]["puts_volume"])
total_options_volume.append(
data_list[i]["volume"]["total_options_volume"]
)
data = pd.DataFrame()
data["date"] = pd.to_datetime(date)
data["open"] = open_
data["high"] = high
data["low"] = low
data["close"] = close
data["volume"] = volume
data["calls_volume"] = calls_volume
data["puts_volume"] = puts_volume
data["total_options_volume"] = total_options_volume
data = data.set_index("date").sort_index()
data.index = data.index.astype(str)
data = data[data["open"] > 0]
return data.reset_index().to_dict("records")
@staticmethod
def transform_data(
query: CboeMarketIndicesQueryParams, data: List[Dict], **kwargs: Any
) -> List[CboeMarketIndicesData]:
"""Transform the data to the standard format."""
return [CboeMarketIndicesData.model_validate(d) for d in data]

View File

@@ -1,21 +1,23 @@
"""CBOE Options Chains Model."""
"""Cboe Options Chains Model."""
# pylint: disable=invalid-name, unused-argument
from datetime import datetime
from typing import Any, Dict, List, Optional
import pandas as pd
from openbb_cboe.utils.helpers import (
TICKER_EXCEPTIONS,
get_cboe_directory,
get_cboe_index_directory,
get_company_directory,
get_index_directory,
)
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.options_chains import (
OptionsChainsData,
OptionsChainsQueryParams,
)
from openbb_core.provider.utils.helpers import make_request
from pydantic import Field, field_validator
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request
from pandas import DataFrame, DatetimeIndex, Series, to_datetime
from pydantic import Field
class CboeOptionsChainsQueryParams(OptionsChainsQueryParams):
@@ -24,42 +26,22 @@ class CboeOptionsChainsQueryParams(OptionsChainsQueryParams):
Source: https://www.cboe.com/
"""
use_cache: bool = Field(
default=True,
description="When True, the company directories will be cached for"
+ "24 hours and are used to validate symbols."
+ " The results of the function are not cached. Set as False to bypass.",
)
class CboeOptionsChainsData(OptionsChainsData):
"""CBOE Options Chains Data."""
bid_size: int = Field(description="Bid size for the option.")
ask_size: int = Field(description="Ask size for the option.")
theoretical: float = Field(description="Theoretical value of the option.")
open: float = Field(description="Opening price of the option.")
high: float = Field(description="High price of the option.")
low: float = Field(description="Low price of the option.")
last_trade_price: Optional[float] = Field(
description="Last trade price of the option.", default=None
)
tick: Optional[str] = Field(
description="Whether the last tick was up or down in price.", default=None
)
prev_close: float = Field(description="Previous closing price of the option.")
change: float = Field(description="Change in price of the option.")
change_percent: float = Field(description="Change, in percent, of the option.")
implied_volatility: float = Field(description="Implied volatility of the option.")
delta: float = Field(description="Delta of the option.")
gamma: float = Field(description="Gamma of the option.")
vega: float = Field(description="Vega of the option.")
theta: float = Field(description="Theta of the option.")
rho: float = Field(description="Rho of the option.")
last_trade_timestamp: Optional[datetime] = Field(
description="Last trade timestamp of the option.", default=None
)
dte: int = Field(description="Days to expiration for the option.")
@field_validator("expiration", mode="before", check_fields=False)
@classmethod
def date_validate(cls, v): # pylint: disable=E0213
"""Return the datetime object from the date string"""
return datetime.strptime(v, "%Y-%m-%d")
class CboeOptionsChainsFetcher(
Fetcher[
@@ -75,19 +57,20 @@ class CboeOptionsChainsFetcher(
return CboeOptionsChainsQueryParams(**params)
@staticmethod
def extract_data(
async def aextract_data(
query: CboeOptionsChainsQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the CBOE endpoint"""
symbol = query.symbol.upper()
"""Return the raw data from the Cboe endpoint"""
INDEXES = get_cboe_index_directory()
SYMBOLS = get_cboe_directory()
symbol = query.symbol.replace("^", "").split(",")[0]
INDEXES = await get_index_directory(use_cache=query.use_cache)
SYMBOLS = await get_company_directory(use_cache=query.use_cache)
INDEXES = INDEXES.set_index("index_symbol")
if symbol not in SYMBOLS.index:
raise RuntimeError(f"{symbol} was not found in the CBOE directory.")
raise RuntimeError(f"{symbol} was not found in the Cboe options directory.")
quotes_url = (
f"https://cdn.cboe.com/api/global/delayed_quotes/options/_{symbol}.json"
@@ -95,22 +78,29 @@ class CboeOptionsChainsFetcher(
else f"https://cdn.cboe.com/api/global/delayed_quotes/options/{symbol}.json"
)
r = make_request(quotes_url)
if r.status_code != 200:
raise RuntimeError(f"No options data found for the symbol, {symbol}.")
return await amake_request(quotes_url)
r_json = r.json()
options_df = pd.DataFrame.from_records(r_json["data"]["options"])
@staticmethod
def transform_data(
query: CboeOptionsChainsQueryParams,
data: List[Dict],
**kwargs: Any,
) -> List[CboeOptionsChainsData]:
"""Transform the data to the standard format"""
if not data:
raise EmptyDataError()
options_df = DataFrame.from_records(data["data"]["options"])
options_df = options_df.rename(
columns={
"option": "contract_symbol",
"iv": "implied_volatility",
"theo": "theoretical",
"last_trade_price": "last_trade_price",
"last_trade_time": "last_trade_timestamp",
"theo": "theoretical_price",
"percent_change": "change_percent",
"prev_day_close": "prev_close",
"last_trade_time": "last_trade_timestamp",
}
)
@@ -126,13 +116,13 @@ class CboeOptionsChainsFetcher(
"C", "call"
).str.replace("P", "put")
option_df_index.strike = [ele.lstrip("0") for ele in option_df_index.strike]
option_df_index.strike = pd.Series(option_df_index.strike).astype(float)
option_df_index.strike = Series(option_df_index.strike).astype(float)
option_df_index.strike = option_df_index.strike * (1 / 1000)
option_df_index.strike = option_df_index.strike.to_list()
option_df_index.expiration = [
ele.lstrip("1") for ele in option_df_index.expiration
]
option_df_index.expiration = pd.DatetimeIndex(
option_df_index.expiration = DatetimeIndex(
option_df_index.expiration, yearfirst=True
).astype(str)
option_df_index = option_df_index.drop(columns=["Ticker"])
@@ -142,12 +132,12 @@ class CboeOptionsChainsFetcher(
quotes = option_df_index.join(options_df)
now = datetime.now()
temp = pd.DatetimeIndex(quotes.expiration)
temp = DatetimeIndex(quotes.expiration)
temp_ = (temp - now).days + 1
quotes["dte"] = temp_
quotes["last_trade_timestamp"] = (
pd.to_datetime(quotes["last_trade_timestamp"], format="%Y-%m-%dT%H:%M:%S")
to_datetime(quotes["last_trade_timestamp"], format="%Y-%m-%dT%H:%M:%S")
.fillna(value="-")
.replace("-", None)
)
@@ -159,15 +149,9 @@ class CboeOptionsChainsFetcher(
quotes["bid_size"] = quotes["bid_size"].astype("int64")
quotes["ask_size"] = quotes["ask_size"].astype("int64")
quotes["prev_close"] = round(quotes["prev_close"], 2)
quotes["change_percent"] = round(quotes["change_percent"], 2)
quotes["change_percent"] = round(quotes["change_percent"] / 100, 4)
return quotes.reset_index().to_dict("records")
@staticmethod
def transform_data(
query: CboeOptionsChainsQueryParams,
data: dict,
**kwargs: Any,
) -> List[CboeOptionsChainsData]:
"""Transform the data to the standard format"""
return [CboeOptionsChainsData.model_validate(d) for d in data]
return [
CboeOptionsChainsData.model_validate(d)
for d in quotes.reset_index().to_dict("records")
]

View File

@@ -1,92 +1,128 @@
"""CBOE Helpers Module."""
"""Cboe Helpers"""
# pylint: disable=expression-not-assigned, unused-argument
from datetime import date, timedelta
from datetime import date as dateType
from io import BytesIO, StringIO
from typing import Any, Dict, List, Optional
from typing import Any, List, Literal, Optional
import pandas as pd
import requests
import requests_cache
from aiohttp_client_cache import SQLiteBackend
from aiohttp_client_cache.session import CachedSession
from openbb_core.app.utils import get_user_cache_directory
from openbb_core.provider.utils.helpers import to_snake_case
cache_dir = get_user_cache_directory()
cboe_session = requests_cache.CachedSession(
f"{cache_dir}/http/cboe_directories", expire_after=timedelta(days=7)
)
from openbb_core.provider.utils.client import ClientResponse
from openbb_core.provider.utils.helpers import amake_request, to_snake_case
from pandas import DataFrame, read_csv
TICKER_EXCEPTIONS = ["NDX", "RUT"]
US_INDEX_COLUMNS = {
"symbol": "symbol",
"name": "name",
"current_price": "price",
"open": "open",
"high": "high",
"low": "low",
"close": "close",
"prev_day_close": "prev_close",
"price_change": "change",
"price_change_percent": "change_percent",
"last_trade_time": "last_trade_timestamp",
}
CONSTITUENTS_EU = Literal[ # pylint: disable=invalid-name
"BAT20P",
"BBE20P",
"BCH20P",
"BCHM30P",
"BDE40P",
"BDEM50P",
"BDES50P",
"BDK25P",
"BEP50P",
"BEPACP",
"BEPBUS",
"BEPCNC",
"BEPCONC",
"BEPCONS",
"BEPENGY",
"BEPFIN",
"BEPHLTH",
"BEPIND",
"BEPNEM",
"BEPTEC",
"BEPTEL",
"BEPUTL",
"BEPXUKP",
"BES35P",
"BEZ50P",
"BEZACP",
"BFI25P",
"BFR40P",
"BFRM20P",
"BIE20P",
"BIT40P",
"BNL25P",
"BNLM25P",
"BNO25G",
"BNORD40P",
"BPT20P",
"BSE30P",
"BUK100P",
"BUK250P",
"BUK350P",
"BUKAC",
"BUKBISP",
"BUKBUS",
"BUKCNC",
"BUKCONC",
"BUKCONS",
"BUKENGY",
"BUKFIN",
"BUKHI50P",
"BUKHLTH",
"BUKIND",
"BUKLO50P",
"BUKMINP",
"BUKNEM",
"BUKSC",
"BUKTEC",
"BUKTEL",
"BUKUTL",
]
EUR_INDEX_COLUMNS = {
"name": "name",
"isin": "isin",
"currency": "currency",
"current_price": "price",
"open": "open",
"high": "high",
"low": "low",
"close": "close",
"prev_day_close": "prev_close",
"price_change": "change",
"price_change_percent": "change_percent",
"last_trade_time": "last_trade_timestamp",
}
EUR_INDEX_CONSTITUENTS_COLUMNS = {
"symbol": "symbol",
"current_price": "price",
"open": "open",
"high": "high",
"low": "low",
"close": "close",
"volume": "volume",
"seqno": "seqno",
"prev_day_close": "prev_close",
"price_change": "change",
"price_change_percent": "change_percent",
"last_trade_time": "last_trade_timestamp",
"exchange_id": "exchange_id",
"tick": "tick",
"security_type": "type",
}
cache_dir = get_user_cache_directory()
backend = SQLiteBackend(f"{cache_dir}/http/cboe_directories", expire_after=3600 * 24)
def get_cboe_directory(**kwargs) -> pd.DataFrame:
"""Get the US Listings Directory for the CBOE.
async def response_callback(response: ClientResponse, _: Any):
"""Callback for HTTP Client Response."""
content_type = response.headers.get("Content-Type", "")
if "application/json" in content_type:
return await response.json()
if "text" in content_type:
return await response.text()
return await response.read()
async def get_cboe_data(url, use_cache: bool = True, **kwargs) -> Any:
"""Generic Cboe HTTP request."""
if use_cache is True:
async with CachedSession(cache=backend) as cached_session:
try:
response = await cached_session.get(url, timeout=10, **kwargs)
data = await response_callback(response, None)
finally:
await cached_session.close()
else:
data = await amake_request(url, response_callback=response_callback)
return data
async def get_company_directory(use_cache: bool = True, **kwargs) -> DataFrame:
"""
Get the US Company Directory for Cboe options. If use_cache is True,
the data will be cached for 24 hours.
Returns
-------
pd.DataFrame: CBOE_DIRECTORY
DataFrame of the CBOE listings directory
DataFrame: Pandas DataFrame of the Cboe listings directory
"""
r = cboe_session.get(
"https://www.cboe.com/us/options/symboldir/equity_index_options/?download=csv",
timeout=10,
)
url = "https://www.cboe.com/us/options/symboldir/equity_index_options/?download=csv"
if r.status_code != 200:
raise RuntimeError(r.status_code)
results = await get_cboe_data(url, use_cache)
r_json = StringIO(r.text)
response = BytesIO(results)
CBOE_DIRECTORY = pd.read_csv(r_json)
directory = read_csv(response)
CBOE_DIRECTORY = CBOE_DIRECTORY.rename(
directory = directory.rename(
columns={
" Stock Symbol": "symbol",
" DPM Name": "dpm_name",
@@ -95,149 +131,33 @@ def get_cboe_directory(**kwargs) -> pd.DataFrame:
}
).set_index("symbol")
return CBOE_DIRECTORY.astype(str)
return directory.astype(str)
def get_cboe_index_directory(**kwargs) -> pd.DataFrame:
"""Get the US Listings Directory for the CBOE
async def get_index_directory(use_cache: bool = True, **kwargs) -> DataFrame:
"""
Get the Cboe Index Directory. If use_cache is True,
the data will be cached for 24 hours.
Returns
-------
pd.DataFrame: CBOE_INDEXES
--------
List[Dict]: A list of dictionaries containing the index information.
"""
r = cboe_session.get(
"https://cdn.cboe.com/api/global/us_indices/definitions/all_indices.json",
timeout=10,
)
url = "https://cdn.cboe.com/api/global/us_indices/definitions/all_indices.json"
if r.status_code != 200:
raise requests.HTTPError(r.status_code)
results = await get_cboe_data(url, use_cache=use_cache)
CBOE_INDEXES = pd.DataFrame(r.json())
[result.pop("featured") for result in results]
[result.pop("featured_order") for result in results]
[result.pop("display") for result in results]
results = DataFrame(results)
results = results[results["source"] != "morningstar"]
CBOE_INDEXES = CBOE_INDEXES.rename(
columns={
"calc_end_time": "close_time",
"calc_start_time": "open_time",
"index_symbol": "symbol",
"mkt_data_delay": "data_delay",
},
)
indices_order: List[str] = [
"symbol",
"name",
"description",
"currency",
"open_time",
"close_time",
"tick_days",
"tick_frequency",
"tick_period",
"time_zone",
"data_delay",
]
CBOE_INDEXES = pd.DataFrame(CBOE_INDEXES, columns=indices_order).set_index("symbol")
idx = (
(CBOE_INDEXES.time_zone.str.contains("America/Chicago"))
& (CBOE_INDEXES.currency.str.contains("USD"))
& (CBOE_INDEXES.description.str.contains("Daily Risk Control") == 0)
& (CBOE_INDEXES.name.str.contains("OEX%-SML% INDEX") == 0)
& (CBOE_INDEXES.name.str.contains("Defined Risk Volatility Income") == 0)
& (CBOE_INDEXES.description.str.contains("is designed to track") == 0)
)
return CBOE_INDEXES[idx]
return results
def get_ticker_info(symbol: str, **kwargs) -> Dict[str, Any]:
symbol = symbol.upper()
SYMBOLS = get_cboe_directory()
INDEXES = get_cboe_index_directory()
data: Dict[str, Any] = {}
if symbol not in SYMBOLS.index and symbol not in INDEXES.index:
raise RuntimeError(f"Data not found for: {symbol}")
url = (
f"https://cdn.cboe.com/api/global/delayed_quotes/quotes/_{symbol}.json"
if symbol in INDEXES.index or symbol in TICKER_EXCEPTIONS
else f"https://cdn.cboe.com/api/global/delayed_quotes/quotes/{symbol}.json"
)
r = requests.get(url, timeout=10)
if r.status_code not in set([200, 403]):
raise requests.HTTPError(r.status_code)
if r.status_code == 403:
raise RuntimeError(f"Data was not found for, {symbol}.")
data = r.json()["data"]
if symbol in SYMBOLS.index.to_list():
data.update({"name": SYMBOLS.at[symbol, "name"]})
if symbol in INDEXES.index.to_list():
data.update({"name": INDEXES.at[symbol, "name"]})
_data = pd.DataFrame.from_dict(data, orient="index").transpose()
_data = _data.rename(
columns={
"current_price": "price",
"prev_day_close": "prev_close",
"price_change": "change",
"price_change_percent": "change_percent",
"last_trade_time": "last_trade_timestamp",
"security_type": "type",
}
)
return _data.transpose()[0].to_dict()
def get_ticker_iv(symbol: str, **kwargs) -> Dict[str, float]:
"""Get annualized high/low historical and implied volatility over 30/60/90 day windows.
Parameters
----------
symbol: str
The loaded ticker
Returns
-------
pd.DataFrame: ticker_iv
"""
symbol = symbol.upper()
INDEXES = get_cboe_index_directory().index.to_list()
quotes_iv_url = (
"https://cdn.cboe.com/api/global/delayed_quotes/historical_data/_"
f"{symbol}"
".json"
if symbol in TICKER_EXCEPTIONS or symbol in INDEXES
else f"https://cdn.cboe.com/api/global/delayed_quotes/historical_data/{symbol}.json"
)
h_iv = requests.get(quotes_iv_url, timeout=10)
if h_iv.status_code not in set([200, 403]):
raise requests.HTTPError(h_iv.status_code)
if h_iv.status_code == 403:
raise RuntimeError(f"Data was not found for, {symbol}.")
data = pd.DataFrame(h_iv.json())[2:-1]["data"].rename(f"{symbol}")
return data.to_dict()
def list_futures(**kwargs) -> List[dict]:
async def list_futures(**kwargs) -> List[dict]:
"""List of CBOE futures and their underlying symbols.
Returns
@@ -246,24 +166,22 @@ def list_futures(**kwargs) -> List[dict]:
Pandas DataFrame with results.
"""
r = requests.get(
"https://cdn.cboe.com/api/global/delayed_quotes/symbol_book/futures-roots.json",
timeout=10,
r = await get_cboe_data(
"https://cdn.cboe.com/api/global/delayed_quotes/symbol_book/futures-roots.json"
)
data = r.get("data")
[d.pop("sort_order") for d in data]
if r.status_code != 200:
raise requests.HTTPError(r.status_code)
return r.json()["data"]
return data
def get_settlement_prices(
settlement_date: Optional[date] = None,
async def get_settlement_prices(
settlement_date: Optional[dateType] = None,
options: bool = False,
archives: bool = False,
final_settlement: bool = False,
**kwargs,
) -> pd.DataFrame:
) -> DataFrame:
"""Gets the settlement prices of CBOE futures.
Parameters
@@ -279,18 +197,20 @@ def get_settlement_prices(
Returns
-------
pd.DataFrame
DataFrame
Pandas DataFrame with results.
"""
url = ""
if archives is True:
url = "https://cdn.cboe.com/resources/futures/archive/volume-and-price/CFE_FinalSettlement_Archive.csv"
if settlement_date is not None:
url = f"https://www.cboe.com/us/futures/market_statistics/settlement/csv?dt={settlement_date}"
url = (
"https://www.cboe.com/us/futures/market_statistics"
+ f"/settlement/csv?dt={settlement_date}"
)
if options is True:
url = f"https://www.cboe.com/us/futures/market_statistics/settlement/csv?options=t&dt={settlement_date}"
url = (
"https://www.cboe.com/us/futures/market_statistics/"
+ f"settlement/csv?options=t&dt={settlement_date}"
)
if settlement_date is None:
url = "https://www.cboe.com/us/futures/market_statistics/settlement/csv"
if options is True:
@@ -299,20 +219,18 @@ def get_settlement_prices(
if final_settlement is True:
url = "https://www.cboe.com/us/futures/market_statistics/final_settlement_prices/csv/"
r = requests.get(url, timeout=10)
if archives is True:
url = (
"https://cdn.cboe.com/resources/futures/archive"
+ "/volume-and-price/CFE_FinalSettlement_Archive.csv"
)
if r.status_code != 200:
raise RuntimeError(r.status_code)
response = await get_cboe_data(url, use_cache=False, **kwargs)
data = pd.read_csv(BytesIO(r.content), index_col=None, parse_dates=True)
data = read_csv(StringIO(response), index_col=None, parse_dates=True)
if data.empty:
error_string = (
f"No results found for, {settlement_date}."
if settlement_date is not None
else "No results found."
)
raise RuntimeError(error_string)
return DataFrame()
data.columns = [to_snake_case(c) for c in data.columns]
data = data.rename(columns={"expiration_date": "expiration"})
@@ -320,78 +238,4 @@ def get_settlement_prices(
if len(data) > 0:
return data
return pd.DataFrame()
class Europe:
"""Class for European CBOE data."""
@staticmethod
def get_all_index_definitions(**kwargs) -> Dict[Any, Any]:
"""Get the full list of European index definitions.
Returns
-------
Dict[Any, Any]
Dictionary with results.
"""
r = cboe_session.get(
"https://cdn.cboe.com/api/global/european_indices/definitions/all-definitions.json",
timeout=10,
)
if r.status_code != 200:
raise requests.HTTPError(r.status_code)
return r.json()["data"]
@staticmethod
def list_indices(**kwargs) -> List[Dict]:
"""Gets names, currencies, ISINs, regions, and symbols for European indices.
Returns
-------
Dict[str, str]
List of dictionaries with the results.
"""
data = Europe.get_all_index_definitions()
data = (
pd.DataFrame.from_records(pd.DataFrame(data)["index"])
.drop(columns=["short_name"])
.rename(columns={"long_name": "name"})
)
return data.to_dict("records")
@staticmethod
def list_index_constituents(symbol: str, **kwargs) -> List[str]:
"""List symbols for constituents of a European index.
Parameters
----------
symbol: str
The symbol of the index.
Returns
-------
List[str]
List of constituents as ticker symbols.
"""
SYMBOLS = pd.DataFrame(Europe.list_indices())["symbol"].to_list()
symbol = symbol.upper()
if symbol not in SYMBOLS:
raise RuntimeError(
f"The symbol, {symbol}, was not found in the CBOE European Index directory.",
)
url = f"https://cdn.cboe.com/api/global/european_indices/definitions/{symbol}.json"
r = requests.get(url, timeout=10)
if r.status_code != 200:
raise requests.HTTPError(r.status_code)
r_json = r.json()["constituents"]
return [r_json[i]["constituent_symbol"] for i in range(0, len(r_json))]
return DataFrame()

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,8 @@ packages = [{ include = "openbb_cboe" }]
[tool.poetry.dependencies]
python = "^3.8"
requests-cache = "^1.1.0"
aiohttp-client-cache = "^0.10.0"
aiosqlite = "^0.19.0"
openbb-core = "^1.1.1"
[build-system]

View File

@@ -1,114 +0,0 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://cdn.cboe.com/api/global/delayed_quotes/quotes/AAPL.json
response:
body:
string: !!binary |
H4sIAAAAAAAAA1xRy27CMBD8FWvPIfIrL9+499BD75ZJLLBI4jQ20BTx79UaEA3HndmZ3Z29QnSD
DdEMEygCnHKxoc2Gl4Q1ihZKMsgIdCYaUOQKYRl2vsfO7fbzA6n2NM92jHqaXWtBEVbxnLOMwM51
zzIjYMLxjdTB/aJA3NlnWWYE/GTH1C3zWmYEDm5/SHWRvHp/eXjRGjfofVhNPvv+NCAiKadVWRQZ
AXcWFBThMq/KJiMQ7PfoUVTSqhJcSIaDp9medWcW/fIs70PSebo9mHGP+EbkdfUG68nOrR0j0jxn
TdU85r5kLG9YvUb/qepcNAKX7U2IOs6msxq/s37MF5OqoErUmL79ebiksDGc6FpMGjp/GSEd2p5m
Fxcdlyk5hejbI9yQWj/z9gcAAP//AwCkZebeDAIAAA==
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 80cdc8f7ea139abd-MIA
Cache-Control:
- max-age=0, s-maxage=5
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:06:36 GMT
ETag:
- W/"23e87da6795e2433a5ea9cc52e3c77b6"
Last-Modified:
- Tue, 26 Sep 2023 19:05:45 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
Via:
- 1.1 5888333a47f4bcb4270f45fea45f08bc.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- R899eJtot2_qu6I8v4eacLquOEAPrpZLQwXmhxiNKOhkh5T0zMGtNQ==
X-Amz-Cf-Pop:
- MIA3-P4
X-Cache:
- RefreshHit from cloudfront
x-amz-server-side-encryption:
- AES256
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://cdn.cboe.com/api/global/delayed_quotes/historical_data/AAPL.json
response:
body:
string: !!binary |
H4sIAAAAAAAAA2ySzWoCQRCEX2WZsw7V3dM93XvznkPeQDYkxAV/ApqEIL572ChKRs/9UUV99DEd
xs3b/jBsPlLfJQbLHDFn64h6SC+UZl16HQ5D6rtj2v9sXnbriVwsnp+m07Ddfg7r5Wp8X6W+o/DM
HBFauaoEbNal1Zdg+Z8rnh3QcCpFhVj/MLvDOIeHgUiUb3HRchKZqtUpkVEINHHjg9qSa1WAlNWr
n/PGB73IzgUgIWZVP2P3tZ6tCgARtRCxm4/17nvSwSWTRYSTeUxLGx0XTDK51ggNtxAv3ui4Yl61
MXA5WfaIAIpYAYnVRsAVCyMApbCiuEuz/4JFBhmAWqo5zEqz/4pVDgAIUuUq5TTr2hc5/QIAAP//
AwAp+lGSYgIAAA==
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 80cdc8fc6b429af1-MIA
Cache-Control:
- max-age=300, s-maxage=600
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:06:37 GMT
ETag:
- W/"85783b2264e96b33536491cd48bea1a9"
Last-Modified:
- Tue, 26 Sep 2023 11:10:22 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
Via:
- 1.1 8f716abb35818e2bd8a0134286c0945c.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- 92zjtgwtkx7fkAAN2UXnVVamN6NmQyzV6AqFTW7aX6lsiFh6OIRiMA==
X-Amz-Cf-Pop:
- MIA3-P4
X-Cache:
- RefreshHit from cloudfront
x-amz-server-side-encryption:
- AES256
status:
code: 200
message: OK
version: 1

View File

@@ -1,424 +0,0 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://cdn.cboe.com/api/global/european_indices/definitions/all-definitions.json
response:
body:
string: !!binary |
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
//8DAO1j9N2/zAMA
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 80cdc90e3a1b4958-MIA
Cache-Control:
- no-cache,no-store,max-age=0,must-revalidate
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:06:39 GMT
ETag:
- W/"c4cd06ee64c9367d0a7f7246bf5efec6"
Last-Modified:
- Tue, 26 Sep 2023 16:59:14 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
Via:
- 1.1 7d65a4eeca34cef9956bcbd99b366288.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- Rf2Axems3JDibm1PDDKvA6bjDrGV9pUQwKUPtW7IoFwUD_729oLXWA==
X-Amz-Cf-Pop:
- MIA3-P4
X-Cache:
- Miss from cloudfront
x-amz-server-side-encryption:
- AES256
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://cdn.cboe.com/api/global/european_indices/constituent_quotes/BUKBUS.json
response:
body:
string: !!binary |
H4sIAAAAAAAAA9SabW8aORDHvwra12DN2B7bw7sofbjo+hClOaXS6YQoQS0qAQ5Ie7mq3/20T8Yb
vLvX7qKo7+L14hHzy38ePHxL9ou7+W4/vdsk40GCZkw8JpsMB8ntdD9NxoM/vyW7h7sP62W6f/bu
/NVydP4+3Z/db7fz1X6y2S5m82Q8QCbBw0HyYXGbjAcgYDhIprvP/u8Pi9vJbvFv+mqxFS7Xm/kq
OwSFGw6ST4uPn7KlETQcJMv112JTpZaX611o8ct6eX+XPrCMYIeDZPFFgbe7m/+9WqdvS7RSG0RM
P7PZzr9MbqcPk8NhUshsYzGbT2afpquP6WMl7KOHk818O5uv9ulnBEvCwuDhM7nd4FnwkdX9cpl+
oeluP9lvp7fzSQog9a0EqUbAI2muEceSx6RSN8//Kc7I3Iqoh4Nkv5ilfk1u119XSfYVZ/fbxf5h
sn/YZGft9uvZ5+T7cBDCO7+8qGVnBZjO7KzQAbp8VZArzvfOLtaeHEplvSfr0EmEODorDB6jG4Eg
qoc3UgKlkqeip7Ffes9enV3V4TOY+boTPaMEHugVq5xecXzp7mLp2RE5aCKHhjB/5YicUYIi4KTA
Rm4K7MlUB9Qzt5sXddiYu2PLXy2o5YscWv536ejClGfWDEyTc2xiwIpzHgHLj6jhdcSlOyu6BjdW
bqxMv6yev798UwdLGvZYfh6XNNaFxKQFDqBJY6nCzVs9REojFbRGSiKM4ZPGuSNW6fMWgFbrk2W5
Vr3db/4/wRdv/6iNkgS2B4IEWNEcoQpl57dLl3urnqBkpDZ+hlWMnz/siQXo4fUswN/O3tWWKOB6
KC/BhVkOgbNqsyhSwGYrX1YUFg/giJm5DR3YaKpLT5MxcraJnNH2ZImuZ3YXb2oTndWyu/CsrrCz
BEGBaTVk3i29XVqsFJiWVHOdopWzFINnScZkN8JG4Y1QKMnuZOKDPiPnxfXV73X8NBjdHaAGrcPI
qcHaIHJqkFYwcyhBb/jAES0Dt2DUOl5uajAqxhFbwqfU5mQQZc8qvHlZG0G1yQTULYLqSv2SLoMI
qvN63se8wuKhQSdwhlpF6KLJDwmi3YI+KklDDUrBUplfJQG+vn5bnwBVdw0i5HHSJ0AVdulQDaKl
RY+PSfuGuY4ekpHx/Cd/Qnrs4GSVp+o1fl6evXxeGz/R9BA+0YToNNpAeRplpXYpLR5qF6VYt6ED
Z6NhE3VMeCioCZ0yyCe7XIF+ZXd1/qxWd5r7yH2ce6uEV5xZwCs2vb/5KOlxi+wUAsXZsfyJhk+D
j9I9szNjlH3K7qo+YJLjTDDd2j1H4YU0sRTm0Ow5qgTM0qIH5yRLcq0RE6LoyDlhYh2DaUKHcDrZ
yb5ld3NWB08xd1ed4kcFZ9ipl5ulu0uLwTTBNJIjQqZopaI42qVzk+hkeqtpny7X/Si5i/prFuwj
XprKJQtQJV6aR1csWCVndFuRqaSM35AR2GinZ1oaPXRKP12R+SMR892Lt/WDBC2YOk8S8rBVThJc
EDCNzG5U/PV/YS643GQwskV1RioXHSY4oWP9gWqqU0YkEE83BeoZ3dVlfXNnRfcJnrYZH9/ccTAF
Spu5ygyvsFiBZ3ULPAs6OlhIG0kdneG5BnggSPETDmB/CN7189e12c700Nspg2HMVEYHs/Ny06co
86i3c4pMS8x0EuPprmaMB43CA4HKnqYxRx7rnu9Vbi5rpWcVdJ/jWVWBly4P3Z2VqjKBLS0eRrCo
dCs+iTramVulYnXmSB3dV1fxaWXkr3It9v6yfrDA+f9px9+tQNjflcvydytQvRYr1h6fbpmfKzDx
lIeO4515Y3dH8nSFpu7Y3f31/T8AAAD//wMAafeNo5MkAAA=
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 80cdc9144934b3bc-MIA
Cache-Control:
- no-cache,no-store,max-age=0,must-revalidate
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:06:40 GMT
ETag:
- W/"25b766cdc019517f7bcf69395b61c7bc"
Last-Modified:
- Tue, 26 Sep 2023 16:59:59 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
Via:
- 1.1 cad07acdf97efe1eced66076ba0912e0.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- EYlB_xpgHJJnqIfDY_9OxtLV6vykoWQeYfXSBWj595M6o_Mhnx5WrA==
X-Amz-Cf-Pop:
- MIA3-P4
X-Cache:
- Miss from cloudfront
x-amz-server-side-encryption:
- AES256
status:
code: 200
message: OK
version: 1

View File

@@ -1,504 +0,0 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://cdn.cboe.com/api/global/european_indices/index_history/BUKBUS.json
response:
body:
string: !!binary |
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
AAD//wMAmIUIfPJIAgA=
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 80cdc9199f8f8da2-MIA
Cache-Control:
- max-age=60, s-maxage=86400, stale-while-revalidate=60
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:06:41 GMT
ETag:
- W/"fdfdd74fd904414d95924bf31468f8fe"
Last-Modified:
- Tue, 26 Sep 2023 06:30:08 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
Via:
- 1.1 7aea57f307e043300c172e8eaaa89c9c.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- enjH_Fruj0aWwcAIqcNwIPFa9-TQxYILSoaVj4KlRfWF1BsVDeix6A==
X-Amz-Cf-Pop:
- MIA3-P4
X-Cache:
- RefreshHit from cloudfront
x-amz-server-side-encryption:
- AES256
status:
code: 200
message: OK
version: 1

View File

@@ -3,31 +3,32 @@ interactions:
body: null
headers:
Accept:
- '*/*'
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Host:
- www.cboe.com
method: GET
uri: https://www.cboe.com/us/futures/market_statistics/settlement/csv
response:
body:
string: ''
body: {}
headers:
CF-Cache-Status:
- EXPIRED
CF-RAY:
- 80cdc9017d2e0a3e-MIA
- 837871329cae6063-YVR
Connection:
- keep-alive
Content-Type:
- text/html; charset=utf-8
Date:
- Tue, 26 Sep 2023 19:06:37 GMT
- Mon, 18 Dec 2023 15:29:35 GMT
Link:
- <https://www.cboe.com/us/futures/market_statistics/settlement/csv>; rel="canonical"
Location:
- https://www.cboe.com/us/futures/market_statistics/settlement/csv/
- /us/futures/market_statistics/settlement/csv/
Server:
- cloudflare
Strict-Transport-Security:
@@ -45,149 +46,46 @@ interactions:
body: null
headers:
Accept:
- '*/*'
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Host:
- www.cboe.com
method: GET
uri: https://www.cboe.com/us/futures/market_statistics/settlement/csv/
response:
body:
string: !!binary |
H4sIAAAAAAAAA3STy07DMBBF90j8Ax9gu57xK15SQZ+KVAkRBXaldFEJKKqKBH+PH3ViR83Gm3Pn
5vp6sjkd3392Z/L09/l2/CCPv9+H0/Z8OH7dPWzPe7I5HXb725umJU0r7ORZEOQoKLcUDQHNLL9A
ySfNBQKnXBIwjKsEIYcABSxQ5ZG0RqZJkWNUAWPnKydtokA5lDRnECZtZTDB1wSRIidQMd4PzqRn
0jlSMJ5V0kCC8wSRgrumZdzlTXCRoAiulgmAbnKVoAy2lhkUHVwnqCiih1UXp05Ih4LcF01gtYP1
eH0Rj5YQ8VgNkY4WEfFoFRGPlLGcLl6IP4qNcXeWyIRSuWDwuk6gqkKQxb8ItCkEWYYgEMzoIFjO
iT+GGdA58JwPIqBwl8z5IIHjsvAfBEDJpE94X0+B+KP7p2IAK5UJT98LmiKBdeMMeC5oiwjBQclc
EDOGZ4yf8KuXC2b9QwYH4dYPcsG8uIVzAKZ0Llj0ux1DasaLkKt+v71A66Fg3W85F9cE3W9gooPr
wTf5DwAA//9slMsNwCAMQxeqUEVIgGMHaE/sP0tJBMKkvT/F+dnX3RRoe8bIUbmI3WoB6AWVSCnw
iQAcs3upCouTQEPIHwCW6D/fp8hOAv7BJDKFuFXAlOA/AJLCpihxLGoCmBdZAd/DszZpEpVnBVKA
PntIMnsYgJ9CyFXwPYx/eAEAAP//AwC82iJ+bwYAAA==
H4sIAAAAAAAAA3SUz07cMBDG70h9Bx7AcTwzHjs+gii7gFJRFSHgxr/DSm23Wi0SffvaSbwZu5uL
L7/Pk29mPud2t337eN2rH39/vWx/qq+ffza75/1m+/v04nn/rm53m9f3Lyf3D+r+oX0ihQapAWzQ
KEBtnQ0TZCywV0AaeYIG2kuboG0MNIYUWG1MhighmAQPNwvkE3LImG9aidEqYFGWJSQoYLvKCBsY
7rENuZV2nSENfTqNxhwMXWdoB0NOew5dhjcZcoOowGvDaDPsM3QNdAlakztpv2Xmh6pe+4PV7xl1
DcYuuqmLPrJ+eSUjXpzeiBenMOLFOYx4cRIjXpjF1fn6UaWjiEQUMGgmyYU7k9aHmrzkwt7EOUgu
5p0ix6Sp4yS4Wql01AaIdDCSVwYojg8krwxEXt6vDBDH7UQDZ/05qHRM28sGgnWkGaTgsnAQrPWa
igqrwkKsYLRxUrCelzQIvBvyMwuu5zUlgXO14Kbo4oign7M7VPAck5AEd0lwBzKkMfjBsasEMqbu
mEBsIiYxevCajRSIVQyf8OPfZxbIsPIxgYgrpDY7nNrMAvl8fRLUHuQbTp8InCtQEtB/c7Aue5gE
dRcxD2WF2sO0zX8AAAD//wMA8VW5u8UFAAA=
headers:
CF-Cache-Status:
- EXPIRED
CF-RAY:
- 80cdc903bfe90a3e-MIA
- 83787134cf0c6063-YVR
Cache-Control:
- max-age=30
Connection:
- keep-alive
Content-Disposition:
- attachment; filename=FuturesSettlements_2023-09-25.csv
- attachment; filename=FuturesSettlements_2023-12-15.csv
Content-Encoding:
- gzip
Content-Type:
- text/csv
Date:
- Tue, 26 Sep 2023 19:06:38 GMT
- Mon, 18 Dec 2023 15:29:35 GMT
Last-Modified:
- Tue, 26 Sep 2023 18:33:18 GMT
Link:
- <https://www.cboe.com/us/futures/market_statistics/settlement/csv/>; rel="canonical"
Server:
- cloudflare
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Host,Accept-Encoding
X-Frame-Options:
- SAMEORIGIN
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://www.cboe.com/us/futures/market_statistics/settlement/csv
response:
body:
string: ''
headers:
Age:
- '1'
CF-Cache-Status:
- HIT
CF-RAY:
- 80cdc908ab822227-MIA
Connection:
- keep-alive
Content-Type:
- text/html; charset=utf-8
Date:
- Tue, 26 Sep 2023 19:06:38 GMT
Link:
- <https://www.cboe.com/us/futures/market_statistics/settlement/csv>; rel="canonical"
Location:
- https://www.cboe.com/us/futures/market_statistics/settlement/csv/
Server:
- cloudflare
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Host,Accept-Encoding
X-Frame-Options:
- SAMEORIGIN
status:
code: 301
message: Moved Permanently
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://www.cboe.com/us/futures/market_statistics/settlement/csv/
response:
body:
string: !!binary |
H4sIAAAAAAAAA3STy07DMBBF90j8Ax9gu57xK15SQZ+KVAkRBXaldFEJKKqKBH+PH3ViR83Gm3Pn
5vp6sjkd3392Z/L09/l2/CCPv9+H0/Z8OH7dPWzPe7I5HXb725umJU0r7ORZEOQoKLcUDQHNLL9A
ySfNBQKnXBIwjKsEIYcABSxQ5ZG0RqZJkWNUAWPnKydtokA5lDRnECZtZTDB1wSRIidQMd4PzqRn
0jlSMJ5V0kCC8wSRgrumZdzlTXCRoAiulgmAbnKVoAy2lhkUHVwnqCiih1UXp05Ih4LcF01gtYP1
eH0Rj5YQ8VgNkY4WEfFoFRGPlLGcLl6IP4qNcXeWyIRSuWDwuk6gqkKQxb8ItCkEWYYgEMzoIFjO
iT+GGdA58JwPIqBwl8z5IIHjsvAfBEDJpE94X0+B+KP7p2IAK5UJT98LmiKBdeMMeC5oiwjBQclc
EDOGZ4yf8KuXC2b9QwYH4dYPcsG8uIVzAKZ0Llj0ux1DasaLkKt+v71A66Fg3W85F9cE3W9gooPr
wTf5DwAA//9slMsNwCAMQxeqUEVIgGMHaE/sP0tJBMKkvT/F+dnX3RRoe8bIUbmI3WoB6AWVSCnw
iQAcs3upCouTQEPIHwCW6D/fp8hOAv7BJDKFuFXAlOA/AJLCpihxLGoCmBdZAd/DszZpEpVnBVKA
PntIMnsYgJ9CyFXwPYx/eAEAAP//AwC82iJ+bwYAAA==
headers:
Age:
- '1'
CF-Cache-Status:
- HIT
CF-RAY:
- 80cdc90a2daa2227-MIA
Cache-Control:
- max-age=30
Connection:
- keep-alive
Content-Disposition:
- attachment; filename=FuturesSettlements_2023-09-25.csv
Content-Encoding:
- gzip
Content-Type:
- text/csv
Date:
- Tue, 26 Sep 2023 19:06:39 GMT
Last-Modified:
- Tue, 26 Sep 2023 19:06:38 GMT
- Mon, 18 Dec 2023 07:58:40 GMT
Link:
- <https://www.cboe.com/us/futures/market_statistics/settlement/csv/>; rel="canonical"
Server:

View File

@@ -0,0 +1,177 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://cdn.cboe.com/api/global/european_indices/constituent_quotes/BUK100P.json
response:
body:
string: !!binary |
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
PwAAAP//AwArzrTSlbMAAA==
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 83787b8e0f38605e-YVR
Cache-Control:
- no-cache,no-store,max-age=0,must-revalidate
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 18 Dec 2023 15:36:39 GMT
Etag:
- W/"acdc84c5358e2615dc7091d4088bb8dd"
Last-Modified:
- Mon, 18 Dec 2023 15:36:38 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
Via:
- 1.1 0da9bec11a1bde5ca7f71b28194afd5a.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- o3XbAh0a7ONPJIV4_hSeD4GnX48cynmu5oszX2uZopAglyFTCtP3MA==
X-Amz-Cf-Pop:
- YVR50-C1
X-Cache:
- Miss from cloudfront
x-amz-server-side-encryption:
- AES256
status:
code: 200
message: OK
version: 1

View File

@@ -3,650 +3,84 @@ interactions:
body: null
headers:
Accept:
- '*/*'
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://cdn.cboe.com/api/global/delayed_quotes/quotes/all_us_indices.json
uri: https://cdn.cboe.com/api/global/european_indices/index_quotes/all-indices.json
response:
body:
string: !!binary |
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
q49PAAAA//8DAE0JRPFV2QYA
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
k0OntwoS++33PwAAAP//AwBrQV8/unUAAA==
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 80cdc92acdab8df4-MIA
- 837871401bbb8435-YVR
Cache-Control:
- max-age=0, s-maxage=5
- no-cache,no-store,max-age=0,must-revalidate
Connection:
- keep-alive
Content-Encoding:
@@ -654,21 +88,21 @@ interactions:
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:06:44 GMT
ETag:
- W/"f47469917907d7018008eda61dcbcfb6"
- Mon, 18 Dec 2023 15:29:37 GMT
Etag:
- W/"2c0ddb5d09f055d6c8b616aa80e20998"
Last-Modified:
- Tue, 26 Sep 2023 19:06:44 GMT
- Mon, 18 Dec 2023 15:29:37 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
Via:
- 1.1 6bc6021a7bdfc58790cf40fa0ce05e78.cloudfront.net (CloudFront)
- 1.1 1ba6aff812e7fb724bcf5efa0e7c3dec.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- iTbJWO0FnlImqHHqbl6CgLFWZIYgNwP3HA08AJDfEfulfJwxwYD_Lg==
- uVRfyDFDrT4Y9g03dmwjgGm9k9Z44A8SHEOkX9ZXNJ9CCch_kfmSBQ==
X-Amz-Cf-Pop:
- MIA3-P4
- SEA900-P3
X-Cache:
- Miss from cloudfront
x-amz-server-side-encryption:

View File

@@ -1,346 +0,0 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://cdn.cboe.com/api/global/delayed_quotes/charts/historical/_AAVE10RP.json
response:
body:
string: !!binary |
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
/wMAJA5R+PcsAQA=
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 80cdc91f2ac4b3d9-MIA
Cache-Control:
- max-age=300, s-maxage=600
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:06:42 GMT
ETag:
- W/"f1f5926b27faf3b7ebde482a00d68cd2"
Last-Modified:
- Tue, 26 Sep 2023 19:00:57 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
Via:
- 1.1 cad07acdf97efe1eced66076ba0912e0.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- d4Az8DcogayqQQzYUVsZNuV62dKHzgKnU7x32a3joTyb1JyS8ipRgw==
X-Amz-Cf-Pop:
- MIA3-P4
X-Cache:
- Miss from cloudfront
x-amz-server-side-encryption:
- AES256
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://cdn.cboe.com/api/global/delayed_quotes/quotes/_AAVE10RP.json
response:
body:
string: !!binary |
H4sIAAAAAAAAA2SRwW6DMBBEf8XaM7FsEwz4lkPvVVX1WMsBK7EChmKThEb592qhTdP05p15Gu16
LhBda0M0bQ+KgGAiXbFyJSThpWJSpRISArWJBhS5QJjabdcg+b7ZvD1x9vKMfrDVOLg46Tj1Fl3n
a3tGx56rvfE7q10NimQJgWocBuuj7gdXISsZLTgXCYFZ0QsPiqw4FawoHgzd26GyPi5AuS4TAts5
XDKaS4azCYfvYMmzxdfBfWIoX+z7seutR5zTLE8R37vdHgVBWYFA052WOJkz3LNquvC4uT3q2kz6
Zgk6o8euGVsUWELAHVOGT/oz/N76T7s7049NM//xh+9wZcnKNRN5nuGpjQlRx8HUVmORfzt85WuV
cZVm2ER0Ff4K1N3JwxUDb13qW5fXLwAAAP//AwAg/lNsEAIAAA==
headers:
CF-Cache-Status:
- DYNAMIC
CF-RAY:
- 80cdc925eaad21e2-MIA
Cache-Control:
- max-age=0, s-maxage=5
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:06:43 GMT
ETag:
- W/"7af2709a5adaf8b0cb0b79307ca3d901"
Last-Modified:
- Tue, 26 Sep 2023 19:06:38 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
Via:
- 1.1 b162b8fd297daf9a97b750bf2aa03d36.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- HXQzs1UhOevV2Ajk3OtSsAcQ8ZmtV2FUo0mshUkimm2c2_YGVeHNkQ==
X-Amz-Cf-Pop:
- MIA3-P4
X-Cache:
- Miss from cloudfront
x-amz-server-side-encryption:
- AES256
status:
code: 200
message: OK
version: 1

View File

@@ -1,7 +1,10 @@
"""CBOE Fetchers Tests.
The CBOE provider extension uses request caching.
So, when an item like a symbol directory is already cached, the cassette recorder does
For tests, set all functions using cache with the `use_cache` parameter to `False`.
When an item like a symbol directory is already cached, the cassette recorder does
not record the request event. If functions share a cached resource, it will only capture
the cassette for the first instance.
@@ -13,23 +16,13 @@ from datetime import date
import pytest
from openbb_cboe.models.available_indices import CboeAvailableIndicesFetcher
from openbb_cboe.models.equity_historical import CboeEquityHistoricalFetcher
from openbb_cboe.models.equity_info import CboeEquityInfoFetcher
from openbb_cboe.models.equity_quote import CboeEquityQuoteFetcher
from openbb_cboe.models.equity_search import CboeEquitySearchFetcher
from openbb_cboe.models.european_index_constituents import (
CboeEuropeanIndexConstituentsFetcher,
)
from openbb_cboe.models.european_indices import (
CboeEuropeanIndicesFetcher,
)
from openbb_cboe.models.futures_curve import CboeFuturesCurveFetcher
from openbb_cboe.models.index_historical import (
CboeIndexHistoricalFetcher,
)
from openbb_cboe.models.index_constituents import CboeIndexConstituentsFetcher
from openbb_cboe.models.index_historical import CboeIndexHistoricalFetcher
from openbb_cboe.models.index_search import CboeIndexSearchFetcher
from openbb_cboe.models.index_snapshots import CboeIndexSnapshotsFetcher
from openbb_cboe.models.market_indices import (
CboeMarketIndicesFetcher,
)
from openbb_cboe.models.options_chains import CboeOptionsChainsFetcher
from openbb_core.app.service.user_service import UserService
@@ -38,33 +31,6 @@ test_credentials = UserService().default_user_settings.credentials.model_dump(
)
@pytest.mark.record_http
def test_cboe_available_indices_fetcher(credentials=test_credentials):
params = {}
fetcher = CboeAvailableIndicesFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_index_search_fetcher(credentials=test_credentials):
params = {}
fetcher = CboeIndexSearchFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_options_chains_fetcher(credentials=test_credentials):
params = {"symbol": "AAPL"}
fetcher = CboeOptionsChainsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.fixture(scope="module")
def vcr_config():
return {
@@ -76,10 +42,28 @@ def vcr_config():
@pytest.mark.record_http
def test_cboe_equity_search_fetcher(credentials=test_credentials):
params = {}
def test_cboe_index_historical_fetcher(credentials=test_credentials):
params = {"symbol": "AAVE10RP", "use_cache": False}
fetcher = CboeEquitySearchFetcher()
fetcher = CboeIndexHistoricalFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_index_constituents_fetcher(credentials=test_credentials):
params = {"symbol": "BUKBUS"}
fetcher = CboeIndexConstituentsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_index_search_fetcher(credentials=test_credentials):
params = {"query": "uk", "use_cache": False}
fetcher = CboeIndexSearchFetcher()
result = fetcher.test(params, credentials)
assert result is None
@@ -91,6 +75,7 @@ def test_cboe_equity_historical_fetcher(credentials=test_credentials):
"start_date": date(2023, 1, 1),
"end_date": date(2023, 1, 10),
"interval": "1d",
"use_cache": False,
}
fetcher = CboeEquityHistoricalFetcher()
@@ -99,10 +84,37 @@ def test_cboe_equity_historical_fetcher(credentials=test_credentials):
@pytest.mark.record_http
def test_cboe_equity_info_fetcher(credentials=test_credentials):
params = {"symbol": "AAPL"}
def test_cboe_available_indices_fetcher(credentials=test_credentials):
params = {"use_cache": False}
fetcher = CboeEquityInfoFetcher()
fetcher = CboeAvailableIndicesFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_options_chains_fetcher(credentials=test_credentials):
params = {"symbol": "AAPL", "use_cache": False}
fetcher = CboeOptionsChainsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_equity_search_fetcher(credentials=test_credentials):
params = {"query": "ETF", "use_cache": False}
fetcher = CboeEquitySearchFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_equity_quote_fetcher(credentials=test_credentials):
params = {"symbol": "AAPL", "use_cache": False}
fetcher = CboeEquityQuoteFetcher()
result = fetcher.test(params, credentials)
assert result is None
@@ -116,49 +128,9 @@ def test_cboe_futures_curve_fetcher(credentials=test_credentials):
assert result is None
@pytest.mark.record_http
def test_cboe_european_index_constituents_fetcher(credentials=test_credentials):
params = {"symbol": "BUKBUS"}
fetcher = CboeEuropeanIndexConstituentsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_european_indices_fetcher(credentials=test_credentials):
params = {
"symbol": "BUKBUS",
"start_date": date(2023, 1, 1),
"end_date": date(2023, 1, 10),
}
fetcher = CboeEuropeanIndicesFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_market_indices_fetcher(credentials=test_credentials):
params = {"symbol": "AAVE10RP"}
fetcher = CboeMarketIndicesFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_index_historical_fetcher(credentials=test_credentials):
params = {"symbol": "AAVE10RP"}
fetcher = CboeIndexHistoricalFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_cboe_index_snapshots_fetcher(credentials=test_credentials):
params = {}
params = {"region": "eu"}
fetcher = CboeIndexSnapshotsFetcher()
result = fetcher.test(params, credentials)

View File

@@ -1,15 +1,19 @@
"""FMP Index Constituents Model."""
from datetime import datetime
from typing import Any, Dict, List, Optional
from datetime import (
date as dateType,
datetime,
)
from typing import Any, Dict, List, Literal, Optional, Union
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.index_constituents import (
IndexConstituentsData,
IndexConstituentsQueryParams,
)
from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
from openbb_fmp.utils.helpers import get_data_many
from pydantic import field_validator
from pydantic import Field, field_validator
class FMPIndexConstituentsQueryParams(IndexConstituentsQueryParams):
@@ -20,11 +24,41 @@ class FMPIndexConstituentsQueryParams(IndexConstituentsQueryParams):
https://site.financialmodelingprep.com/developer/docs/list-of-nasdaq-companies-api/
"""
index: Literal["dowjones", "sp500", "nasdaq"] = Field(
default="dowjones",
)
class FMPIndexConstituentsData(IndexConstituentsData):
"""FMP Index Constituents Data."""
__alias_dict__ = {"headquarter": "headQuarter"}
__alias_dict__ = {
"headquarter": "headQuarter",
"date_first_added": "dateFirstAdded",
"sub_sector": "subSector",
}
sector: str = Field(
description="Sector the constituent company in the index belongs to."
)
sub_sector: Optional[str] = Field(
default=None,
description="Sub-sector the constituent company in the index belongs to.",
)
headquarter: Optional[str] = Field(
default=None,
description="Location of the headquarter of the constituent company in the index.",
)
date_first_added: Optional[Union[dateType, str]] = Field(
default=None, description="Date the constituent company was added to the index."
)
cik: Optional[int] = Field(
description=DATA_DESCRIPTIONS.get("cik", ""), default=None
)
founded: Optional[Union[dateType, str]] = Field(
default=None,
description="Founding year of the constituent company in the index.",
)
@field_validator("dateFirstAdded", mode="before", check_fields=False)
@classmethod

View File

@@ -28,7 +28,7 @@ class IntrinioOptionsChainsQueryParams(OptionsChainsQueryParams):
"""
date: Optional[dateType] = Field(
description="Date for which the options chains are returned."
default=None, description="The end-of-day date for options chains data."
)
@@ -42,7 +42,19 @@ class IntrinioOptionsChainsData(OptionsChainsData):
"option_type": "type",
}
@field_validator("expiration", "date", mode="before", check_fields=False)
exercise_style: Optional[str] = Field(
default=None,
description="The exercise style of the option, American or European.",
)
@field_validator(
"date",
"close_time",
"close_ask_time",
"close_bid_time",
mode="before",
check_fields=False,
)
@classmethod
def date_validate(cls, v): # pylint: disable=E0213
"""Return the datetime object from the date string."""
@@ -60,15 +72,12 @@ class IntrinioOptionsChainsFetcher(
@staticmethod
def transform_query(params: Dict[str, Any]) -> IntrinioOptionsChainsQueryParams:
"""Transform the query."""
transform_params = params
now = datetime.now().date()
if params.get("date") is None:
transform_params["date"] = (now - timedelta(days=1)).strftime("%Y-%m-%d")
elif isinstance(params["date"], dateType):
transform_params["date"] = params["date"].strftime("%Y-%m-%d")
else:
transform_params["date"] = parser.parse(params["date"]).date()
transform_params = params.copy()
if params.get("date") is not None:
if isinstance(params["date"], dateType):
transform_params["date"] = params["date"].strftime("%Y-%m-%d")
else:
transform_params["date"] = parser.parse(params["date"]).date()
return IntrinioOptionsChainsQueryParams(**transform_params)
@@ -89,18 +98,24 @@ class IntrinioOptionsChainsFetcher(
f"{base_url}/expirations/{query.symbol}/eod?"
f"after={date}&api_key={api_key}"
)
expiration = await get_data_many(url, "expirations", **kwargs)
return [
f"{base_url}/chain/{query.symbol}/{expiration}/eod?date={query.date}&api_key={api_key}"
for expiration in expiration
]
expirations = await get_data_many(url, "expirations", **kwargs)
def generate_url(expiration) -> str:
url = f"{base_url}/chain/{query.symbol}/{expiration}/eod?date="
if query.date is not None:
url += date
return url + f"&api_key={api_key}"
return [generate_url(expiration) for expiration in expirations]
async def callback(response: ClientResponse, _: Any) -> list:
"""Return the response."""
response_data = await response.json()
return response_data.get("chain", [])
date = get_weekday(query.date)
date = datetime.now().date() if query.date is None else query.date
date = get_weekday(date)
results = await amake_requests(await get_urls(date), callback, **kwargs)
if not results:
@@ -115,4 +130,7 @@ class IntrinioOptionsChainsFetcher(
) -> List[IntrinioOptionsChainsData]:
"""Return the transformed data."""
data = [{**item["option"], **item["prices"]} for item in data]
data = sorted(
data, key=lambda x: (x["expiration"], x["strike"], x["type"]), reverse=False
)
return [IntrinioOptionsChainsData.model_validate(d) for d in data]

View File

@@ -115,7 +115,7 @@ def test_intrinio_equity_quote_fetcher(credentials=test_credentials):
@pytest.mark.record_http
def test_intrinio_options_chains_fetcher(credentials=test_credentials):
params = {"symbol": "AAPL", "date": "2023-09-15"}
params = {"symbol": "AAPL", "date": date(2023, 9, 15)}
fetcher = IntrinioOptionsChainsFetcher()
result = fetcher.test(params, credentials)

View File

@@ -1,80 +0,0 @@
---
title: European Index Constituents
description: OpenBB Platform Data Model
---
<!-- markdownlint-disable MD012 MD031 MD033 -->
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
---
## Implementation details
### Class names
| Model name | Parameters class | Data class |
| ---------- | ---------------- | ---------- |
| `EuropeanIndexConstituents` | `EuropeanIndexConstituentsQueryParams` | `EuropeanIndexConstituentsData` |
### Import Statement
```python
from openbb_core.provider.standard_models.european_index_constituents import (
EuropeanIndexConstituentsData,
EuropeanIndexConstituentsQueryParams,
)
```
## Parameters
<Tabs>
<TabItem value="standard" label="Standard">
| Name | Type | Description | Default | Optional |
| ---- | ---- | ----------- | ------- | -------- |
| symbol | Union[str, List[str]] | Symbol to get data for. | | False |
| provider | Literal['cboe'] | The provider to use for the query, by default None. If None, the provider specified in defaults is selected or 'cboe' if there is no default. | cboe | True |
</TabItem>
</Tabs>
## Data
<Tabs>
<TabItem value="standard" label="Standard">
| Name | Type | Description |
| ---- | ---- | ----------- |
| symbol | str | Symbol representing the entity requested in the data. The symbol is the constituent company in the index. |
| price | float | Current price of the constituent company in the index. |
| open | float | The open price. |
| high | float | The high price. |
| low | float | The low price. |
| close | float | The close price. |
| volume | float | The trading volume. |
</TabItem>
<TabItem value='cboe' label='cboe'>
| Name | Type | Description |
| ---- | ---- | ----------- |
| symbol | str | Symbol representing the entity requested in the data. The symbol is the constituent company in the index. |
| price | float | Current price of the constituent company in the index. |
| open | float | The open price. |
| high | float | The high price. |
| low | float | The low price. |
| close | float | The close price. |
| volume | float | The trading volume. |
| prev_close | float | Previous closing price. |
| change | float | Change in price. |
| change_percent | float | Change in price as a percentage. |
| tick | str | Whether the last sale was an up or down tick. |
| last_trade_timestamp | datetime | Last trade timestamp for the symbol. |
| exchange_id | int | The Exchange ID number. |
| seqno | int | Sequence number of the last trade on the tape. |
| asset_type | str | Type of asset. |
</TabItem>
</Tabs>

View File

@@ -1,79 +0,0 @@
---
title: Historical European Indices
description: OpenBB Platform Data Model
---
<!-- markdownlint-disable MD012 MD031 MD033 -->
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
---
## Implementation details
### Class names
| Model name | Parameters class | Data class |
| ---------- | ---------------- | ---------- |
| `EuropeanIndices` | `EuropeanIndicesQueryParams` | `EuropeanIndicesData` |
### Import Statement
```python
from openbb_core.provider.standard_models.european_indices import (
EuropeanIndicesData,
EuropeanIndicesQueryParams,
)
```
## Parameters
<Tabs>
<TabItem value="standard" label="Standard">
| Name | Type | Description | Default | Optional |
| ---- | ---- | ----------- | ------- | -------- |
| symbol | Union[str, List[str]] | Symbol to get data for. | | False |
| start_date | Union[date, str] | Start date of the data, in YYYY-MM-DD format. | None | True |
| end_date | Union[date, str] | End date of the data, in YYYY-MM-DD format. | None | True |
| provider | Literal['cboe'] | The provider to use for the query, by default None. If None, the provider specified in defaults is selected or 'cboe' if there is no default. | cboe | True |
</TabItem>
<TabItem value='cboe' label='cboe'>
| Name | Type | Description | Default | Optional |
| ---- | ---- | ----------- | ------- | -------- |
| symbol | Union[str, List[str]] | Symbol to get data for. | | False |
| start_date | Union[date, str] | Start date of the data, in YYYY-MM-DD format. | None | True |
| end_date | Union[date, str] | End date of the data, in YYYY-MM-DD format. | None | True |
| provider | Literal['cboe'] | The provider to use for the query, by default None. If None, the provider specified in defaults is selected or 'cboe' if there is no default. | cboe | True |
| interval | Literal['1m', '1d'] | Data granularity. | 1d | True |
</TabItem>
</Tabs>
## Data
<Tabs>
<TabItem value="standard" label="Standard">
| Name | Type | Description |
| ---- | ---- | ----------- |
| date | datetime | The date of the data. |
| close | float | The close price. |
</TabItem>
<TabItem value='cboe' label='cboe'>
| Name | Type | Description |
| ---- | ---- | ----------- |
| date | datetime | The date of the data. |
| close | float | The close price. |
| open | float | Opening price for the interval. Only valid when interval is 1m. |
| high | float | High price for the interval. Only valid when interval is 1m. |
| low | float | Low price for the interval. Only valid when interval is 1m. |
| utc_datetime | datetime | UTC datetime. Only valid when interval is 1m. |
</TabItem>
</Tabs>

View File

@@ -2851,53 +2851,6 @@
"index constituents founding year"
]
},
"index.european": {
"title": "european",
"description": "Learn how to retrieve Historical European Indices data using the OBB\n Python library. Understand the available parameters, return values, and data fields.",
"keywords": [
"Historical European Indices",
"python obb index european",
"symbol parameter",
"start_date parameter",
"end_date parameter",
"provider parameter",
"interval parameter",
"returns",
"data",
"date",
"close price",
"open price",
"high price",
"low price",
"utc datetime"
]
},
"index.european_constituents": {
"title": "european_constituents",
"description": "Learn how to get the constituents of select European indices using the\n `obb.index.european_constituents` function in Python. Explore the parameters, return\n values, and data structure.",
"keywords": [
"European Index Constituents",
"European indices",
"python obb.index.european_constituents",
"symbol",
"provider",
"data",
"price",
"open",
"high",
"low",
"close",
"volume",
"prev_close",
"change",
"change percent",
"tick",
"last trade timestamp",
"exchange ID",
"seqno",
"asset type"
]
},
"index.fred": {
"title": "fred",
"description": "Learn how to retrieve Historical Fred Indices close values for selected\n symbols using the OBB Python library. This documentation page provides details on\n the required parameters, their types, and default values. It also explains the structure\n of the returned data and its fields.",