Merge branch 'develop' into feature/charting-obbject

This commit is contained in:
Henrique Joaquim
2024-02-06 14:34:00 +00:00
committed by GitHub
45 changed files with 5541 additions and 1140 deletions

View File

@@ -46,7 +46,7 @@ def model_t_discriminator(v: Any) -> str:
if isinstance(v, dict):
return v.get("provider", "openbb")
if isinstance(v, list):
if isinstance(v, list) and v:
return model_t_discriminator(v[0])
return getattr(v, "provider", "openbb")
@@ -397,7 +397,7 @@ class SignatureInspector:
Also updates __name__ and __doc__ for API schemas.
"""
union_models = [Annotated[None, Tag("openbb")]]
union_models = [Annotated[Union[list, dict], Tag("openbb")]]
for provider, return_type in return_map.items():
union_models.append(Annotated[return_type, Tag(provider)])
@@ -406,15 +406,17 @@ class SignatureInspector:
f"OBBject_{model}",
__base__=OBBject,
results=(
Annotated[
Union[tuple(union_models)], # type: ignore
Field(
...,
description="Serializable results.",
discriminator=Discriminator(model_t_discriminator),
),
Optional[
Annotated[
Union[tuple(union_models)], # type: ignore
Field(
None,
description="Serializable results.",
discriminator=Discriminator(model_t_discriminator),
),
]
],
Field(..., description="Serializable results."),
Field(None, description="Serializable results."),
),
)

View File

@@ -113,17 +113,30 @@ class RegistryMap:
"""Extract info (fields and docstring) from fetcher query params or data."""
model: BaseModel = RegistryMap._get_model(fetcher, "data")
class DataModel(model):
model_config = ConfigDict(alias_generator=alias_generators.to_snake)
fields = {}
for field_name, field in model.model_fields.items():
field.alias_priority = None
fields[field_name] = (field.annotation, field)
provider: Literal[provider_str, "openbb"] = Field( # type: ignore
default=provider_str,
description="The data provider for the data.",
exclude=True,
)
fields.pop("provider", None)
return create_model(
model.__name__, __base__=DataModel, __module__=model.__module__
model.__name__.replace("Data", ""),
__doc__=model.__doc__,
__config__=ConfigDict(
extra="allow",
alias_generator=alias_generators.to_snake,
populate_by_name=True,
),
provider=(
Literal[provider_str, "openbb"], # type: ignore
Field(
default=provider_str,
description="The data provider for the data.",
exclude=True,
),
),
**fields,
)
@staticmethod

View File

@@ -27,12 +27,12 @@ class CalendarDividendQueryParams(QueryParams):
class CalendarDividendData(Data):
"""Dividend Calendar Data."""
date: dateType = Field(
description=DATA_DESCRIPTIONS.get("date", "") + " (Ex-Dividend)"
ex_dividend_date: dateType = Field(
description="The ex-dividend date - the date on which the stock begins trading without rights to the dividend."
)
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
amount: Optional[float] = Field(
default=None, description="Dividend amount, per-share."
default=None, description="The dividend amount per share."
)
name: Optional[str] = Field(default=None, description="Name of the entity.")
record_date: Optional[dateType] = Field(

View File

@@ -16,6 +16,10 @@ class EquitySearchQueryParams(QueryParams):
is_symbol: bool = Field(
description="Whether to search by ticker symbol.", default=False
)
use_cache: Optional[bool] = Field(
default=True,
description="Whether to use the cache or not.",
)
class EquitySearchData(Data):

View File

@@ -8,7 +8,6 @@ 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,
)
@@ -35,5 +34,7 @@ class HistoricalDividendsQueryParams(QueryParams):
class HistoricalDividendsData(Data):
"""Historical Dividends Data."""
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
dividend: float = Field(description="Dividend of the historical dividends.")
ex_dividend_date: dateType = Field(
description="The ex-dividend date - the date on which the stock begins trading without rights to the dividend."
)
amount: float = Field(description="The dividend amount per share.")

View File

@@ -71,11 +71,9 @@ def test_crypto_search(params, headers):
),
(
{
"multiplier": 1,
"timespan": "minute",
"interval": "1m",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"provider": "polygon",
"symbol": "BTCUSD",
"start_date": "2023-01-01",
@@ -84,11 +82,9 @@ def test_crypto_search(params, headers):
),
(
{
"multiplier": 1,
"timespan": "day",
"interval": "1d",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"provider": "polygon",
"symbol": "BTCUSD",
"start_date": "2023-01-01",

View File

@@ -65,11 +65,9 @@ def test_crypto_search(params, obb):
),
(
{
"multiplier": 1,
"timespan": "minute",
"interval": "1m",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"provider": "polygon",
"symbol": "BTCUSD",
"start_date": "2023-01-01",
@@ -78,11 +76,9 @@ def test_crypto_search(params, obb):
),
(
{
"multiplier": 1,
"timespan": "day",
"interval": "1d",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"provider": "polygon",
"symbol": "BTCUSD",
"start_date": "2023-01-01",

View File

@@ -81,11 +81,9 @@ def test_currency_search(params, headers):
),
(
{
"multiplier": 1,
"timespan": "minute",
"interval": "1m",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"provider": "polygon",
"symbol": "EURUSD",
"start_date": "2023-01-01",
@@ -94,11 +92,9 @@ def test_currency_search(params, headers):
),
(
{
"multiplier": 1,
"timespan": "day",
"interval": "1d",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"provider": "polygon",
"symbol": "EURUSD",
"start_date": "2023-01-01",

View File

@@ -76,11 +76,9 @@ def test_currency_search(params, obb):
),
(
{
"multiplier": 1,
"timespan": "minute",
"interval": "1m",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"provider": "polygon",
"symbol": "EURUSD",
"start_date": "2023-01-01",
@@ -89,11 +87,9 @@ def test_currency_search(params, obb):
),
(
{
"multiplier": 1,
"timespan": "day",
"interval": "1d",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"provider": "polygon",
"symbol": "EURUSD",
"start_date": "2023-01-01",

View File

@@ -313,6 +313,14 @@ def test_equity_fundamental_historical_splits(params, headers):
"provider": "fmp",
}
),
(
{
"symbol": "AAPL",
"start_date": "2021-01-01",
"end_date": "2023-06-06",
"provider": "nasdaq",
}
),
(
{
"symbol": "AAPL",
@@ -1204,7 +1212,15 @@ def test_equity_fundamental_latest_attributes(params, headers):
({"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}),
(
{
"query": "gold",
"provider": "intrinio",
"active": True,
"limit": 100,
"use_cache": None,
}
),
],
)
@pytest.mark.integration

View File

@@ -290,6 +290,14 @@ def test_equity_fundamental_historical_splits(params, obb):
"provider": "fmp",
}
),
(
{
"symbol": "AAPL",
"start_date": "2021-01-01",
"end_date": "2023-06-06",
"provider": "nasdaq",
}
),
(
{
"symbol": "AAPL",
@@ -1142,7 +1150,15 @@ def test_equity_fundamental_latest_attributes(params, obb):
({"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}),
(
{
"query": "gold",
"provider": "intrinio",
"active": True,
"limit": 100,
"use_cache": None,
}
),
],
)
@pytest.mark.integration

View File

@@ -90,30 +90,24 @@ def test_index_constituents(params, headers):
),
(
{
"timespan": "minute",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"interval": "1m",
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": None,
}
),
(
{
"timespan": "day",
"interval": "1d",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": None,
}
),
(

View File

@@ -86,30 +86,24 @@ def test_index_constituents(params, obb):
),
(
{
"timespan": "minute",
"interval": "1m",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": None,
}
),
(
{
"timespan": "day",
"interval": "1d",
"sort": "desc",
"limit": 49999,
"adjusted": True,
"multiplier": 1,
"provider": "polygon",
"symbol": "NDX",
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": None,
}
),
(

View File

@@ -17,12 +17,6 @@ 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."""

View File

