[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:
Danglewood
2024-02-05 11:53:17 -08:00
committed by GitHub
parent 16f2bec65f
commit 7c953d5964
14 changed files with 377 additions and 192 deletions

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

@@ -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

@@ -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