mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-02 15:32:04 +00:00
[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>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
),
|
||||
(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
),
|
||||
(
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user