diff options
author | Danglewood <85772166+deeleeramone@users.noreply.github.com> | 2024-02-05 11:53:17 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-05 19:53:17 +0000 |
commit | 7c953d5964b1a5b5427dc2b2d188ccaed66acbdb (patch) | |
tree | 2e82be46fd095cf790ba88d3f541e1a31ac9e524 | |
parent | 16f2bec65f15e38bffcf910d0c9686343a3d6c96 (diff) |
[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 <montezdesousa@gmail.com>
14 files changed, 383 insertions, 198 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) - - for d in data: - d["t"] = datetime.fromtimestamp(d["t"] / 1000) - if query.timespan not in ["minute", "hour"]: - d["t"] = d["t"].date() - - return data + 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(",") + ] + + async def callback( + response: ClientResponse, session: ClientSession + ) -> List[Dict]: + data = await response.json() + + 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) - - for d in data: - d["t"] = datetime.fromtimestamp(d["t"] / 1000) - if query.timespan not in ["minute", "hour"]: - d["t"] = d["t"].date() - - return data + 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(",") + ] + + async def callback( + response: ClientResponse, session: ClientSession + ) -> List[Dict]: + data = await response.json() + + 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 |