From 7c953d5964b1a5b5427dc2b2d188ccaed66acbdb Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Mon, 5 Feb 2024 11:53:17 -0800 Subject: [PATCH] [BugFix] - Adds a timezone to the Unix timestamp conversion to correct localization errors. (#6023) * fix: remove specific imports * make polygon historical tz-aware * undo including that commit * that PR wasn't supposed to be there. * pylint unused argument * fix polygon crypto historical intervals * pylint * new cassette * fix the rest of the polygon historical price query params * black * pylint.. * integration test param * appease daylight savings handling * paginate for crypto currency and index * black * raise EmptyDataError when no results instead of returning an empty list * ruff --------- Co-authored-by: Diogo Sousa --- .../crypto/integration/test_crypto_api.py | 8 +- .../crypto/integration/test_crypto_python.py | 8 +- .../currency/integration/test_currency_api.py | 8 +- .../integration/test_currency_python.py | 8 +- .../index/integration/test_index_api.py | 10 +- .../index/integration/test_index_python.py | 10 +- .../models/crypto_historical.py | 106 ++++++++++++---- .../models/currency_historical.py | 106 ++++++++++++---- .../models/equity_historical.py | 18 ++- .../openbb_polygon/models/index_historical.py | 113 ++++++++++++++---- .../openbb_polygon/models/market_snapshots.py | 30 ++--- ...est_polygon_crypto_historical_fetcher.yaml | 30 ++--- ...t_polygon_currency_historical_fetcher.yaml | 26 ++-- ...test_polygon_index_historical_fetcher.yaml | 88 +++++++------- 14 files changed, 377 insertions(+), 192 deletions(-) diff --git a/openbb_platform/extensions/crypto/integration/test_crypto_api.py b/openbb_platform/extensions/crypto/integration/test_crypto_api.py index 111f9d08a0b..97ff10da864 100644 --- a/openbb_platform/extensions/crypto/integration/test_crypto_api.py +++ b/openbb_platform/extensions/crypto/integration/test_crypto_api.py @@ -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", diff --git a/openbb_platform/extensions/crypto/integration/test_crypto_python.py b/openbb_platform/extensions/crypto/integration/test_crypto_python.py index 5a46fa9e988..e8d3ba097a5 100644 --- a/openbb_platform/extensions/crypto/integration/test_crypto_python.py +++ b/openbb_platform/extensions/crypto/integration/test_crypto_python.py @@ -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", diff --git a/openbb_platform/extensions/currency/integration/test_currency_api.py b/openbb_platform/extensions/currency/integration/test_currency_api.py index 574fd9cac62..d5dea2303af 100644 --- a/openbb_platform/extensions/currency/integration/test_currency_api.py +++ b/openbb_platform/extensions/currency/integration/test_currency_api.py @@ -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", diff --git a/openbb_platform/extensions/currency/integration/test_currency_python.py b/openbb_platform/extensions/currency/integration/test_currency_python.py index 223b0b1ed60..329f017eb2c 100644 --- a/openbb_platform/extensions/currency/integration/test_currency_python.py +++ b/openbb_platform/extensions/currency/integration/test_currency_python.py @@ -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", diff --git a/openbb_platform/extensions/index/integration/test_index_api.py b/openbb_platform/extensions/index/integration/test_index_api.py index 9b811f561c1..dff0265d57a 100644 --- a/openbb_platform/extensions/index/integration/test_index_api.py +++ b/openbb_platform/extensions/index/integration/test_index_api.py @@ -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, } ), ( diff --git a/openbb_platform/extensions/index/integration/test_index_python.py b/openbb_platform/extensions/index/integration/test_index_python.py index 30e4631dbc6..600a1279d7e 100644 --- a/openbb_platform/extensions/index/integration/test_index_python.py +++ b/openbb_platform/extensions/index/integration/test_index_python.py @@ -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, } ), ( diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/crypto_historical.py b/openbb_platform/providers/polygon/openbb_polygon/models/crypto_historical.py index c7ae932f62c..96f2918b4d1 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/crypto_historical.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/crypto_historical.py @@ -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] diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/currency_historical.py b/openbb_platform/providers/polygon/openbb_polygon/models/currency_historical.py index b0b9611e1f2..4b5c0acb436 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/currency_historical.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/currency_historical.py @@ -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] diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/equity_historical.py b/openbb_platform/providers/polygon/openbb_polygon/models/equity_historical.py index 38980590b6f..a74aac6f64f 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/equity_historical.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/equity_historical.py @@ -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] diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/index_historical.py b/openbb_platform/providers/polygon/openbb_polygon/models/index_historical.py index 31a38b5261c..adc57bfa2c6 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/index_historical.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/index_historical.py @@ -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] diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/market_snapshots.py b/openbb_platform/providers/polygon/openbb_polygon/models/market_snapshots.py index 5737b7ab7c8..bba381c00a2 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/market_snapshots.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/market_snapshots.py @@ -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 diff --git a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_crypto_historical_fetcher.yaml b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_crypto_historical_fetcher.yaml index ad6818c557a..ce8f46b4411 100644 --- a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_crypto_historical_fetcher.yaml +++ b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_crypto_historical_fetcher.yaml @@ -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 diff --git a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_currency_historical_fetcher.yaml b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_currency_historical_fetcher.yaml index 1aa8571e38b..a706bc34071 100644 --- a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_currency_historical_fetcher.yaml +++ b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_currency_historical_fetcher.yaml @@ -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 diff --git a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_index_historical_fetcher.yaml b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_index_historical_fetcher.yaml index 84afa7c8c45..5d27a705e3e 100644 --- a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_index_historical_fetcher.yaml +++ b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_index_historical_fetcher.yaml @@ -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