@@ -30,6 +30,7 @@ class FMPCalendarDividendData(CalendarDividendData):
__alias_dict__ = {
"amount": "dividend",
"ex_dividend_date": "date",
"record_date": "recordDate",
"payment_date": "paymentDate",
"declaration_date": "declarationDate",
@@ -45,7 +46,7 @@ class FMPCalendarDividendData(CalendarDividendData):
)
@field_validator(
"date",
"ex_dividend_date",
"record_date",
"payment_date",
"declaration_date",

View File

@@ -27,6 +27,11 @@ class FMPHistoricalDividendsQueryParams(HistoricalDividendsQueryParams):
class FMPHistoricalDividendsData(HistoricalDividendsData):
"""FMP Historical Dividends Data."""
__alias_dict__ = {
"ex_dividend_date": "date",
"amount": "dividend",
}
label: str = Field(description="Label of the historical dividends.")
adj_dividend: float = Field(
description="Adjusted dividend of the historical dividends."
@@ -48,6 +53,7 @@ class FMPHistoricalDividendsData(HistoricalDividendsData):
"declaration_date",
"record_date",
"payment_date",
"ex_dividend_date",
mode="before",
check_fields=False,
)
@@ -99,7 +105,7 @@ class FMPHistoricalDividendsFetcher(
query: FMPHistoricalDividendsQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPHistoricalDividendsData]:
"""Return the transformed data."""
result = []
result: List[FMPHistoricalDividendsData] = []
for d in data:
if "date" in d:
dt = parser.parse(str(d["date"])).date()

View File

@@ -12,7 +12,7 @@ from openbb_core.provider.standard_models.index_historical import (
from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
from openbb_core.provider.utils.helpers import get_querystring
from openbb_fmp.utils.helpers import get_data_many
from pydantic import Field, NonNegativeInt, validator
from pydantic import Field, NonNegativeInt, field_validator
class FMPIndexHistoricalQueryParams(IndexHistoricalQueryParams):
@@ -30,7 +30,7 @@ class FMPIndexHistoricalQueryParams(IndexHistoricalQueryParams):
Field(default="1day", description="Data granularity.")
)
@validator("interval")
@field_validator("interval")
@classmethod
def map_interval(cls, v):
"""Map the interval from standard to the FMP format."""

View File

@@ -33,16 +33,25 @@ class IntrinioHistoricalDividendsQueryParams(HistoricalDividendsQueryParams):
class IntrinioHistoricalDividendsData(HistoricalDividendsData):
"""Intrinio Historical Dividends Data."""
factor: float = Field(
__alias_dict__ = {
"ex_dividend_date": "date",
"amount": "dividend",
"currency": "dividend_currency",
}
factor: Optional[float] = Field(
default=None,
description=(
"factor by which to multiply stock prices before this date, "
"in order to calculate historically-adjusted stock prices."
),
)
dividend_currency: Optional[str] = Field(
default=None, description="The currency of the dividend."
currency: Optional[str] = Field(
default=None,
description="The currency in which the dividend is paid.",
)
split_ratio: float = Field(
split_ratio: Optional[float] = Field(
default=None,
description="The ratio of the stock split, if a stock split occurred.",
)

View File

@@ -3,36 +3,37 @@ interactions:
body: null
headers:
Accept:
- '*/*'
- application/json
Accept-Encoding:
- gzip, deflate, br
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api-v2.intrinio.com/companies/search?active=True&api_key=MOCK_API_KEY&page_size=100&query=gold
uri: https://api-v2.intrinio.com/companies/search?active=True&api_key=MOCK_API_KEY&page_size=100&query=gold&use_cache=True
response:
body:
string: !!binary |
H4sIAHb8b2UAA4yWW3PiOBCF/wrF8+yWkGxZnjdfQAFsY2wuhq2tKQMujyfEJlwSYGr++4rNZlCL
YnHewOFT6/ic7v7ZXFYvm7Qssl3z618/m8Wq+fXy1bfF+bW/yJtfmvti+Zxtxbd84Lnic5m+ZOKT
bUVR1+k3Lt82nEEUikfrrBBP0EDr20OnN08YHve5Y08MIh4ui+fLQ/Fn6JSZWvPXF+m4c3vS3pzk
47rWOLkeV/zBUINX61XDqbabPz8PKw/rtYRuMZ2YFCvoV1tfAHQw4V7nyg4mV/I9MEUGQ4YC9rNX
dgZgLikUZO8f3G65/C2OrpkEIZ0jHjhPIXqinkmBOOxyGIUH5RkbTMENxrE1vp40jh9eACFsIJMo
3NkPGiQytz/irqRMHz9WRme3kifT43oGlBlFUSKBR0F06xyVrBG9ZZpKyS7N3gA59i0ukePqJa1h
FEPAEYLs08LdWkCOiTV2JPakeKvFxgRpqld6Ln8HcfKTmF/RfnY87D7Y4/gemOg6NRTwibylQyhI
xOeSHvt0W6NmTWSH6Gpwtu4rsF00a8dXdFTssjporDGqxP20Cn60gdRekMiv0SvKw/Gx+RBjwoCQ
vc2RHwF2xOXOFVWndH2TzJu8MF1XBcmPxqYPm1TgWFLVgpc2BmUdVXRDw0Q199GfPYPKff4kG9Av
ymVVQxWMGFZqT7jHpsAnPaGKxO6lYgzUKJwyitTk5JrnUuBueySZ28b5o5I1bJpEsXYeDtfQJBPZ
2ZNit09vxfivxeKp0xl5/YndZa7G4PxhhGDVkC36boAbjGweyh2r7duDa8+6Kw+59EPlIgkNCQW+
iXkgT6C4yEvJk//TWTBuKZXnk0UJtbd494q21ulLtbs3iEbuIDHIjM+StqmbskqtliGypRpUa//A
MFoTT45tlL1ldS5ChY2Ymi7khS7cOKaxnC5evWe7/Qfc26/uRwszTKg6q8vFM1wCOtyBw7pTHcrV
43QxDYny1S7sDUHtHz/6jG36vqvK34XfFQVjis2bSbqlILbeIJBbgleJlrB9rLhmYKq+zrM/9dtA
ccfmcwnupIttrdeJTGYoy8WZBXYFfWnJS8sF+iI6Tpwuv+8a4ffTrlh+ntUede5GAIlhoqq/iRYa
sCXvdOFJjU6RrVc7Wf6mLnoOQqSfdO3ecObYtmtgJQLYUBvFucLTEkbAHXfkCFhu97KQXTqFGA93
NWu1CKZInbhROAQudSNpcokP/2JvLzFHIeFRFGDic6I14TAQa5S6EvvTlQnezZi7XekS47LIHwxI
U6MaUTeotauvQf3WnMviWPNR27Eer35ipSS6GuFeSG1oqJgPZPguLfe1Zq9Y0FTpczzL2qB0zwe7
sHd4Kco66x81CSaK4PmQtxcwxmEf7DuHTfpch65jsVuqsvdDLwXKxP6gA5bistpWdeiEtZjoEn+L
n2bH/bdNmmcf//XrHwAAAP//AwB3pkhmKg4AAA==
H4sIAN0HwWUAA4yX23LiOBCGX4XienZLlnyQ586nCPABkDkYtramDHg9zoDNKQkwNe8+YkkGSRTB
uTMOn1p/99/d/GzOq9U6LYts1/z6z89msWh+PX/0bXba+LO8+aW5L+Y/si37lHQDlz2X6SpjT7ZF
advxG+dPG06X9tirZVawN6Cr+nbf6UwTDIc+ceyRgdjLefHj/JL9GZqOTbX56wt33Mkbeesjf1zb
GibX44q/MGiQarloONV2/ffHYeXLcsmhFawhU4cSemNrMwEdjUjwdGVHoyv5HlgHBgaGCM4z3B0L
4GFsDa/cYfyQCwA0gIkk7uRZjxKe6w+IywXsw8cBa/hWiWR8WE5OPHhAacKBBxG9TahMVpGmmKZI
Pi7dStQ4JtThyHFavsf8GRmoWFFuyFORTD3H4sjBMHIvQQcD9x6Z1RyrOklmK/UXuagGV3Hs4V2L
T1RWDKSrUvqOij41BG47ph0u4l5EauQPGoYi5S939exVyF8cWoRXuVqlNVxisBQCIEU9c7eWUHQj
a8hncFS81mJDBFTJKKcw22Ah7ohwrSTK3i7gdjn/00U01UQAaASQyGn1QEsPTF3oIvjsSimlp45L
3gTpwyQm15PC7PCyu5w1jO/dAGmabsg32NhArMLWpMVb0rGm1tU6d9VRVUNRpKCP6DXti1mlZMol
dZ9uawivsu6HNDnsrbsRzTPx4iuaFrusDhqqWJcb9mpblgLaIS7fSxIraNdRxDBVLNViYnTGmZDG
uCU0wLjVpd4F3o6ce2iIkWLIYi+iZ08o8yBKeAsFRflyeGxPgDFrsZI9ATWp2F4nLd747NHzHjdY
xcAqlNmHcPJDiDskLd6eYVHOqxpxQ4ChVCW5HYJQSKUXUO+KnhTlfy91ygRhVZfad0ICPBY06ZCA
T2UnXf8ZDZ/BdawDuWflauDqQp3YA87tNswfyaFC00SS17c5CKkgNSX85kOrY7q8aVg3gx1rmmzH
/GCsfXHJiYRZxnhpo1vW8aRmqBBJUzjRe0iXpnDEbzpxkZdc7J80cQgVyfD5aFaKYlukfUVby3RV
7e718YHbTQw0IZPEMzWzKVU7Vkw5sd4zFFMwCniX0uw1q3MRndUN1uR+G/Rd/iKXL334KH3bVe8F
GewXd8EQ6lCWf3zY6kKtB92I92hQMY9uH0etGlCXJTmF49AT5HdsMuUnUDrb1pIEmNiQVpYTjuxK
zK3Fb7Jn6IrZNE7n33eN3vfjrph/nOUNnu6WEWBjQx7RG9rrCzXqUpcz7fnp/2Xuqn1TYy4FYAp6
iFAaQRQSpDbFrsZ2UnntD8cLU7jTkLhtTrBhWeQPTGyqbLuTm06vvxQnyIif1qNit09v+/C7D+DY
eRoE/shuY1fF4q8ijBCUh2ynp9tiXmLS5e5g7dJyX6tdsLVMl2/SX2/EdmRFvkCPiN+lNYa4AQFQ
ZTqcZJ5AD0JhiAcvq6Kss1LqJoJIyu7R7wWpuCKE3SdhFS6rbVVHGYQVLPefbLE0RWVaEeWEGbS8
yHo8ydkqrALG/pd9Mzvsv63TPLv816/fAAAA//8DANBYgPZ5DwAA
headers:
Connection:
- keep-alive
@@ -41,7 +42,7 @@ interactions:
Content-Type:
- application/json
Date:
- Wed, 06 Dec 2023 04:45:42 GMT
- Mon, 05 Feb 2024 16:07:58 GMT
Transfer-Encoding:
- chunked
Vary:

View File

@@ -8,6 +8,7 @@ from openbb_nasdaq.models.cot import NasdaqCotFetcher
from openbb_nasdaq.models.cot_search import NasdaqCotSearchFetcher
from openbb_nasdaq.models.economic_calendar import NasdaqEconomicCalendarFetcher
from openbb_nasdaq.models.equity_search import NasdaqEquitySearchFetcher
from openbb_nasdaq.models.historical_dividends import NasdaqHistoricalDividendsFetcher
from openbb_nasdaq.models.lbma_fixing import NasdaqLbmaFixingFetcher
from openbb_nasdaq.models.sp500_multiples import NasdaqSP500MultiplesFetcher
from openbb_nasdaq.models.top_retail import NasdaqTopRetailFetcher
@@ -27,6 +28,7 @@ unmatched technology, insights and markets expertise.""",
"COTSearch": NasdaqCotSearchFetcher,
"EconomicCalendar": NasdaqEconomicCalendarFetcher,
"EquitySearch": NasdaqEquitySearchFetcher,
"HistoricalDividends": NasdaqHistoricalDividendsFetcher,
"LbmaFixing": NasdaqLbmaFixingFetcher,
"SP500Multiples": NasdaqSP500MultiplesFetcher,
"TopRetail": NasdaqTopRetailFetcher,

View File

@@ -1,5 +1,6 @@
"""Nasdaq Dividend Calendar Model."""
# pylint: disable=unused-argument
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
@@ -26,7 +27,7 @@ class NasdaqCalendarDividendData(CalendarDividendData):
__alias_dict__ = {
"name": "companyName",
"date": "dividend_Ex_Date",
"ex_dividend_date": "dividend_Ex_Date",
"payment_date": "payment_Date",
"record_date": "record_Date",
"declaration_date": "announcement_Date",
@@ -40,13 +41,14 @@ class NasdaqCalendarDividendData(CalendarDividendData):
)
@field_validator(
"date",
"ex_dividend_date",
"record_date",
"payment_date",
"declaration_date",
mode="before",
check_fields=False,
)
@classmethod
def validate_date(cls, v: str):
v = v.replace("N/A", "")
return datetime.strptime(v, "%m/%d/%Y").date() if v else None
@@ -115,4 +117,7 @@ class NasdaqCalendarDividendFetcher(
**kwargs: Any, # pylint: disable=unused-argument
) -> List[NasdaqCalendarDividendData]:
"""Return the transformed data."""
return [NasdaqCalendarDividendData.model_validate(d) for d in data]
return [
NasdaqCalendarDividendData.model_validate(d)
for d in sorted(data, key=lambda x: x["dividend_Ex_Date"], reverse=True)
]

View File

@@ -33,10 +33,6 @@ class NasdaqEquitySearchQueryParams(EquitySearchQueryParams):
default=None,
description="If True, returns ETFs.",
)
use_cache: Optional[bool] = Field(
default=True,
description="If True, caches the symbol directory for one day.",
)
class NasdaqEquitySearchData(EquitySearchData):
@@ -108,16 +104,17 @@ class NasdaqEquitySearchFetcher(
@staticmethod
def transform_query(params: Dict[str, Any]) -> NasdaqEquitySearchQueryParams:
"""Transform the query parameters."""
return NasdaqEquitySearchQueryParams(**params)
# pylint: disable=unused-argument
@staticmethod
async def extract_data(
async def aextract_data(
query: NasdaqEquitySearchQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> str:
"""Extract data from Nasdaq."""
url = "https://www.nasdaqtrader.com/dynamic/SymDir/nasdaqtraded.txt"
def fetch_data():
@@ -133,6 +130,7 @@ class NasdaqEquitySearchFetcher(
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, fetch_data)
# pylint: disable=unused-argument
@staticmethod
def transform_data(
query: NasdaqEquitySearchQueryParams,
@@ -140,7 +138,6 @@ class NasdaqEquitySearchFetcher(
**kwargs: Any,
) -> List[NasdaqEquitySearchData]:
"""Transform the data and filter the results."""
directory = read_csv(StringIO(data), sep="|").iloc[:-1]
if query.is_etf is True:

View File

@@ -0,0 +1,156 @@
"""Nasdaq Historical Dividends Model."""
# pylint: disable=unused-argument
import asyncio
import warnings
from datetime import (
date as dateType,
datetime,
)
from typing import Any, Dict, List, Optional
from dateutil import parser
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.historical_dividends import (
HistoricalDividendsData,
HistoricalDividendsQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request
from openbb_nasdaq.utils.helpers import IPO_HEADERS
from pydantic import Field, field_validator
_warn = warnings.warn
class NasdaqHistoricalDividendsQueryParams(HistoricalDividendsQueryParams):
"""Nasdaq Historical Dividends Query Params."""
class NasdaqHistoricalDividendsData(HistoricalDividendsData):
"""Nasdaq Historical Dividends Data."""
__alias_dict__ = {
"ex_dividend_date": "exOrEffDate",
"declaration_date": "declarationDate",
"record_date": "recordDate",
"payment_date": "paymentDate",
"dividend_type": "type",
}
dividend_type: Optional[str] = Field(
default=None,
description="The type of dividend - i.e., cash, stock.",
)
currency: Optional[str] = Field(
default=None,
description="The currency in which the dividend is paid.",
)
record_date: Optional[dateType] = Field(
default=None,
description="The record date of ownership for eligibility.",
)
payment_date: Optional[dateType] = Field(
default=None,
description="The payment date of the dividend.",
)
declaration_date: Optional[dateType] = Field(
default=None,
description="Declaration date of the dividend.",
)
@field_validator(
"ex_dividend_date",
"declaration_date",
"record_date",
"payment_date",
mode="before",
check_fields=False,
)
@classmethod
def validate_date(cls, v: str):
"""Validate the date if available is a date object."""
v = v.replace("N/A", "")
if not v:
return None
return datetime.strptime(v, "%m/%d/%Y").date().strftime("%Y-%m-%d")
@field_validator("amount", mode="before", check_fields=False)
@classmethod
def validate_amount(cls, v: str):
"""Validate the amount if available is a float."""
v = v.replace("$", "").replace("N/A", "")
if not v:
return None
return float(v)
class NasdaqHistoricalDividendsFetcher(
Fetcher[NasdaqHistoricalDividendsQueryParams, List[NasdaqHistoricalDividendsData]]
):
"""Nasdaq Historical Dividends Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> NasdaqHistoricalDividendsQueryParams:
"""Transform the params to the provider-specific query."""
return NasdaqHistoricalDividendsQueryParams(**params)
@staticmethod
async def aextract_data(
query: NasdaqHistoricalDividendsQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Extract the raw data."""
results = []
symbols = query.symbol.split(",")
async def get_one(symbol):
"""Response Callback."""
data = []
asset_class = "stocks"
url = f"https://api.nasdaq.com/api/quote/{symbol}/dividends?assetclass={asset_class}"
response = await amake_request(
url,
headers=IPO_HEADERS,
)
if response.get("status").get("rCode") == 400:
response = await amake_request(
url.replace("stocks", "etf"),
headers=IPO_HEADERS,
)
if response.get("status").get("rCode") == 200:
data = response.get("data").get("dividends").get("rows")
if data:
if len(symbols) > 1:
for d in data:
d["symbol"] = symbol
results.extend(data)
if not data:
_warn(f"No data found for {symbol}")
tasks = [get_one(symbol) for symbol in symbols]
await asyncio.gather(*tasks)
if results:
return results
raise EmptyDataError()
@staticmethod
def transform_data(
query: NasdaqHistoricalDividendsQueryParams,
data: List[Dict],
**kwargs: Any,
) -> List[NasdaqHistoricalDividendsData]:
"""Return the transformed data."""
results: List[NasdaqHistoricalDividendsData] = []
for d in data:
dt = parser.parse(str(d["exOrEffDate"])).date()
if query.start_date and query.start_date > dt:
continue
if query.end_date and query.end_date < dt:
continue
results.append(NasdaqHistoricalDividendsData(**d))
return results

View File

@@ -0,0 +1,100 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json, text/plain, */*
Accept-Encoding:
- gzip
Accept-Language:
- en-CA,en-US;q=0.7,en;q=0.3
Connection:
- keep-alive
Host:
- api.nasdaq.com
Origin:
- https://www.nasdaq.com
Referer:
- https://www.nasdaq.com/
method: GET
uri: https://api.nasdaq.com/api/quote/AAPL/dividends?assetclass=stocks
response:
body:
string: !!binary |
H4sIAAAAAAAAAK2aTW/jNhCG/0ogtDfXJilRlnILNln00u5iuy1QFHvQxko3gGMH/kiTLvzfq6E5
tFQNlfFEp8Qa8tWTmdekRsz3ZFHtquTye7K4f7pf1KvFz3W1qDd/VMt9vU0u//qeLKuv9TK5TG6e
f7r2Yy6uq12dTJInGNWEtJ5pNTPKpMlhcpoRhv95Xy8XrfFqmpU/doZerVb7anmBM1pjf1DTMu+M
/Ti7ufhU7e7XrVGpnmZZcvgySepnFHGQHbhJ+Cs/Vi8P9WrXGpLjkBcHi5CTpHJo9//Wi0AHwQZq
kjxWL+v97giDEKebbCGv1fbDXXK52i+Xk+Sby627XD9/2Nzc3XmAm+fZzfv3mNbdyyNc/Aw/mvs/
rPerXfP5XbX9dnF1/NTcpL5dVhu488qLXJ+uoNKmvl1vMBGf3AcMPXYy4PNxDB6aiet/jtXvcnZy
6TEBq43ZFMxkJGAzWxmc3UED3RQjj/Ha3O43m3p1+9Jc//23a+eKLqAqZs0EKWAzW6UkIOhmNCCE
5nxAO9NGDmhnKqMBG10bAWxCBR/Q8Eqc0oAmVmLQjZQYQvwSg4tcDowEEP62Oc7+vwdViPQ9qDDE
8aCyYkBgKEhA0C1owGB7HmDjolwOmEUBG90yAoi25wEaXokNDahjJQbdSImD7VmA4BVXYi0BhBsV
OLvnwRDpe1BjiOXBXAwISZqTgKBb0oDgQcMHtL4UMsAslkEwmooAWr8C8QANr8TKDpuwT2iiNQYT
8msMZnE1ViJCSFOJ03suDJG+Cw2GWC6cv05YRF2YKhIQrKZoQAilfEDrayEDzKKA1heSALT+SYIH
aFgZnM+jJixoQBPPoDkng7hr6lIC2DC4DLrZvSdCjZG+BzMMsTxYigHRg31AXO4IQAhZPqBfsYSA
WRTQL3cUoD0+cDEB/YL1GmDkcUEflxkC0ERLDCF+iXHX1IUEEGZrnN3zoMFI34MWQ6yuRIkBwYM0
IC52BCCEcj6gX6+EgDaWQVzsKEB7bJuYgMZ/i4cB83hXEgE00RJDiF9ibBH0XAKInbGbTXbGLkJ3
xi7E96AIEFZRTQJiZ0wAYmfMBEQPygBtLIPYGVOAvjNmAqIHhwFtdC9OIxk00RJjZ8wDBBc5oVwC
CO6wOJvsjHVOelBhiLUXZ2JAYMhJQOyMCUDsjJmA9vjQLwTMooC+M6YAfWfMBDS8DMY74wigiWfQ
nJNB7Iy1lQDi2xk3m+xJXITuSVyI3RnLAMGDmgQM62sfMGzTPEDfGQsBs1gGw/pKAOI2zQM0rBJn
8Z4kAmiiJQ7bNAsQG2OdSQBdFXF2by8Okb4HUwyx+2IZIEw1JGBY7PqAYZvmAfq+eBAwnZoy6sGU
BsTFjgDEbZoHaBglTqevvJwhAE20xGGbZgEGDw69Ro8C4htCTZ6UaIxE+mLuSYkqxIDgwZQEDH0x
fVJi+YD+1ccgoJnm8RJHAE00g6Ht5B5ENHuBLsvhdVDTC/WvsyuitifBLtlxNOshPx8TqSMoQ7Lw
zDEmUltQhuS+yWMitQVFSPDdLUBheEc4y0snQbGX7JhIHUGxl+ajIrUFpV5S4yK1BcVeKkFh+AT3
PC8FwbetS2MhdQTFXipGRWoLitclMypSW1DqpVSBwvBJ7FleOgmKvTQfE6kjKEPK4e3YmEhtQbGX
slGR2oJv2+OGT1QFe1z/JPUML5VjInUExeuSGhWpLSj2kh0VqS0o9pJb/4fPRc/zUhCUeumY51eQ
9BleOgmKvaRHRWoLir3EKRwfyby1cFB6WP+L4fNNPlJHUOwlzUBS53hJvw3JwlunMZHagmIvcQrH
RzJvLRy28MXwOSUfqSPIQfpyOEySh3q7rf6u8f+Ot7tqt3f/drx5t140l41Sk+Qr/P5Ld+SifqqX
68d607l+OPwHWZhG6SguAAA=
headers:
Access-Control-Allow-Origin:
- https://www.nasdaq.com
Cache-Control:
- max-age=0, no-cache, no-store
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Length:
- '1442'
Content-Type:
- application/json; charset=utf-8
Date:
- Sun, 28 Jan 2024 01:33:24 GMT
Expires:
- Sun, 28 Jan 2024 01:33:24 GMT
Pragma:
- no-cache
Server:
- Kestrel
Set-Cookie:
- ak_bmsc=D814B8E13C27CB46A5C31FDFF508A2DD~000000000000000000000000000000~YAAQD6MAF7gsfjqNAQAAxSqzTRYZaYQlf5VY/MEzu8m19srQM7M9z1C2q6lrQDVzNdGuUnQhAXdlZrf9cpvIHkanLPt8/S1r1fV1+jMw1z21kB2SXFtsBaUB9zq1a2Gi2OcyyB0u/2EucnGGJfAYzP47lQBxwelZzIo/rhrp1sx/uaimm0ih8TaPsfUrRSRqgLI9GVbAgA1sLlDBvsbDPNRuKDNGz3eAI5NtmG85P3Du5dS2izz5X3RxSm3Ld07nD1zOVpBoUJkFfIuSQrQFQkOxBwAd8mpAyEHbfjnNNkqLGWw58zBG1S8taYPNX0bzy0qubakjMBD/pSn8tLinbEbtm+N9nQ5aiwCFmSMFuTgg8H37QFgnf5026avJvNLbNd8PU6RrgH9/dN5LKUT8WdrFtC2N6Q==;
Domain=.nasdaq.com; Path=/; Expires=Sun, 28 Jan 2024 03:33:24 GMT; Max-Age=7200;
HttpOnly
Strict-Transport-Security:
- max-age=86400
Vary:
- Accept-Encoding
X-EdgeConnect-MidMile-RTT:
- '0'
- '43'
- '0'
- '3'
X-EdgeConnect-Origin-MEX-Latency:
- '49'
- '49'
- '49'
- '49'
gid:
- '203'
lang:
- en
rid:
- e93e6a09-7709-403a-aaa8-208566eb1d7a
srctype:
- default
status:
code: 200
message: OK
version: 1

View File

@@ -9,6 +9,7 @@ from openbb_nasdaq.models.cot import NasdaqCotFetcher
from openbb_nasdaq.models.cot_search import NasdaqCotSearchFetcher
from openbb_nasdaq.models.economic_calendar import NasdaqEconomicCalendarFetcher
from openbb_nasdaq.models.equity_search import NasdaqEquitySearchFetcher
from openbb_nasdaq.models.historical_dividends import NasdaqHistoricalDividendsFetcher
from openbb_nasdaq.models.lbma_fixing import NasdaqLbmaFixingFetcher
from openbb_nasdaq.models.sp500_multiples import NasdaqSP500MultiplesFetcher
from openbb_nasdaq.models.top_retail import NasdaqTopRetailFetcher
@@ -126,6 +127,15 @@ def test_nasdaq_calendar_earnings_fetcher(credentials=test_credentials):
assert result is None
@pytest.mark.record_http
def test_nasdaq_historical_dividends_fetcher(credentials=test_credentials):
params = {"symbol": "AAPL"}
fetcher = NasdaqHistoricalDividendsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_nasdaq_lbma_fixing_fetcher(credentials=test_credentials):
params = {"asset": "gold"}

View File

@@ -1,5 +1,8 @@
"""Polygon Crypto Historical Price Model."""
# pylint: disable=unused-argument,protected-access,line-too-long
import warnings
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
@@ -10,8 +13,21 @@ from openbb_core.provider.standard_models.crypto_historical import (
CryptoHistoricalQueryParams,
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_polygon.utils.helpers import get_data_many
from pydantic import Field, PositiveInt
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import (
ClientResponse,
ClientSession,
amake_requests,
)
from pydantic import (
Field,
PositiveInt,
PrivateAttr,
model_validator,
)
from pytz import timezone
_warn = warnings.warn
class PolygonCryptoHistoricalQueryParams(CryptoHistoricalQueryParams):
@@ -20,11 +36,8 @@ class PolygonCryptoHistoricalQueryParams(CryptoHistoricalQueryParams):
Source: https://polygon.io/docs/crypto/get_v2_aggs_ticker__cryptoticker__range__multiplier___timespan___from___to
"""
multiplier: PositiveInt = Field(
default=1, description="Multiplier of the timespan."
)
timespan: Literal["minute", "hour", "day", "week", "month", "quarter", "year"] = (
Field(default="day", description="Timespan of the data.")
interval: str = Field(
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
)
sort: Literal["asc", "desc"] = Field(
default="desc", description="Sort order of the data."
@@ -32,7 +45,28 @@ class PolygonCryptoHistoricalQueryParams(CryptoHistoricalQueryParams):
limit: PositiveInt = Field(
default=49999, description=QUERY_DESCRIPTIONS.get("limit", "")
)
adjusted: bool = Field(default=True, description="Whether the data is adjusted.")
_multiplier: PositiveInt = PrivateAttr(default=None)
_timespan: str = PrivateAttr(default=None)
@model_validator(mode="after")
@classmethod
def get_api_interval_params(cls, values: "PolygonCryptoHistoricalQueryParams"):
"""Get the multiplier and timespan parameters for the Polygon API."""
intervals = {
"s": "second",
"m": "minute",
"h": "hour",
"d": "day",
"W": "week",
"M": "month",
"Q": "quarter",
"Y": "year",
}
values._multiplier = int(values.interval[:-1])
values._timespan = intervals[values.interval[-1]]
return values
class PolygonCryptoHistoricalData(CryptoHistoricalData):
@@ -84,28 +118,56 @@ class PolygonCryptoHistoricalFetcher(
query: PolygonCryptoHistoricalQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> dict:
) -> List[Dict]:
"""Extract raw data from the Polygon endpoint."""
api_key = credentials.get("polygon_api_key") if credentials else ""
request_url = (
f"https://api.polygon.io/v2/aggs/ticker/"
f"X:{query.symbol}/range/{query.multiplier}/{query.timespan}/"
f"{query.start_date}/{query.end_date}?adjusted={query.adjusted}"
f"&sort={query.sort}&limit={query.limit}&apiKey={api_key}"
)
data = await get_data_many(request_url, "results", **kwargs)
urls = [
(
"https://api.polygon.io/v2/aggs/ticker/"
f"X:{symbol.upper()}/range/{query._multiplier}/{query._timespan}/"
f"{query.start_date}/{query.end_date}?"
f"&sort={query.sort}&limit={query.limit}&apiKey={api_key}"
)
for symbol in query.symbol.split(",")
]
for d in data:
d["t"] = datetime.fromtimestamp(d["t"] / 1000)
if query.timespan not in ["minute", "hour"]:
d["t"] = d["t"].date()
async def callback(
response: ClientResponse, session: ClientSession
) -> List[Dict]:
data = await response.json()
return data
symbol = response.url.parts[4]
next_url = data.get("next_url", None)
results: list = data.get("results", [])
while next_url:
url = f"{next_url}&apiKey={api_key}"
data = await session.get_json(url)
results.extend(data.get("results", []))
next_url = data.get("next_url", None)
for r in results:
r["t"] = datetime.fromtimestamp(r["t"] / 1000, tz=timezone("UTC"))
if query._timespan not in ["second", "minute", "hour"]:
r["t"] = r["t"].date()
else:
r["t"] = r["t"].strftime("%Y-%m-%dT%H:%M:%S%z")
if "," in query.symbol:
r["symbol"] = symbol.replace("X:", "")
if results == []:
_warn(f"Symbol Error: No data found for {symbol.replace('X:', '')}")
return results
return await amake_requests(urls, callback, **kwargs)
@staticmethod
def transform_data(
query: PolygonCryptoHistoricalQueryParams, data: dict, **kwargs: Any
query: PolygonCryptoHistoricalQueryParams, data: List[Dict], **kwargs: Any
) -> List[PolygonCryptoHistoricalData]:
"""Transform the data."""
if not data:
raise EmptyDataError()
return [PolygonCryptoHistoricalData.model_validate(d) for d in data]

View File

@@ -1,5 +1,8 @@
"""Polygon Currency Historical Price Model."""
# pylint: disable=unused-argument,protected-access,line-too-long
import warnings
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
@@ -10,8 +13,21 @@ from openbb_core.provider.standard_models.currency_historical import (
CurrencyHistoricalQueryParams,
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_polygon.utils.helpers import get_data_many
from pydantic import Field, PositiveInt
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import (
ClientResponse,
ClientSession,
amake_requests,
)
from pydantic import (
Field,
PositiveInt,
PrivateAttr,
model_validator,
)
from pytz import timezone
_warn = warnings.warn
class PolygonCurrencyHistoricalQueryParams(CurrencyHistoricalQueryParams):
@@ -20,11 +36,8 @@ class PolygonCurrencyHistoricalQueryParams(CurrencyHistoricalQueryParams):
Source: https://polygon.io/docs/forex/get_v2_aggs_ticker__forexticker__range__multiplier___timespan___from___to
"""
multiplier: PositiveInt = Field(
default=1, description="Multiplier of the timespan."
)
timespan: Literal["minute", "hour", "day", "week", "month", "quarter", "year"] = (
Field(default="day", description="Timespan of the data.")
interval: str = Field(
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
)
sort: Literal["asc", "desc"] = Field(
default="desc", description="Sort order of the data."
@@ -32,7 +45,28 @@ class PolygonCurrencyHistoricalQueryParams(CurrencyHistoricalQueryParams):
limit: PositiveInt = Field(
default=49999, description=QUERY_DESCRIPTIONS.get("limit", "")
)
adjusted: bool = Field(default=True, description="Whether the data is adjusted.")
_multiplier: PositiveInt = PrivateAttr(default=None)
_timespan: str = PrivateAttr(default=None)
@model_validator(mode="after")
@classmethod
def get_api_interval_params(cls, values: "PolygonCurrencyHistoricalQueryParams"):
"""Get the multiplier and timespan parameters for the Polygon API."""
intervals = {
"s": "second",
"m": "minute",
"h": "hour",
"d": "day",
"W": "week",
"M": "month",
"Q": "quarter",
"Y": "year",
}
values._multiplier = int(values.interval[:-1])
values._timespan = intervals[values.interval[-1]]
return values
class PolygonCurrencyHistoricalData(CurrencyHistoricalData):
@@ -81,28 +115,56 @@ class PolygonCurrencyHistoricalFetcher(
query: PolygonCurrencyHistoricalQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> dict:
) -> List[Dict]:
"""Return the raw data from the polygon endpoint."""
api_key = credentials.get("polygon_api_key") if credentials else ""
request_url = (
f"https://api.polygon.io/v2/aggs/ticker/"
f"C:{query.symbol}/range/{query.multiplier}/{query.timespan}/"
f"{query.start_date}/{query.end_date}?adjusted={query.adjusted}"
f"&sort={query.sort}&limit={query.limit}&apiKey={api_key}"
)
data = await get_data_many(request_url, "results", **kwargs)
urls = [
(
"https://api.polygon.io/v2/aggs/ticker/"
f"C:{symbol.upper()}/range/{query._multiplier}/{query._timespan}/"
f"{query.start_date}/{query.end_date}?"
f"&sort={query.sort}&limit={query.limit}&apiKey={api_key}"
)
for symbol in query.symbol.split(",")
]
for d in data:
d["t"] = datetime.fromtimestamp(d["t"] / 1000)
if query.timespan not in ["minute", "hour"]:
d["t"] = d["t"].date()
async def callback(
response: ClientResponse, session: ClientSession
) -> List[Dict]:
data = await response.json()
return data
symbol = response.url.parts[4]
next_url = data.get("next_url", None)
results: list = data.get("results", [])
while next_url:
url = f"{next_url}&apiKey={api_key}"
data = await session.get_json(url)
results.extend(data.get("results", []))
next_url = data.get("next_url", None)
for r in results:
r["t"] = datetime.fromtimestamp(r["t"] / 1000, tz=timezone("UTC"))
if query._timespan not in ["second", "minute", "hour"]:
r["t"] = r["t"].date()
else:
r["t"] = r["t"].strftime("%Y-%m-%dT%H:%M:%S%z")
if "," in query.symbol:
r["symbol"] = symbol
if results == []:
_warn(f"Symbol Error: No data found for {symbol.replace('C:', '')}")
return results
return await amake_requests(urls, callback, **kwargs)
@staticmethod
def transform_data(
query: PolygonCurrencyHistoricalQueryParams, data: dict, **kwargs: Any
query: PolygonCurrencyHistoricalQueryParams, data: List[Dict], **kwargs: Any
) -> List[PolygonCurrencyHistoricalData]:
"""Return the transformed data."""
if not data:
raise EmptyDataError()
return [PolygonCurrencyHistoricalData.model_validate(d) for d in data]

View File

@@ -1,5 +1,8 @@
"""Polygon Equity Historical Price Model."""
# pylint: disable=unused-argument
import warnings
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
@@ -10,6 +13,7 @@ from openbb_core.provider.standard_models.equity_historical import (
EquityHistoricalQueryParams,
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import (
ClientResponse,
ClientSession,
@@ -21,6 +25,9 @@ from pydantic import (
PrivateAttr,
model_validator,
)
from pytz import timezone
_warn = warnings.warn
class PolygonEquityHistoricalQueryParams(EquityHistoricalQueryParams):
@@ -143,12 +150,19 @@ class PolygonEquityHistoricalFetcher(
next_url = data.get("next_url", None)
for r in results:
r["t"] = datetime.fromtimestamp(r["t"] / 1000)
r["t"] = datetime.fromtimestamp(
r["t"] / 1000, tz=timezone("America/New_York")
)
if query._timespan not in ["second", "minute", "hour"]:
r["t"] = r["t"].date()
else:
r["t"] = r["t"].strftime("%Y-%m-%dT%H:%M:%S")
if "," in query.symbol:
r["symbol"] = symbol
if results == []:
_warn(f"Symbol Error: No data found for {symbol}")
return results
return await amake_requests(urls, callback, **kwargs)
@@ -160,4 +174,6 @@ class PolygonEquityHistoricalFetcher(
**kwargs: Any,
) -> List[PolygonEquityHistoricalData]:
"""Transform the data from the Polygon endpoint."""
if not data:
raise EmptyDataError()
return [PolygonEquityHistoricalData.model_validate(d) for d in data]

View File

@@ -1,5 +1,8 @@
"""Polygon Index Historical Model."""
# pylint: disable=unused-argument,protected-access,line-too-long
import warnings
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
@@ -9,8 +12,22 @@ from openbb_core.provider.standard_models.index_historical import (
IndexHistoricalData,
IndexHistoricalQueryParams,
)
from openbb_polygon.utils.helpers import get_data_many
from pydantic import Field, PositiveInt
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import (
ClientResponse,
ClientSession,
amake_requests,
)
from pydantic import (
Field,
PositiveInt,
PrivateAttr,
model_validator,
)
from pytz import timezone
_warn = warnings.warn
class PolygonIndexHistoricalQueryParams(IndexHistoricalQueryParams):
@@ -19,13 +36,37 @@ class PolygonIndexHistoricalQueryParams(IndexHistoricalQueryParams):
Source: https://polygon.io/docs/indices/getting-started
"""
timespan: Literal["minute", "hour", "day", "week", "month", "quarter", "year"] = (
Field(default="day", description="Timespan of the data.")
interval: str = Field(
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
)
adjusted: bool = Field(default=True, description="Whether the data is adjusted.")
multiplier: PositiveInt = Field(
default=1, description="Multiplier of the timespan."
sort: Literal["asc", "desc"] = Field(
default="desc", description="Sort order of the data."
)
limit: PositiveInt = Field(
default=49999, description=QUERY_DESCRIPTIONS.get("limit", "")
)
_multiplier: PositiveInt = PrivateAttr(default=None)
_timespan: str = PrivateAttr(default=None)
@model_validator(mode="after")
@classmethod
def get_api_interval_params(cls, values: "PolygonIndexHistoricalQueryParams"):
"""Get the multiplier and timespan parameters for the Polygon API."""
intervals = {
"s": "second",
"m": "minute",
"h": "hour",
"d": "day",
"W": "week",
"M": "month",
"Q": "quarter",
"Y": "year",
}
values._multiplier = int(values.interval[:-1])
values._timespan = intervals[values.interval[-1]]
return values
class PolygonIndexHistoricalData(IndexHistoricalData):
@@ -74,30 +115,60 @@ class PolygonIndexHistoricalFetcher(
query: PolygonIndexHistoricalQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> dict:
) -> List[Dict]:
"""Extract raw data from the Polygon endpoint."""
api_key = credentials.get("polygon_api_key") if credentials else ""
request_url = (
f"https://api.polygon.io/v2/aggs/ticker/"
f"I:{query.symbol}/range/{query.multiplier}/{query.timespan}/"
f"{query.start_date}/{query.end_date}?adjusted={query.adjusted}"
f"&sort={query.sort}&limit={query.limit}&apiKey={api_key}"
)
data = await get_data_many(request_url, "results", **kwargs)
urls = [
(
"https://api.polygon.io/v2/aggs/ticker/"
f"I:{symbol.upper()}/range/{query._multiplier}/{query._timespan}/"
f"{query.start_date}/{query.end_date}?"
f"&sort={query.sort}&limit={query.limit}&apiKey={api_key}"
)
for symbol in query.symbol.split(",")
]
for d in data:
d["t"] = datetime.fromtimestamp(d["t"] / 1000)
if query.timespan not in ["minute", "hour"]:
d["t"] = d["t"].date()
async def callback(
response: ClientResponse, session: ClientSession
) -> List[Dict]:
data = await response.json()
return data
symbol = response.url.parts[4]
next_url = data.get("next_url", None)
results: list = data.get("results", [])
while next_url:
url = f"{next_url}&apiKey={api_key}"
data = await session.get_json(url)
results.extend(data.get("results", []))
next_url = data.get("next_url", None)
for r in results:
r["t"] = datetime.fromtimestamp(
r["t"] / 1000, tz=timezone("America/New_York")
)
if query._timespan not in ["second", "minute", "hour"]:
r["t"] = r["t"].date()
else:
r["t"] = r["t"].strftime("%Y-%m-%dT%H:%M:%S")
if "," in query.symbol:
r["symbol"] = symbol
if results == []:
_warn(f"Symbol Error: No data found for {symbol.replace('I:', '')}")
return results
return await amake_requests(urls, callback, **kwargs)
@staticmethod
def transform_data(
query: PolygonIndexHistoricalQueryParams,
data: dict,
data: List[Dict],
**kwargs: Any,
) -> List[PolygonIndexHistoricalData]:
"""Transform the data."""
if not data:
raise EmptyDataError()
return [PolygonIndexHistoricalData.model_validate(d) for d in data]

View File

@@ -1,15 +1,17 @@
"""Polygon Market Snapshots Model."""
# pylint: disable=unused-argument
from datetime import datetime
from typing import Any, Dict, List, Optional
import pandas as pd
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.market_snapshots import (
MarketSnapshotsData,
MarketSnapshotsQueryParams,
)
from openbb_polygon.utils.helpers import get_data_many
from pandas import to_datetime
from pydantic import Field, field_validator
@@ -23,23 +25,23 @@ class PolygonMarketSnapshotsQueryParams(MarketSnapshotsQueryParams):
class PolygonMarketSnapshotsData(MarketSnapshotsData):
"""Polygon Market Snapshots Data."""
vwap: float = Field(
vwap: Optional[float] = Field(
description="The volume weighted average price of the stock on the current trading day.",
default=None,
)
prev_open: float = Field(
prev_open: Optional[float] = Field(
description="The previous trading session opening price.", default=None
)
prev_high: float = Field(
prev_high: Optional[float] = Field(
description="The previous trading session high price.", default=None
)
prev_low: float = Field(
prev_low: Optional[float] = Field(
description="The previous trading session low price.", default=None
)
prev_volume: float = Field(
prev_volume: Optional[float] = Field(
description="The previous trading session volume.", default=None
)
prev_vwap: float = Field(
prev_vwap: Optional[float] = Field(
description="The previous trading session VWAP.", default=None
)
last_updated: Optional[datetime] = Field(
@@ -79,9 +81,7 @@ class PolygonMarketSnapshotsData(MarketSnapshotsData):
def date_validate(cls, v):
"""Return formatted datetime."""
return (
pd.to_datetime(v, unit="ns", origin="unix", utc=True).tz_convert(
"US/Eastern"
)
to_datetime(v, unit="ns", origin="unix", utc=True).tz_convert("US/Eastern")
if v
else None
)
@@ -120,7 +120,7 @@ class PolygonMarketSnapshotsFetcher(
**kwargs: Any,
) -> List[PolygonMarketSnapshotsData]:
"""Return the transformed data."""
processed_data = []
processed_data: List[PolygonMarketSnapshotsData] = []
# Process and flatten the response from a nested dictionary
for item in data:
@@ -129,7 +129,7 @@ class PolygonMarketSnapshotsFetcher(
day_data = item.get("day", {})
prev_day = item.get("prevDay", {})
market_data: Dict[str, List[Any]] = {
market_data: Dict = {
"symbol": item["ticker"],
"bid": last_quote.get("p"),
"ask": last_quote.get("P"),
@@ -158,6 +158,8 @@ class PolygonMarketSnapshotsFetcher(
"last_trade_timestamp": last_trade.get("t"),
}
processed_data.append(market_data)
processed_data.append(
PolygonMarketSnapshotsData.model_validate(market_data)
)
return [PolygonMarketSnapshotsData.model_validate(d) for d in processed_data]
return processed_data

View File

@@ -3,27 +3,27 @@ interactions:
body: null
headers:
Accept:
- '*/*'
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.polygon.io/v2/aggs/ticker/X:BTCUSD/range/1/day/2023-01-01/2023-01-10?adjusted=True&apiKey=MOCK_API_KEY&limit=49999&sort=desc
uri: https://api.polygon.io/v2/aggs/ticker/X:BTCUSD/range/1/day/2023-01-01/2023-01-10?apiKey=MOCK_API_KEY&limit=49999&sort=desc
response:
body:
string: !!binary |
H4sIAAAAAAAA/0zUz2rDRhAG8HfZsxjm/+zo2PTWQw9toVBKcWyFpA0JseWUEvLuRZZ2Zd/WCP/8
zXzarzK/HP+ZzmUsv48//Prw2y8/lqF8XKfzfw/v17e5jIRDOU+X6+t8ufvmcPr7epmnUxnn83Xq
T5Txj6/yWUaurAzImRHhpuJD+fy3jBRCAuYhQ3lfjhQBqUM5LgdVAfWhPC8HQx/K6+0ZNaChLLKH
CNaKy2cob2U0D0T6Hm6qhEZAGAWyiYs1lFUhDKmhSpt44+sm6vKbq4gMGZ1kZt1JNyL1jSQiqeBJ
5MqhrLmZnkEQWHU1PdcQq8oG1FSqtKqehKB7UBLHXeVMM9tUN08wCU3jYOK0roqBGlNTjZrqqQwS
q+qZ2FQUsNpV1PQ7lVNZN3VZqoNxKpKne42mVkXAXa1sEF2tgG2n3CbsHgiVm8rpwruqYUFtqevo
vGZYKkVwR9mBk9uAqybIhi5/wHOLWmvd0DAE7GvlGn7XJKlhhi1qiiMQuzibEHrtqhhkDd9U9+xR
qyoob1ERm+pqUHc1Eu/KZMS1ZlOJUYGzppkuzVlJrwgoqZ2sPejCa9tppTZdywTfxQ3ccoYztpWS
ESfwAqJkqkhHk8EIO0rWSYelbyuJ0khlsH2hTnFXI7HK2VISCyIImYkIeXI3TRNMe1ATbibRraPP
6zx1M3Up8QaaEN+/LSym338O5TIf5uuljOXnn8pyTX1cp8v818upjIUmVpk4H0+ajwc6nJCe9OhP
xyO5PJGXoRzbjff9fwAAAP//RZi8tykFAAA=
H4sIAAAAAAAA/0zUTavbRhQG4P8ya3E43x9aNt110UVbKJRSnGuHpA0JseWUcrn/vcjSjOzdGOHH
7zmv5rUtn17+uVzb3H6ff/j13W+//Nim9u1+uf737uv9y9JmwqldL7f75+X29M3p/Pf9tlzObV6u
98t4os1/vLbvbeZkZUCuigg3FZ/a93/bTCEkYB4yta/rkSKgdGov60FVQH1qH9eDoU/t8+MZNaCp
rbKHCGbi+pnalzabByK9TQ9VQiMgjALZxMU6yqoQhtRRpV188LmLuv7mJiJDxSCZWQ/SjUh9J4lI
EryIXDmUtXbTKwgCUzfTawuxqWxAXaWkTfUiBD2CkjgeKleZ2a66eYFJaBkHE5cNVQzUmLpq1FUv
ZZDYVK/CrqKA5VBRy59ULmXd1XWpDsalSF7uGV1NRcBDTTaIoSZg3yn3CbsHQnJXuVz4UDUsqC91
G51nhZVSBA+UHbi4Dzi1QHZ0/QNee9TM3NEwBBxr5Qx/apJkmGGPWuIIxC7OJoSeQxWDyvBdda8R
NVVBeY+K2FVXgzzUKHwqkxFnVleJUYEry0zX5mykJwJK6SBzBF157TtN6tO1KvBD3ME9ZzhjXykZ
cQGvIEqVigy0GIxwoGSDdFj7tpEonVQGOxbqFE81EkuunpJYEEHITETIi4dpWmA6gppwN4keHf24
zVN3U9cS76AJ8fPbwmL69ufUbstpud/a3H7+qa3X1Lf75bb89enc5san0+n9+w8ZLyImcjaWiFN+
MCY6n9nb1F76jff2fwAAAP//OenW5ykFAAA=
headers:
Connection:
- keep-alive
@@ -34,7 +34,7 @@ interactions:
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:08:18 GMT
- Fri, 02 Feb 2024 00:32:56 GMT
Server:
- nginx/1.19.2
Strict-Transport-Security:
@@ -42,7 +42,7 @@ interactions:
Vary:
- Accept-Encoding
X-Request-Id:
- 1e243e29bd49ba1ad01f4c6fcc163f16
- 2aaabbf87c33533d52377a8f5211dd26
status:
code: 200
message: OK

View File

@@ -3,35 +3,35 @@ interactions:
body: null
headers:
Accept:
- '*/*'
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.polygon.io/v2/aggs/ticker/C:EURUSD/range/1/day/2023-01-01/2023-01-10?adjusted=True&apiKey=MOCK_API_KEY&limit=49999&sort=desc
uri: https://api.polygon.io/v2/aggs/ticker/C:EURUSD/range/1/day/2023-01-01/2023-01-10?apiKey=MOCK_API_KEY&limit=49999&sort=desc
response:
body:
string: !!binary |
H4sIAAAAAAAA/2yUTWsbMRCG/8ucRZFG8yHtNe2ph0JLTqWU9e6apg0J8e6mlOD/XrTW1yE+CSE/
j0fvK7/B9jD9WS4wwN3w6f7r/bePYOBlXy7/7p73pw0GZw1clnV/3NZuZ5x/7+u2zDBsl32pJ2D4
/gavMKBFJjbw+hcG98GqJwPPeYlsYMrr4A38uq05egOPt7UzkDyi3tsQbPoYeCrYq8kORtXmsFIc
IkjNkXzZIa4ohKsCEalXJGpWkHpXBekrmU82FL4ISuGLoFaBt1oVzottikQtM3R03/DU0Tt4ZacD
GW0pSvfr691oSFsZzuwKnNHGRnfv0ClgLHiM4rG/nIQtDmeZfHNozVgsS3EwuibxVcLOhyoJKn3I
B7dIMFKkJomhDkLpxqYibA6uPeJ2T6jR9ikf2OLwIr4ViUMLWqWW9ehzdgTX5mhjaDZkxUHNCiVH
zSBaq6o2VWYq21Sr2ppKtaooTruwD2gWuK5JsYYtgWrYGttba09NNLQB2LsubIfXHwbWbdz2FQb4
8hnSM3/Zl3X7+TDDALwsciLlMdDZySjzfD7504j+vMR4nhgMTOUf4/o/AAD//4GaYFNpBAAA
H4sIAAAAAAAA/2yUQWsbPRCG/8ucxYdmpJmR9pqvpx4KLTmVUjbrDU0bEuLdTSnB/73IkTQ61Cch
5Ofx6H3lN9gfll/rGSa4mT7cfr798j84eDnW85+b5+Nphwm9g/O6HY/7NuzMp5/Htq8nmPbzsfYT
MH19g1eYyBNHdvD6Gyb8z2uIDp7rktjBUtcpOPjxvuYcHDy+r9FB8YiG4FPy5ePgqWEvrjqYVM3h
pTlEKJqj+KpDsCmEu4KI4qgo1KqIGrALylcqP/rU+CIkjS9C2gXBa1dgEG+KQm0zDPRg+DjQB3hn
lwMV7WOW4df3u9FUtiqcGRucyWej4z/oMVFueMoSaLycgm0O9ByDObRnLJ6lOZjQJKFLGEPqkqQy
hnzlNgnlmKNJcuqDxHJjSxOag3uP2O6JNPsx5Su2OYJIsCJxsqBVelmvfa6OhDaHjaHVUBVXalVo
xGgG0V5V9aUyS9uOvarW1NirSoI6hH2FVgEOTco9bEmxh63Z3po9NdFkA3DAIWykyzcH2z7vxwYT
fPoI5Zm/HOu2f384wQSCeZ7lXu4wzUsOeLeEOablfuE087oSOFjaP8blbwAAAP//JClhEWkEAAA=
headers:
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Length:
- '453'
- '455'
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:08:19 GMT
- Fri, 02 Feb 2024 00:32:57 GMT
Server:
- nginx/1.19.2
Strict-Transport-Security:
@@ -39,7 +39,7 @@ interactions:
Vary:
- Accept-Encoding
X-Request-Id:
- 5ee6b475a84f16a6ddfb3ba23fe99fc5
- 619aa6f6b18ac931bc3a48cfc58a5ee2
status:
code: 200
message: OK

View File

@@ -9,51 +9,51 @@ interactions:
Connection:
- keep-alive
method: GET
uri: https://api.polygon.io/v2/aggs/ticker/I:NDX/range/1/day/2023-01-01/2023-05-10?adjusted=True&apiKey=MOCK_API_KEY&limit=10000&sort=asc
uri: https://api.polygon.io/v2/aggs/ticker/I:NDX/range/1/day/2023-01-01/2023-05-10?apiKey=MOCK_API_KEY&limit=49999&sort=desc
response:
body:
string: !!binary |
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
JF+aP5nzz7en28v6FfyP/wUAAP//g1i9uSgXAAA=
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
headers:
Connection:
- keep-alive
@@ -62,7 +62,7 @@ interactions:
Content-Type:
- application/json
Date:
- Mon, 05 Feb 2024 12:25:28 GMT
- Fri, 02 Feb 2024 00:35:30 GMT
Server:
- nginx/1.19.2
Strict-Transport-Security:
@@ -72,7 +72,7 @@ interactions:
Vary:
- Accept-Encoding
X-Request-Id:
- cd19fc998cee9a09eb5d7417c90d450b
- 1ab70490ebcfe654deb56d619924d97d
status:
code: 200
message: OK

View File

@@ -25,10 +25,6 @@ class SecEquitySearchQueryParams(EquitySearchQueryParams):
default=False,
description="Whether to direct the search to the list of mutual funds and ETFs.",
)
use_cache: bool = Field(
default=True,
description="Whether to use the cache or not. Company names, tickers, and CIKs are cached for seven days.",
)
class SecEquitySearchData(EquitySearchData):

View File

@@ -54,8 +54,8 @@ class YFinanceHistoricalDividendsFetcher(
raise ValueError(f"No dividend data found for {symbol}")
except Exception as e:
raise RuntimeError(f"Error getting data for {symbol}: {e}") from e
ticker.index.name = "date"
ticker.name = "dividend" # type: ignore
ticker.index.name = "ex_dividend_date"
ticker.name = "amount" # type: ignore
if query.start_date is not None:
ticker = ticker[ticker.index.astype(str) >= query.start_date.strftime("%Y-%m-%d")] # type: ignore
if query.end_date is not None:

View File

@@ -49,26 +49,40 @@ class HedgeController(BaseController):
self.underlying_asset_position: str = ""
self.chain = get_option_chain(ticker, expiration)
self.calls = list(
zip(
self.chain.calls["strike"].tolist(),
self.chain.calls["impliedVolatility"].tolist(),
self.chain.calls["lastPrice"].tolist(),
self.chain.calls["currency"].tolist(),
self.calls = (
list(
zip(
self.chain.calls["strike"].tolist(),
self.chain.calls["impliedVolatility"].tolist(),
self.chain.calls["lastPrice"].tolist(),
self.chain.calls["currency"].tolist(),
)
)
if "calls" in self.chain.columns
else []
)
self.puts = list(
zip(
self.chain.puts["strike"].tolist(),
self.chain.puts["impliedVolatility"].tolist(),
self.chain.puts["lastPrice"].tolist(),
self.chain.calls["currency"].tolist(),
self.puts = (
list(
zip(
self.chain.puts["strike"].tolist(),
self.chain.puts["impliedVolatility"].tolist(),
self.chain.puts["lastPrice"].tolist(),
self.chain.calls["currency"].tolist(),
)
)
if "puts" in self.chain.columns
else []
)
start = self.calls[0] if self.calls else 0
end = self.calls[-1] if self.calls else 0
start = int(start[0]) if isinstance(start, list) and start else 0
end = int(end[0]) if isinstance(end, list) and end else 0
self.PICK_CHOICES = [
f"{strike} {position} {side}"
for strike in range(int(self.calls[0][0]), int(self.calls[-1][0]), 5)
for strike in range(start, end, 5)
for position in ["Long", "Short"]
for side in ["Call", "Put"]
]
@@ -76,7 +90,11 @@ class HedgeController(BaseController):
self.ticker = ticker
self.current_price: float = get_price(ticker)
self.expiration = expiration
self.implied_volatility = self.chain.calls["impliedVolatility"]
self.implied_volatility = (
self.chain.calls.get("impliedVolatility", 0.0)
if "calls" in self.chain.columns
else 0.0
)
self.options: Dict = {"Portfolio": {}, "Option A": {}, "Option B": {}}
self.underlying = 0.0
self.side: str = ""

View File

@@ -0,0 +1,36 @@
---
title: Data connectors
sidebar_position: 4
description: Access your data connectors from OpenBB Terminal Pro inside OpenBB Add-in for Excel.
keywords:
- Microsoft Excel
- Add-in
- Advanced
- Data connectors
- BYOD
- Bring your own data
---
<!-- markdownlint-disable MD033 -->
import HeadTitle from '@site/src/components/General/HeadTitle.tsx';
<HeadTitle title="Data connectors | OpenBB Add-in for Excel Docs" />
OpenBB Add-In for Excel allows you to access your [data connectors](https://docs.openbb.co/pro/main-menu/data-connectors) from OpenBB Terminal Pro inside Microsoft Excel.
To access data from a [single widget](https://docs.openbb.co/pro/main-menu/data-connectors/single-widget) use:
```excel
=OBB.BYOD("widget_name")
```
To access data from your own [backend](https://docs.openbb.co/pro/main-menu/data-connectors/integrate-your-own-backend) use:
```excel
=OBB.BYOD("widget_name", "backend_name")
```
#### Additional notes
- Make sure your backend's CORS settings allow requests coming from <https://excel.openbb.co>.
- Requests via HTTP will be blocked by Excel. So if you are using the Add-in for Excel on Mac or Office on the web with Safari browser you need to run your backend via HTTPS.

View File

@@ -1,5 +1,27 @@
{
"functions": [
{
"description": "Bring Your Own Data features from OpenBB Terminal Pro.",
"helpUrl": "https://docs.openbb.co/excel/data-conectors",
"id": "BYOD",
"name": "BYOD",
"parameters": [
{
"description": "Name of the widget to get data for.",
"name": "widget",
"type": "string"
},
{
"description": "Name of the backend to use, required for widgets in custom backends.",
"name": "backend",
"optional": true,
"type": "string"
}
],
"result": {
"dimensionality": "matrix"
}
},
{
"description": "Cryptocurrency Historical Price. Cryptocurrency historical price data.",
"helpUrl": "https://docs.openbb.co/excel/reference/crypto/price/historical",
@@ -1316,6 +1338,18 @@
"name": "symbol",
"type": "string"
},
{
"description": "Start date of the data, in YYYY-MM-DD format.",
"name": "start_date",
"optional": true,
"type": "string"
},
{
"description": "End date of the data, in YYYY-MM-DD format.",
"name": "end_date",
"optional": true,
"type": "string"
},
{
"description": "Options: fmp, intrinio, defaults to fmp.",
"name": "provider",
@@ -1324,7 +1358,7 @@
},
{
"description": "The number of data entries to return. (provider: intrinio)",
"name": "page_size",
"name": "limit",
"optional": true,
"type": "number"
}
@@ -1385,6 +1419,12 @@
"optional": true,
"type": "number"
},
{
"description": "Sort the data in ascending or descending order. (provider: fmp)",
"name": "sort",
"optional": true,
"type": "string"
},
{
"description": "Start date of the data, in YYYY-MM-DD format. (provider: intrinio)",
"name": "start_date",
@@ -1745,6 +1785,18 @@
"name": "symbol",
"type": "string"
},
{
"description": "Start date of the data, in YYYY-MM-DD format.",
"name": "start_date",
"optional": true,
"type": "string"
},
{
"description": "End date of the data, in YYYY-MM-DD format.",
"name": "end_date",
"optional": true,
"type": "string"
},
{
"description": "Options: fmp, defaults to fmp.",
"name": "provider",
@@ -2040,6 +2092,12 @@
"optional": true,
"type": "string"
},
{
"description": "The number of data entries to return. Default is 252, the number of trading days in a year.",
"name": "limit",
"optional": true,
"type": "number"
},
{
"description": "Options: tiingo, defaults to tiingo.",
"name": "provider",
@@ -2227,6 +2285,12 @@
"name": "provider",
"optional": true,
"type": "string"
},
{
"description": "Include current quarter data. (provider: fmp)",
"name": "include_current_quarter",
"optional": true,
"type": "boolean"
}
],
"result": {
@@ -2430,7 +2494,7 @@
"name": "EQUITY.PRICE.QUOTE",
"parameters": [
{
"description": "Symbol to get data for. In this case, the comma separated list of symbols.",
"description": "Symbol to get data for. This endpoint will accept multiple symbols separated by commas.",
"name": "symbol",
"type": "string"
},
@@ -3723,7 +3787,7 @@
"type": "number"
},
{
"description": "Options: benzinga, fmp, intrinio, polygon, tiingo, defaults to benzinga.",
"description": "Options: benzinga, fmp, intrinio, polygon, tiingo, ultima, defaults to benzinga.",
"name": "provider",
"optional": true,
"type": "string"
@@ -3835,6 +3899,34 @@
"dimensionality": "matrix"
}
},
{
"description": "Sector News. Get news for one or more sectors.",
"helpUrl": "https://docs.openbb.co/excel/reference/news/sector",
"id": "NEWS.SECTOR",
"name": "NEWS.SECTOR",
"parameters": [
{
"description": "A coma separated list of sectors.",
"name": "sectors",
"type": "string"
},
{
"description": "The number of data entries to return. Here it is the no. of articles to return.",
"name": "limit",
"optional": true,
"type": "number"
},
{
"description": "Options: ultima, defaults to ultima.",
"name": "provider",
"optional": true,
"type": "string"
}
],
"result": {
"dimensionality": "matrix"
}
},
{
"description": "World News. Global news data.",
"helpUrl": "https://docs.openbb.co/excel/reference/news/world",

View File

@@ -14,7 +14,7 @@ import HeadTitle from '@site/src/components/General/HeadTitle.tsx';
<HeadTitle title="Basics | OpenBB Add-in for Excel Docs" />
The OpenBB Excel Add-in provides direct access to the OpenBB platform, where each function implements the following pattern:
The OpenBB Add-in for Excel provides direct access to the OpenBB platform, where each function implements the following pattern:
- `OBB.[MENU].[SUB_MENU].[COMMAND]`

View File

@@ -12,7 +12,7 @@ import HeadTitle from '@site/src/components/General/HeadTitle.tsx';
<HeadTitle title="Installation | OpenBB Add-in for Excel Docs" />
## Requirements
- The OpenBB Excel Add-in is available on the following platforms: Windows (Microsoft 365), Mac (Microsoft 365), Excel on the web.
- The OpenBB Add-in for Excel is available on the following platforms: Windows (Microsoft 365), Mac (Microsoft 365), Excel on the web.
- Access to the OpenBB Terminal Pro. If you don't have access you can sign up for the waitlist [here](https://my.openbb.co/app/pro).
## Installation
@@ -27,7 +27,7 @@ import HeadTitle from '@site/src/components/General/HeadTitle.tsx';
/>
</div>
The OpenBB Excel Add-in is available on the [Microsoft AppSource](https://appsource.microsoft.com/pt-pt/product/office/wa200006381?tab=overview). It can be installed by an administrator or by individual users.
The OpenBB Add-in for Excel is available on the [Microsoft AppSource](https://appsource.microsoft.com/pt-pt/product/office/wa200006381?tab=overview). It can be installed by an administrator or by individual users.
### Individual user

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
---
title: Templates
sidebar_position: 4
description: OpenBB Excel Add-In offered by OpenBB. These templates are designed to help you perform a variety of tasks, from equity analysis to economic analysis. They can be used as-is or customized to suit your needs.
description: OpenBB Add-In for Excel offered by OpenBB. These templates are designed to help you perform a variety of tasks, from equity analysis to economic analysis. They can be used as-is or customized to suit your needs.
keywords:
- Microsoft Excel
- Add-in
@@ -14,7 +14,7 @@ import HeadTitle from '@site/src/components/General/HeadTitle.tsx';
<HeadTitle title="Templates | OpenBB Add-in for Excel Docs" />
OpenBB Excel Add-In offered by OpenBB. These templates are designed to help you perform a variety of tasks, from equity analysis to economic analysis. They can be used as-is or customized to suit your needs.
OpenBB Add-In for Excel offered by OpenBB. These templates are designed to help you perform a variety of tasks, from equity analysis to economic analysis. They can be used as-is or customized to suit your needs.
These can be downloaded in the [OpenBB Hub](https://my.openbb.co).

View File

@@ -55,4 +55,3 @@ The language-agnostic API approach offers flexibility by allowing you to use the
### Quick Start with Python and FastAPI
Our team has created several examples that you can use to quickly get started with Python and FastAPI. For more information, check [this open source repository](https://github.com/OpenBB-finance/backend-for-terminal-pro/tree/main) for examples.
e

View File

@@ -33,6 +33,9 @@ class CommandLib:
# These examples will be generated in the core, but we keep them here meanwhile
EXAMPLE_PARAMS: Dict[str, Dict] = {
"/byod": {
"widget": '"widget_name"',
},
"crypto": {
"symbol": '"BTCUSD"',
"start_date": '"2023-01-01"',
@@ -200,7 +203,12 @@ class CommandLib:
def _get_data(self, cmd: str) -> dict:
"""Get the data of the command from the openapi."""
model = self.openapi["paths"][f"/api/v1{cmd}"]["get"]["model"]
model = (
self.openapi.get("paths", {})
.get(f"/api/v1{cmd}", {})
.get("get", {})
.get("model")
)
if model:
schema = self.openapi["components"]["schemas"][model]["properties"]
data = {}
@@ -324,53 +332,58 @@ class Editor:
return description
def get_syntax() -> str:
sig = cmd_info["signature"]
syntax = "## Syntax\n\n"
syntax += f"```{self.interface} wordwrap\n"
syntax += f"{sig}\n"
syntax += "```\n\n"
return syntax
if sig := cmd_info["signature"]:
syntax = "## Syntax\n\n"
syntax += f"```{self.interface} wordwrap\n"
syntax += f"{sig}\n"
syntax += "```\n\n"
return syntax
return ""
def get_parameters() -> str:
parameters = "## Parameters\n\n"
parameters += "| Name | Type | Description | Required |\n"
parameters += "| ---- | ---- | ----------- | -------- |\n"
for field_name, field_info in cmd_info["parameters"].items():
name = field_name
type_ = field_info["type"]
description = field_info["description"]
required = field_info["required"]
required_str = str(required).title()
if required:
parameters += f"| **{name}** | **{type_}** | **{description}** | **{required_str}** |\n"
else:
parameters += (
f"| {name} | {type_} | {description} | {required_str} |\n"
)
parameters += "\n"
return parameters
if parameter_schema := cmd_info["parameters"]:
parameters = "## Parameters\n\n"
parameters += "| Name | Type | Description | Required |\n"
parameters += "| ---- | ---- | ----------- | -------- |\n"
for field_name, field_info in parameter_schema.items():
name = field_name
type_ = field_info["type"]
description = field_info["description"]
required = field_info["required"]
required_str = str(required).title()
if required:
parameters += f"| **{name}** | **{type_}** | **{description}** | **{required_str}** |\n"
else:
parameters += (
f"| {name} | {type_} | {description} | {required_str} |\n"
)
parameters += "\n"
return parameters
return ""
def get_return_data() -> str:
data = "## Return Data\n\n"
data += "| Name | Description |\n"
data += "| ---- | ----------- |\n"
for field_name, field_info in cmd_info["data"].items():
name = field_name
description = field_info["description"]
data += f"| {name} | {description} |\n"
return data
if data_schema := cmd_info["data"]:
data = "## Return Data\n\n"
data += "| Name | Description |\n"
data += "| ---- | ----------- |\n"
for field_name, field_info in data_schema.items():
name = field_name
description = field_info["description"]
data += f"| {name} | {description} |\n"
return data
return ""
def get_examples() -> str:
examples = "### Example\n\n"
if cmd_examples := cmd_info["examples"]:
examples = "### Example\n\n"
for _, v in cmd_examples.items():
# examples += f"### {k}\n\n"
examples += f"```{self.interface} wordwrap\n"
examples += f"{v}\n"
examples += "```\n\n"
for _, v in cmd_info["examples"].items():
# examples += f"### {k}\n\n"
examples += f"```{self.interface} wordwrap\n"
examples += f"{v}\n"
examples += "```\n\n"
return examples
return examples
return ""
content = get_header()
content += get_head_title()

View File

@@ -53,10 +53,10 @@ export default function Home(): JSX.Element {
className="!bg-grey-900 bg-[url('/img/excelbg.png')] bg-no-repeat shadow-sm group !no-underline text-white dark:hover:border-white hover:border-grey-600/80 relative w-full h-[238px] max-w-full p-8 rounded flex flex-col items-start justify-start border border-grey-300 hover:!text-white"
>
<h3 className="uppercase tracking-widest font-bold mb-0 mt-0 text-white">
OpenBB Excel Add-In
OpenBB Add-In for Excel
</h3>
<p className="text-sm lg:text-base text-grey-300">
The OpenBB Excel Add-In allows access to the same data as the OpenBB Terminal Pro, but through Excel.
The OpenBB Add-In for Excel allows access to the same data as the OpenBB Terminal Pro, but through Excel.
</p>
<p
className={clsx(