From f041e1dc0ffe2c31cb56ed64cddb4e3757a1e5bb Mon Sep 17 00:00:00 2001 From: mcdayoub <38268139+mcdayoub@users.noreply.github.com> Date: Wed, 27 Apr 2022 17:25:16 -0400 Subject: [PATCH 1/5] aggs endpoints --- polygon/rest/aggs.py | 64 ++++++++++++++++++++++-- polygon/rest/base.py | 60 ++++++++++------------- polygon/rest/models/aggs.py | 97 +++++++++++++++++++++++++++++++++---- tests/mocks.py | 12 +++++ tests/test_aggs.py | 19 +++++++- 5 files changed, 204 insertions(+), 48 deletions(-) diff --git a/polygon/rest/aggs.py b/polygon/rest/aggs.py index ccaa5187..0ddda6bd 100644 --- a/polygon/rest/aggs.py +++ b/polygon/rest/aggs.py @@ -1,6 +1,7 @@ +from email.headerregistry import Group from .base import BaseClient from typing import Optional, Any, Dict, List, Union -from .models import Agg, Sort +from .models import Agg, GroupedDailyAgg, DailyOpenCloseAgg, PreviousCloseAgg, Sort from urllib3 import HTTPResponse # https://polygon.io/docs/stocks @@ -21,7 +22,6 @@ def get_aggs( ) -> Union[List[Agg], HTTPResponse]: """ Get aggregate bars for a ticker over a given date range in custom time window sizes. - :param ticker: The ticker symbol. :param multiplier: The size of the timespan multiplier. :param timespan: The size of the time window. @@ -33,7 +33,6 @@ def get_aggs( :param params: Any additional query params :param raw: Return raw object instead of results object :return: List of aggregates - :rtype: List[Agg] """ url = f"/v2/aggs/ticker/{ticker}/range/{multiplier}/{timespan}/{from_}/{to}" @@ -44,3 +43,62 @@ def get_aggs( deserializer=Agg.from_dict, raw=raw, ) + + def get_grouped_daily_aggs(self, + date: str, + adjusted: Optional[bool] = None, + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + ) -> Union[List[GroupedDailyAgg], HTTPResponse]: + """ + Get the daily open, high, low, and close (OHLC) for the entire market. + + :param date: The beginning date for the aggregate window. + :param adjusted: Whether or not the results are adjusted for splits. By default, results are adjusted. Set this to false to get results that are NOT adjusted for splits. + :param params: Any additional query params + :param raw: Return raw object instead of results object + :return: List of grouped daily aggregates + """ + url = f"/v2/aggs/grouped/locale/us/market/stocks/{date}" + + return self._get(path=url, params=self._get_params(self.get_grouped_daily_aggs, locals()), resultKey="results", deserializer=GroupedDailyAgg.from_dict, raw=raw) + + def get_daily_open_close_agg(self, + ticker: str, + date: str, + adjusted: Optional[bool] = None, + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + ) -> Union[DailyOpenCloseAgg, HTTPResponse]: + """ + Get the open, close and afterhours prices of a stock symbol on a certain date. + + :param ticker: The exchange symbol that this item is traded under. + :param date: The beginning date for the aggregate window. + :param adjusted: Whether or not the results are adjusted for splits. By default, results are adjusted. Set this to false to get results that are NOT adjusted for splits. + :param params: Any additional query params + :param raw: Return raw object instead of results object + :return: Daily open close aggregate + """ + url = f"/v1/open-close/{ticker}/{date}" + + return self._get(path=url, params=self._get_params(self.get_daily_open_close_agg, locals()), deserializer=DailyOpenCloseAgg.from_dict, raw=raw) + + def get_previous_close_agg(self, + ticker: str, + adjusted: Optional[bool] = None, + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + ) -> Union[PreviousCloseAgg, HTTPResponse]: + """ + Get the previous day's open, high, low, and close (OHLC) for the specified stock ticker. + + :param ticker: The ticker symbol of the stock/equity. + :param adjusted: Whether or not the results are adjusted for splits. By default, results are adjusted. Set this to false to get results that are NOT adjusted for splits. + :param params: Any additional query params + :param raw: Return raw object instead of results object + :return: Previous close aggregate + """ + url = f"/v2/aggs/ticker/{ticker}/prev" + + return self._get(path=url, params=self._get_params(self.get_previous_close_agg, locals()), resultKey="results", deserializer=PreviousCloseAgg.from_dict, raw=raw) diff --git a/polygon/rest/base.py b/polygon/rest/base.py index 9349b251..2ad65917 100644 --- a/polygon/rest/base.py +++ b/polygon/rest/base.py @@ -5,54 +5,43 @@ from enum import Enum from typing import Optional, Any -base = "https://api.polygon.io" +base = 'https://api.polygon.io' env_key = "POLYGON_API_KEY" # https://urllib3.readthedocs.io/en/stable/reference/urllib3.poolmanager.html class BaseClient: def __init__( - self, - api_key: Optional[str] = os.getenv(env_key), - connect_timeout: float = 10.0, - read_timeout: float = 10.0, - num_pools: int = 10, - retries=3, - base: str = base, - ): + self, + api_key: Optional[str] = os.getenv(env_key), + connect_timeout: float = 10.0, + read_timeout: float = 10.0, + num_pools: int = 10, + retries = 3, + base: str = base + ): if api_key is None: - raise Exception( - f"Must specify env var {env_key} or pass api_key in constructor" - ) + raise Exception(f"Must specify env var {env_key} or pass api_key in constructor") self.API_KEY = api_key self.BASE = base # https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html#urllib3.HTTPConnectionPool - self.client = urllib3.PoolManager( - num_pools=num_pools, headers={"Authorization": "Bearer " + self.API_KEY} - ) - self.timeout = urllib3.Timeout(connect=connect_timeout, read=read_timeout) + self.client = urllib3.PoolManager(num_pools=num_pools, headers={ + 'Authorization': 'Bearer ' + self.API_KEY + }) + self.timeout=urllib3.Timeout(connect=connect_timeout, read=read_timeout) self.retries = retries def _decode(self, resp): - return json.loads(resp.data.decode("utf-8")) + return json.loads(resp.data.decode('utf-8')) - def _get( - self, - path: str, - params: Optional[dict] = None, - resultKey: Optional[str] = None, - deserializer=None, - raw: bool = False, - ) -> Any: + def _get(self, path: str, params: Optional[dict] = None, resultKey: Optional[str] = None, deserializer = None, raw: bool = False) -> Any: if params is None: params = {} params = {str(k): str(v) for k, v in params.items() if v is not None} - resp = self.client.request( - "GET", self.BASE + path, fields=params, retries=self.retries - ) + resp = self.client.request('GET', self.BASE + path, fields=params, retries=self.retries) if resp.status != 200: - raise Exception(resp.data.decode("utf-8")) + raise Exception(resp.data.decode('utf-8')) if raw: return resp @@ -61,7 +50,10 @@ def _get( if resultKey: obj = obj[resultKey] - + else: + # If the resultKey does not exist, still need to put the results in a list + obj = [obj] + if deserializer: obj = [deserializer(o) for o in obj] @@ -74,7 +66,7 @@ def _get_params(self, fn, caller_locals): # https://docs.python.org/3.7/library/inspect.html#inspect.Signature for argname, v in inspect.signature(fn).parameters.items(): # https://docs.python.org/3.7/library/inspect.html#inspect.Parameter - if argname in ["params", "raw"]: + if argname in ['params', 'raw']: continue if v.default != v.empty: # timestamp_lt -> timestamp.lt @@ -88,16 +80,14 @@ def _get_params(self, fn, caller_locals): def _paginate(self, path: str, params: dict, raw: bool, deserializer): while True: - resp = self._get( - path=path, params=params, deserializer=deserializer, raw=True - ) + resp = self._get(path=path, params=params, deserializer=deserializer, raw=True) if raw: return resp decoded = self._decode(resp) for t in decoded["results"]: yield deserializer(t) if "next_url" in decoded: - path = decoded["next_url"].replace(self.BASE, "") + path = decoded["next_url"].replace(self.BASE, '') params = {} else: return diff --git a/polygon/rest/models/aggs.py b/polygon/rest/models/aggs.py index bdaa311c..526b3e82 100644 --- a/polygon/rest/models/aggs.py +++ b/polygon/rest/models/aggs.py @@ -3,24 +3,103 @@ @dataclass class Agg: + open: float + high: float + low: float + close: float + volume: float + vwap: float timestamp: int + transactions: int + + @staticmethod + def from_dict(d): + return Agg( + d.get('o', None), + d.get('h', None), + d.get('l', None), + d.get('c', None), + d.get('v', None), + d.get('vw', None), + d.get('t', None), + d.get('n', None) + ) + +@dataclass +class GroupedDailyAgg: + ticker: str open: float high: float low: float close: float volume: float vwap: Optional[float] + timestamp: int transactions: Optional[int] @staticmethod def from_dict(d): - return Agg( - timestamp=d["t"], - open=d["o"], - high=d["h"], - low=d["l"], - close=d["c"], - volume=d["v"], - vwap=d.get("vw", None), - transactions=d.get("n", None) + return GroupedDailyAgg( + d.get('T', None), + d.get('o', None), + d.get('h', None), + d.get('l', None), + d.get('c', None), + d.get('v', None), + d.get('vw', None), + d.get('t', None), + d.get('n', None) + ) + +@dataclass +class DailyOpenCloseAgg: + after_hours: Optional[float] + close: float + from_: str + high: float + low: float + open: float + pre_market: float + status: Optional[str] + symbol: str + volume: float + + @staticmethod + def from_dict(d): + return DailyOpenCloseAgg( + d.get('afterHours', None), + d.get('close', None), + d.get('from', None), + d.get('high', None), + d.get('low', None), + d.get('open', None), + d.get('preMarket', None), + d.get('status', None), + d.get('symbol', None), + d.get('volume', None) ) + +@dataclass +class PreviousCloseAgg: + ticker: str + close: float + high: float + low: float + open: float + timestamp: float + volume: float + vwap: Optional[float] + + @staticmethod + def from_dict(d): + return PreviousCloseAgg( + d.get('T', None), + d.get('c', None), + d.get('h', None), + d.get('l', None), + d.get('o', None), + d.get('t', None), + d.get('v', None), + d.get('vw', None), + ) + diff --git a/tests/mocks.py b/tests/mocks.py index ce35c718..3566e7e9 100644 --- a/tests/mocks.py +++ b/tests/mocks.py @@ -6,6 +6,18 @@ ( "/v2/aggs/ticker/AAPL/range/1/day/2005-04-01/2005-04-04", '{"ticker":"AAPL","queryCount":2,"resultsCount":2,"adjusted":true,"results":[{"v":6.42646396e+08,"vw":1.469,"o":1.5032,"c":1.4604,"h":1.5064,"l":1.4489,"t":1112331600000,"n":82132},{"v":5.78172308e+08,"vw":1.4589,"o":1.4639,"c":1.4675,"h":1.4754,"l":1.4343,"t":1112587200000,"n":65543}],"status":"OK","request_id":"12afda77aab3b1936c5fb6ef4241ae42","count":2}' + ), + ( + "/v2/aggs/grouped/locale/us/market/stocks/2005-04-04", + '{"queryCount":1,"resultsCount":1,"adjusted": true,"results": [{"T":"GIK","v":895345,"vw":9.9979,"o":9.99,"c":10.02,"h":10.02,"l":9.9,"t":1602705600000,"n":96}],"status":"OK","request_id":"eae3ded2d6d43f978125b7a8a609fad9","count":1}' + ), + ( + "/v1/open-close/AAPL/2005-04-01", + '{"status": "OK","from": "2021-04-01","symbol": "AAPL","open": 123.66,"high": 124.18,"low": 122.49,"close": 123,"volume": 75089134,"afterHours": 123,"preMarket": 123.45}' + ), + ( + "/v2/aggs/ticker/AAPL/prev", + '{"ticker":"AAPL","queryCount":1,"resultsCount":1,"adjusted":true,"results":[{"T":"AAPL","v":9.5595226e+07,"vw":158.6074,"o":162.25,"c":156.8,"h":162.34,"l":156.72,"t":1651003200000,"n":899965}],"status":"OK","request_id":"5e5378d5ecaf3df794bb52e45d015d2e","count":1}' ) ] diff --git a/tests/test_aggs.py b/tests/test_aggs.py index f8c88cca..58e9ba57 100644 --- a/tests/test_aggs.py +++ b/tests/test_aggs.py @@ -1,5 +1,5 @@ from polygon import RESTClient -from polygon.rest.models import Agg +from polygon.rest.models import Agg, GroupedDailyAgg, DailyOpenCloseAgg, PreviousCloseAgg from mocks import BaseTest class AggsTest(BaseTest): @@ -9,3 +9,20 @@ def test_get_aggs(self): expected = [Agg(open=1.5032, high=1.5064, low=1.4489, close=1.4604, volume=642646396.0, vwap=1.469, timestamp=1112331600000, transactions=82132), Agg(open=1.4639, high=1.4754, low=1.4343, close=1.4675, volume=578172308.0, vwap=1.4589, timestamp=1112587200000, transactions=65543)] self.assertEqual(aggs, expected) + def test_get_grouped_daily_aggs(self): + c = RESTClient("") + aggs = c.get_grouped_daily_aggs("2005-04-04", True) + expected = [GroupedDailyAgg(ticker="GIK", open=9.99, high=10.02, low=9.9, close=10.02, volume=895345, vwap=9.9979, timestamp=1602705600000, transactions=96)] + self.assertEqual(aggs, expected) + + def test_get_daily_open_close_agg(self): + c = RESTClient("") + aggs = c.get_daily_open_close_agg("AAPL", "2005-04-01", True) + expected = [DailyOpenCloseAgg(after_hours=123, close=123, from_="2021-04-01", high=124.18, low=122.49, open=123.66, pre_market=123.45, status="OK", symbol="AAPL", volume=75089134) ] + self.assertEqual(aggs, expected) + + def test_get_previous_close_agg(self): + c = RESTClient("") + aggs = c.get_previous_close_agg("AAPL") + expected = [PreviousCloseAgg(ticker='AAPL', close=156.8, high=162.34, low=156.72, open=162.25, timestamp=1651003200000, volume=95595226.0, vwap=158.6074)] + self.assertEqual(aggs, expected) \ No newline at end of file From 77e9c3fd7cd0eea393c5cfa8c14abf23e4b80fd5 Mon Sep 17 00:00:00 2001 From: mcdayoub <38268139+mcdayoub@users.noreply.github.com> Date: Wed, 27 Apr 2022 17:32:40 -0400 Subject: [PATCH 2/5] aggs endpoints --- polygon/rest/aggs.py | 38 ++++++++++++++----- polygon/rest/base.py | 57 +++++++++++++++++----------- polygon/rest/models/aggs.py | 74 +++++++++++++++++++------------------ tests/mocks.py | 10 ++--- tests/test_aggs.py | 49 ++++++++++++++++++++++-- 5 files changed, 153 insertions(+), 75 deletions(-) diff --git a/polygon/rest/aggs.py b/polygon/rest/aggs.py index 98ef587b..35655fe6 100644 --- a/polygon/rest/aggs.py +++ b/polygon/rest/aggs.py @@ -50,12 +50,13 @@ def get_aggs( raw=raw, ) - def get_grouped_daily_aggs(self, + def get_grouped_daily_aggs( + self, date: str, adjusted: Optional[bool] = None, params: Optional[Dict[str, Any]] = None, raw: bool = False, - ) -> Union[List[GroupedDailyAgg], HTTPResponse]: + ) -> Union[List[GroupedDailyAgg], HTTPResponse]: """ Get the daily open, high, low, and close (OHLC) for the entire market. @@ -67,15 +68,22 @@ def get_grouped_daily_aggs(self, """ url = f"/v2/aggs/grouped/locale/us/market/stocks/{date}" - return self._get(path=url, params=self._get_params(self.get_grouped_daily_aggs, locals()), resultKey="results", deserializer=GroupedDailyAgg.from_dict, raw=raw) + return self._get( + path=url, + params=self._get_params(self.get_grouped_daily_aggs, locals()), + resultKey="results", + deserializer=GroupedDailyAgg.from_dict, + raw=raw, + ) - def get_daily_open_close_agg(self, + def get_daily_open_close_agg( + self, ticker: str, date: str, adjusted: Optional[bool] = None, params: Optional[Dict[str, Any]] = None, raw: bool = False, - ) -> Union[DailyOpenCloseAgg, HTTPResponse]: + ) -> Union[DailyOpenCloseAgg, HTTPResponse]: """ Get the open, close and afterhours prices of a stock symbol on a certain date. @@ -88,14 +96,20 @@ def get_daily_open_close_agg(self, """ url = f"/v1/open-close/{ticker}/{date}" - return self._get(path=url, params=self._get_params(self.get_daily_open_close_agg, locals()), deserializer=DailyOpenCloseAgg.from_dict, raw=raw) + return self._get( + path=url, + params=self._get_params(self.get_daily_open_close_agg, locals()), + deserializer=DailyOpenCloseAgg.from_dict, + raw=raw, + ) - def get_previous_close_agg(self, + def get_previous_close_agg( + self, ticker: str, adjusted: Optional[bool] = None, params: Optional[Dict[str, Any]] = None, raw: bool = False, - ) -> Union[PreviousCloseAgg, HTTPResponse]: + ) -> Union[PreviousCloseAgg, HTTPResponse]: """ Get the previous day's open, high, low, and close (OHLC) for the specified stock ticker. @@ -107,4 +121,10 @@ def get_previous_close_agg(self, """ url = f"/v2/aggs/ticker/{ticker}/prev" - return self._get(path=url, params=self._get_params(self.get_previous_close_agg, locals()), resultKey="results", deserializer=PreviousCloseAgg.from_dict, raw=raw) + return self._get( + path=url, + params=self._get_params(self.get_previous_close_agg, locals()), + resultKey="results", + deserializer=PreviousCloseAgg.from_dict, + raw=raw, + ) diff --git a/polygon/rest/base.py b/polygon/rest/base.py index 2ad65917..7a6bc098 100644 --- a/polygon/rest/base.py +++ b/polygon/rest/base.py @@ -5,43 +5,54 @@ from enum import Enum from typing import Optional, Any -base = 'https://api.polygon.io' +base = "https://api.polygon.io" env_key = "POLYGON_API_KEY" # https://urllib3.readthedocs.io/en/stable/reference/urllib3.poolmanager.html class BaseClient: def __init__( - self, - api_key: Optional[str] = os.getenv(env_key), - connect_timeout: float = 10.0, - read_timeout: float = 10.0, - num_pools: int = 10, - retries = 3, - base: str = base - ): + self, + api_key: Optional[str] = os.getenv(env_key), + connect_timeout: float = 10.0, + read_timeout: float = 10.0, + num_pools: int = 10, + retries=3, + base: str = base, + ): if api_key is None: - raise Exception(f"Must specify env var {env_key} or pass api_key in constructor") + raise Exception( + f"Must specify env var {env_key} or pass api_key in constructor" + ) self.API_KEY = api_key self.BASE = base # https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html#urllib3.HTTPConnectionPool - self.client = urllib3.PoolManager(num_pools=num_pools, headers={ - 'Authorization': 'Bearer ' + self.API_KEY - }) - self.timeout=urllib3.Timeout(connect=connect_timeout, read=read_timeout) + self.client = urllib3.PoolManager( + num_pools=num_pools, headers={"Authorization": "Bearer " + self.API_KEY} + ) + self.timeout = urllib3.Timeout(connect=connect_timeout, read=read_timeout) self.retries = retries def _decode(self, resp): - return json.loads(resp.data.decode('utf-8')) + return json.loads(resp.data.decode("utf-8")) - def _get(self, path: str, params: Optional[dict] = None, resultKey: Optional[str] = None, deserializer = None, raw: bool = False) -> Any: + def _get( + self, + path: str, + params: Optional[dict] = None, + resultKey: Optional[str] = None, + deserializer=None, + raw: bool = False, + ) -> Any: if params is None: params = {} params = {str(k): str(v) for k, v in params.items() if v is not None} - resp = self.client.request('GET', self.BASE + path, fields=params, retries=self.retries) + resp = self.client.request( + "GET", self.BASE + path, fields=params, retries=self.retries + ) if resp.status != 200: - raise Exception(resp.data.decode('utf-8')) + raise Exception(resp.data.decode("utf-8")) if raw: return resp @@ -53,7 +64,7 @@ def _get(self, path: str, params: Optional[dict] = None, resultKey: Optional[str else: # If the resultKey does not exist, still need to put the results in a list obj = [obj] - + if deserializer: obj = [deserializer(o) for o in obj] @@ -66,7 +77,7 @@ def _get_params(self, fn, caller_locals): # https://docs.python.org/3.7/library/inspect.html#inspect.Signature for argname, v in inspect.signature(fn).parameters.items(): # https://docs.python.org/3.7/library/inspect.html#inspect.Parameter - if argname in ['params', 'raw']: + if argname in ["params", "raw"]: continue if v.default != v.empty: # timestamp_lt -> timestamp.lt @@ -80,14 +91,16 @@ def _get_params(self, fn, caller_locals): def _paginate(self, path: str, params: dict, raw: bool, deserializer): while True: - resp = self._get(path=path, params=params, deserializer=deserializer, raw=True) + resp = self._get( + path=path, params=params, deserializer=deserializer, raw=True + ) if raw: return resp decoded = self._decode(resp) for t in decoded["results"]: yield deserializer(t) if "next_url" in decoded: - path = decoded["next_url"].replace(self.BASE, '') + path = decoded["next_url"].replace(self.BASE, "") params = {} else: return diff --git a/polygon/rest/models/aggs.py b/polygon/rest/models/aggs.py index db7853ea..0e463b02 100644 --- a/polygon/rest/models/aggs.py +++ b/polygon/rest/models/aggs.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from typing import Optional + @dataclass class Agg: open: float @@ -15,16 +16,17 @@ class Agg: @staticmethod def from_dict(d): return Agg( - d.get('o', None), - d.get('h', None), - d.get('l', None), - d.get('c', None), - d.get('v', None), - d.get('vw', None), - d.get('t', None), - d.get('n', None) + d.get("o", None), + d.get("h", None), + d.get("l", None), + d.get("c", None), + d.get("v", None), + d.get("vw", None), + d.get("t", None), + d.get("n", None), ) + @dataclass class GroupedDailyAgg: ticker: str @@ -40,17 +42,18 @@ class GroupedDailyAgg: @staticmethod def from_dict(d): return GroupedDailyAgg( - d.get('T', None), - d.get('o', None), - d.get('h', None), - d.get('l', None), - d.get('c', None), - d.get('v', None), - d.get('vw', None), - d.get('t', None), - d.get('n', None) + d.get("T", None), + d.get("o", None), + d.get("h", None), + d.get("l", None), + d.get("c", None), + d.get("v", None), + d.get("vw", None), + d.get("t", None), + d.get("n", None), ) + @dataclass class DailyOpenCloseAgg: after_hours: Optional[float] @@ -67,18 +70,19 @@ class DailyOpenCloseAgg: @staticmethod def from_dict(d): return DailyOpenCloseAgg( - d.get('afterHours', None), - d.get('close', None), - d.get('from', None), - d.get('high', None), - d.get('low', None), - d.get('open', None), - d.get('preMarket', None), - d.get('status', None), - d.get('symbol', None), - d.get('volume', None) + d.get("afterHours", None), + d.get("close", None), + d.get("from", None), + d.get("high", None), + d.get("low", None), + d.get("open", None), + d.get("preMarket", None), + d.get("status", None), + d.get("symbol", None), + d.get("volume", None), ) + @dataclass class PreviousCloseAgg: ticker: str @@ -93,12 +97,12 @@ class PreviousCloseAgg: @staticmethod def from_dict(d): return PreviousCloseAgg( - d.get('T', None), - d.get('c', None), - d.get('h', None), - d.get('l', None), - d.get('o', None), - d.get('t', None), - d.get('v', None), - d.get('vw', None), + d.get("T", None), + d.get("c", None), + d.get("h", None), + d.get("l", None), + d.get("o", None), + d.get("t", None), + d.get("v", None), + d.get("vw", None), ) diff --git a/tests/mocks.py b/tests/mocks.py index 13d75e5e..a00bae89 100644 --- a/tests/mocks.py +++ b/tests/mocks.py @@ -5,20 +5,20 @@ mocks = [ ( "/v2/aggs/ticker/AAPL/range/1/day/2005-04-01/2005-04-04", - '{"ticker":"AAPL","queryCount":2,"resultsCount":2,"adjusted":true,"results":[{"v":6.42646396e+08,"vw":1.469,"o":1.5032,"c":1.4604,"h":1.5064,"l":1.4489,"t":1112331600000,"n":82132},{"v":5.78172308e+08,"vw":1.4589,"o":1.4639,"c":1.4675,"h":1.4754,"l":1.4343,"t":1112587200000,"n":65543}],"status":"OK","request_id":"12afda77aab3b1936c5fb6ef4241ae42","count":2}' + '{"ticker":"AAPL","queryCount":2,"resultsCount":2,"adjusted":true,"results":[{"v":6.42646396e+08,"vw":1.469,"o":1.5032,"c":1.4604,"h":1.5064,"l":1.4489,"t":1112331600000,"n":82132},{"v":5.78172308e+08,"vw":1.4589,"o":1.4639,"c":1.4675,"h":1.4754,"l":1.4343,"t":1112587200000,"n":65543}],"status":"OK","request_id":"12afda77aab3b1936c5fb6ef4241ae42","count":2}', ), ( "/v2/aggs/grouped/locale/us/market/stocks/2005-04-04", - '{"queryCount":1,"resultsCount":1,"adjusted": true,"results": [{"T":"GIK","v":895345,"vw":9.9979,"o":9.99,"c":10.02,"h":10.02,"l":9.9,"t":1602705600000,"n":96}],"status":"OK","request_id":"eae3ded2d6d43f978125b7a8a609fad9","count":1}' + '{"queryCount":1,"resultsCount":1,"adjusted": true,"results": [{"T":"GIK","v":895345,"vw":9.9979,"o":9.99,"c":10.02,"h":10.02,"l":9.9,"t":1602705600000,"n":96}],"status":"OK","request_id":"eae3ded2d6d43f978125b7a8a609fad9","count":1}', ), ( "/v1/open-close/AAPL/2005-04-01", - '{"status": "OK","from": "2021-04-01","symbol": "AAPL","open": 123.66,"high": 124.18,"low": 122.49,"close": 123,"volume": 75089134,"afterHours": 123,"preMarket": 123.45}' + '{"status": "OK","from": "2021-04-01","symbol": "AAPL","open": 123.66,"high": 124.18,"low": 122.49,"close": 123,"volume": 75089134,"afterHours": 123,"preMarket": 123.45}', ), ( "/v2/aggs/ticker/AAPL/prev", - '{"ticker":"AAPL","queryCount":1,"resultsCount":1,"adjusted":true,"results":[{"T":"AAPL","v":9.5595226e+07,"vw":158.6074,"o":162.25,"c":156.8,"h":162.34,"l":156.72,"t":1651003200000,"n":899965}],"status":"OK","request_id":"5e5378d5ecaf3df794bb52e45d015d2e","count":1}' - ) + '{"ticker":"AAPL","queryCount":1,"resultsCount":1,"adjusted":true,"results":[{"T":"AAPL","v":9.5595226e+07,"vw":158.6074,"o":162.25,"c":156.8,"h":162.34,"l":156.72,"t":1651003200000,"n":899965}],"status":"OK","request_id":"5e5378d5ecaf3df794bb52e45d015d2e","count":1}', + ), ] diff --git a/tests/test_aggs.py b/tests/test_aggs.py index 93ed1537..961df02b 100644 --- a/tests/test_aggs.py +++ b/tests/test_aggs.py @@ -1,5 +1,10 @@ from polygon import RESTClient -from polygon.rest.models import Agg, GroupedDailyAgg, DailyOpenCloseAgg, PreviousCloseAgg +from polygon.rest.models import ( + Agg, + GroupedDailyAgg, + DailyOpenCloseAgg, + PreviousCloseAgg, +) from mocks import BaseTest @@ -34,17 +39,53 @@ def test_get_aggs(self): def test_get_grouped_daily_aggs(self): c = RESTClient("") aggs = c.get_grouped_daily_aggs("2005-04-04", True) - expected = [GroupedDailyAgg(ticker="GIK", open=9.99, high=10.02, low=9.9, close=10.02, volume=895345, vwap=9.9979, timestamp=1602705600000, transactions=96)] + expected = [ + GroupedDailyAgg( + ticker="GIK", + open=9.99, + high=10.02, + low=9.9, + close=10.02, + volume=895345, + vwap=9.9979, + timestamp=1602705600000, + transactions=96, + ) + ] self.assertEqual(aggs, expected) def test_get_daily_open_close_agg(self): c = RESTClient("") aggs = c.get_daily_open_close_agg("AAPL", "2005-04-01", True) - expected = [DailyOpenCloseAgg(after_hours=123, close=123, from_="2021-04-01", high=124.18, low=122.49, open=123.66, pre_market=123.45, status="OK", symbol="AAPL", volume=75089134) ] + expected = [ + DailyOpenCloseAgg( + after_hours=123, + close=123, + from_="2021-04-01", + high=124.18, + low=122.49, + open=123.66, + pre_market=123.45, + status="OK", + symbol="AAPL", + volume=75089134, + ) + ] self.assertEqual(aggs, expected) def test_get_previous_close_agg(self): c = RESTClient("") aggs = c.get_previous_close_agg("AAPL") - expected = [PreviousCloseAgg(ticker='AAPL', close=156.8, high=162.34, low=156.72, open=162.25, timestamp=1651003200000, volume=95595226.0, vwap=158.6074)] + expected = [ + PreviousCloseAgg( + ticker="AAPL", + close=156.8, + high=162.34, + low=156.72, + open=162.25, + timestamp=1651003200000, + volume=95595226.0, + vwap=158.6074, + ) + ] self.assertEqual(aggs, expected) From cb4f18fe907972eff1a9c35699263b379e0a0ebc Mon Sep 17 00:00:00 2001 From: mcdayoub <38268139+mcdayoub@users.noreply.github.com> Date: Thu, 28 Apr 2022 11:31:33 -0400 Subject: [PATCH 3/5] aggs models optional --- polygon/rest/models/aggs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/polygon/rest/models/aggs.py b/polygon/rest/models/aggs.py index 0e463b02..5b947523 100644 --- a/polygon/rest/models/aggs.py +++ b/polygon/rest/models/aggs.py @@ -9,9 +9,9 @@ class Agg: low: float close: float volume: float - vwap: float - timestamp: int - transactions: int + vwap: Optional[float] + timestamp: Optional[int] + transactions: Optional[int] @staticmethod def from_dict(d): @@ -36,7 +36,7 @@ class GroupedDailyAgg: close: float volume: float vwap: Optional[float] - timestamp: int + timestamp: Optional[int] transactions: Optional[int] @staticmethod @@ -62,7 +62,7 @@ class DailyOpenCloseAgg: high: float low: float open: float - pre_market: float + pre_market: Optional[float] status: Optional[str] symbol: str volume: float @@ -90,7 +90,7 @@ class PreviousCloseAgg: high: float low: float open: float - timestamp: float + timestamp: Optional[float] volume: float vwap: Optional[float] From e130b167d5cd1f178fca4c5fd4251f8a2ed8f8bb Mon Sep 17 00:00:00 2001 From: mcdayoub <38268139+mcdayoub@users.noreply.github.com> Date: Thu, 28 Apr 2022 11:36:46 -0400 Subject: [PATCH 4/5] aggs models optional --- polygon/rest/aggs.py | 4 ++-- polygon/rest/base.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/polygon/rest/aggs.py b/polygon/rest/aggs.py index 022563f2..3182bc1c 100644 --- a/polygon/rest/aggs.py +++ b/polygon/rest/aggs.py @@ -71,7 +71,7 @@ def get_grouped_daily_aggs( return self._get( path=url, params=self._get_params(self.get_grouped_daily_aggs, locals()), - resultKey="results", + result_key="results", deserializer=GroupedDailyAgg.from_dict, raw=raw, ) @@ -124,7 +124,7 @@ def get_previous_close_agg( return self._get( path=url, params=self._get_params(self.get_previous_close_agg, locals()), - resultKey="results", + result_key="result_keys", deserializer=PreviousCloseAgg.from_dict, raw=raw, ) diff --git a/polygon/rest/base.py b/polygon/rest/base.py index 7c79fd74..07a13e40 100644 --- a/polygon/rest/base.py +++ b/polygon/rest/base.py @@ -59,8 +59,8 @@ def _get( obj = self._decode(resp) - if resultKey: - obj = obj[resultKey] + if result_key: + obj = obj[result_key] else: # If the resultKey does not exist, still need to put the results in a list obj = [obj] From 931b02f504d80482cff43732d13ce4583d47ab41 Mon Sep 17 00:00:00 2001 From: mcdayoub <38268139+mcdayoub@users.noreply.github.com> Date: Thu, 28 Apr 2022 11:40:32 -0400 Subject: [PATCH 5/5] fixed result_keys --- polygon/rest/aggs.py | 2 +- polygon/rest/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/polygon/rest/aggs.py b/polygon/rest/aggs.py index 3182bc1c..9643711a 100644 --- a/polygon/rest/aggs.py +++ b/polygon/rest/aggs.py @@ -124,7 +124,7 @@ def get_previous_close_agg( return self._get( path=url, params=self._get_params(self.get_previous_close_agg, locals()), - result_key="result_keys", + result_key="results", deserializer=PreviousCloseAgg.from_dict, raw=raw, ) diff --git a/polygon/rest/base.py b/polygon/rest/base.py index 07a13e40..fcad62c4 100644 --- a/polygon/rest/base.py +++ b/polygon/rest/base.py @@ -62,7 +62,7 @@ def _get( if result_key: obj = obj[result_key] else: - # If the resultKey does not exist, still need to put the results in a list + # If the result_key does not exist, still need to put the results in a list obj = [obj] if deserializer: