From 433970f69720f4131779311eb8e2576a79160d57 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Thu, 12 Oct 2023 08:47:49 -0700 Subject: [PATCH 01/21] coerce string type, add default=None --- .../providers/cboe/openbb_cboe/models/stock_search.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openbb_platform/providers/cboe/openbb_cboe/models/stock_search.py b/openbb_platform/providers/cboe/openbb_cboe/models/stock_search.py index 2b9a916f6f4c..c2f8f7312cb4 100644 --- a/openbb_platform/providers/cboe/openbb_cboe/models/stock_search.py +++ b/openbb_platform/providers/cboe/openbb_cboe/models/stock_search.py @@ -52,12 +52,12 @@ def extract_data( query: CboeStockSearchQueryParams, credentials: Optional[Dict[str, str]], **kwargs: Any, - ) -> dict: + ) -> Dict: """Return the raw data from the CBOE endpoint.""" data = {} - symbols = get_cboe_directory().reset_index().replace("nan", None) - target = "name" if not query.is_symbol else "symbol" + symbols = get_cboe_directory().reset_index() + target = "name" if query.is_symbol is False else "symbol" idx = symbols[target].str.contains(query.query, case=False) result = symbols[idx].to_dict("records") data.update({"results": result}) From 56a8c80883f441358b030a92a7b70a7ef52930cb Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Fri, 27 Oct 2023 00:47:50 -0700 Subject: [PATCH 02/21] add polygon stock quote --- .../stocks/integration/test_stocks_api.py | 1 + .../stocks/integration/test_stocks_python.py | 1 + .../polygon/openbb_polygon/__init__.py | 2 + .../openbb_polygon/models/stock_quote.py | 178 ++++++++++ .../polygon/openbb_polygon/utils/helpers.py | 309 ++++++++++++++++++ .../test_polygon_stock_quote_fetcher.yaml | 70 ++++ .../polygon/tests/test_polygon_fetchers.py | 10 + 7 files changed, 571 insertions(+) create mode 100644 openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py create mode 100644 openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_api.py b/openbb_platform/extensions/stocks/integration/test_stocks_api.py index 1469edc6af23..dee9fdfb1654 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_api.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_api.py @@ -920,6 +920,7 @@ def test_stocks_search(params, headers): [ ({"source": "iex", "provider": "intrinio", "symbol": "AAPL"}), ({"symbol": "AAPL", "provider": "fmp"}), + ({"symbol": "AAPL", "provider": "polygon"}), ], ) @pytest.mark.integration diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_python.py b/openbb_platform/extensions/stocks/integration/test_stocks_python.py index 9ce6bdb186d1..fd3cf827f006 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_python.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_python.py @@ -873,6 +873,7 @@ def test_stocks_search(params, obb): ({"symbol": "AAPL"}), ({"source": "iex", "provider": "intrinio", "symbol": "AAPL"}), ({"symbol": "AAPL", "provider": "fmp"}), + ({"symbol": "AAPL", "provider": "polygon"}), ], ) @pytest.mark.integration diff --git a/openbb_platform/providers/polygon/openbb_polygon/__init__.py b/openbb_platform/providers/polygon/openbb_polygon/__init__.py index 05a34bcaf52a..232eb7ed498f 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/__init__.py +++ b/openbb_platform/providers/polygon/openbb_polygon/__init__.py @@ -10,6 +10,7 @@ ) from openbb_polygon.models.stock_historical import PolygonStockHistoricalFetcher from openbb_polygon.models.stock_news import PolygonStockNewsFetcher +from openbb_polygon.models.stock_quote import PolygonStockQuoteFetcher from openbb_provider.abstract.provider import Provider polygon_provider = Provider( @@ -29,5 +30,6 @@ "MajorIndicesHistorical": PolygonMajorIndicesHistoricalFetcher, "ForexHistorical": PolygonForexHistoricalFetcher, "ForexPairs": PolygonForexPairsFetcher, + "StockQuote": PolygonStockQuoteFetcher, }, ) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py new file mode 100644 index 000000000000..120e5693acae --- /dev/null +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py @@ -0,0 +1,178 @@ +"""Polygon Stock Quotes Model.""" + +from datetime import datetime +from typing import Any, Dict, List, Optional, Union + +from openbb_polygon.utils.helpers import get_data_one, map_exchanges, map_tape +from openbb_provider.abstract.data import Data +from openbb_provider.abstract.fetcher import Fetcher +from openbb_provider.standard_models.stock_quote import StockQuoteQueryParams +from openbb_provider.utils.descriptions import QUERY_DESCRIPTIONS +from openbb_provider.utils.helpers import get_querystring +from pandas import to_datetime +from pydantic import Field, field_validator + + +class PolygonStockQuoteQueryParams(StockQuoteQueryParams): + """Polygon Stock Quote query params.""" + + symbol: str = Field( + description=QUERY_DESCRIPTIONS.get("symbol", "") + + " If a list is supplied, only the first symbol will be processed." + ) + + limit: Optional[int] = Field( + default=50, + description=( + QUERY_DESCRIPTIONS.get("interval", "") + + " Up to one million records will be returned. Pagination occurs in groups of 50,000." + + " Remaining limit values will always return 50,000 more records unless it is the last page." + + " High volume tickers will require multiple max requests for a single day's NBBO records." + + " Expect stocks, like SPY, to require 5-10M rows, approaching 1GB in size, per day." + ), + ) + timestamp: Optional[Union[str, int]] = Field( + default=None, + description="Query by timestamp. Either a date with the format YYYY-MM-DD or a nanosecond timestamp.", + ) + + +class PolygonStockQuoteData(Data): + """Polygon Stock Quote data.""" + + ask_exchange: Optional[Union[int, str]] = Field( + default=None, + description="The exchange ID for the ask. https://polygon.io/docs/stocks/get_v3_reference_exchanges", + alias="ask_exchange", + ) + ask: Optional[float] = Field( + default=None, description="The last ask price.", alias="ask_price" + ) + ask_size: Optional[int] = Field( + default=None, + description=""" + The ask size. This represents the number of round lot orders at the given ask price. + The normal round lot size is 100 shares. + An ask size of 2 means there are 200 shares available to purchase at the given ask price. + """, + alias="ask_size", + ) + bid_size: Optional[int] = Field( + default=None, description="The bid size in round lots.", alias="bid_size" + ) + bid: Optional[float] = Field( + default=None, description="The last bid price.", alias="bid_price" + ) + bid_exchange: Optional[Union[int, str]] = Field( + default=None, + description="The exchange ID for the bid. https://polygon.io/docs/stocks/get_v3_reference_exchanges", + alias="bid_exchange", + ) + tape: Optional[Union[int, str]] = Field( + default=None, description="The exchange tape.", alias="tape_integer" + ) + conditions: Optional[Union[str, List[int], List[str]]] = Field( + default=None, description="A list of condition codes.", alias="conditions" + ) + indicators: Optional[Any] = Field( + default=None, description="A list of indicator codes.", alias="indicators" + ) + sequence_num: Optional[int] = Field( + default=None, + description=""" + The sequence number represents the sequence in which message events happened. + These are increasing and unique per ticker symbol, but will not always be sequential + (e.g., 1, 2, 6, 9, 10, 11) + """, + alias="sequence_number", + ) + participant_timestamp: Optional[Union[int, datetime]] = Field( + default=None, + description=""" + The nanosecond accuracy Participant/Exchange Unix Timestamp. + This is the timestamp of when the quote was actually generated at the exchange. + """, + ) + sip_timestamp: Optional[Union[int, datetime]] = Field( + default=None, + description=""" + The nanosecond accuracy SIP Unix Timestamp. + This is the timestamp of when the SIP received this quote from the exchange which produced it. + """, + ) + trf_timestamp: Optional[Union[int, datetime]] = Field( + default=None, + description=""" + The nanosecond accuracy TRF (Trade Reporting Facility) Unix Timestamp. + This is the timestamp of when the trade reporting facility received this quote. + """, + ) + + @field_validator( + "sip_timestamp", + "participant_timestamp", + "trf_timestamp", + mode="before", + check_fields=False, + ) + def date_validate(cls, v): # pylint: disable=E0213 + """Return formatted datetime.""" + return ( + to_datetime(v, unit="ns", origin="unix", utc=True).tz_convert("US/Eastern") + if v + else None + ) + + +class PolygonStockQuoteFetcher( + Fetcher[PolygonStockQuoteQueryParams, List[PolygonStockQuoteData]] +): + @staticmethod + def transform_query(params: Dict[str, Any]) -> PolygonStockQuoteQueryParams: + """Transform the query parameters.""" + return PolygonStockQuoteQueryParams(**params) + + @staticmethod + def extract_data( + query: PolygonStockQuoteQueryParams, + credentials: Optional[Dict[str, str]], + **kwargs: Any, + ) -> List[Dict]: + """Extract the data from the Polygon endpoint.""" + + api_key = credentials.get("polygon_api_key") if credentials else "" + # This is to ensure that a list of symbols is not processed, only the first item will be passed. + symbols = query.symbol.split(",") if "," in query.symbol else [query.symbol] + query.symbol = symbols[0] + records = 0 + # Internal hard limit to prevent system overloads. + max = 1000000 + if query.limit is None or query.limit >= 50000: + max = query.limit if query.limit and query.limit < max else max + query.limit = 50000 + results = [] + base_url = f"https://api.polygon.io/v3/quotes/{symbols[0]}" + query_str = get_querystring(query.model_dump(by_alias=True), ["symbol"]) + url = f"{base_url}?{query_str}&apiKey={api_key}" + data = get_data_one(url, **kwargs) + results = data["results"] + results = map_exchanges(results) + results = map_tape(results) + records += len(results) + if records == 50000 and "next_url" in data: + while records < max and "next_url" in data and data["next_url"]: + new_data = get_data_one(f"{data['next_url']}&apiKey={api_key}") + records += len(new_data["results"]) + new_data = map_tape(new_data["results"]) + results.extend(map_exchanges(new_data)) + data = new_data + + return results + + @staticmethod + def transform_data( + data: List[Dict], + **kwargs: Any, + ) -> List[PolygonStockQuoteData]: + """Transform the data.""" + return [PolygonStockQuoteData.model_validate(d) for d in data] diff --git a/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py b/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py index bc8bb957f5bb..8f0d6f075c7d 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py +++ b/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py @@ -130,3 +130,312 @@ def get_date_condition(date: str) -> str: return date.split(key)[1], value return date, "eq" + + +STOCK_TAPE_MAP = { + 1: "Tape A: NYSE", + 2: "Tape B: NYSE ARCA", + 3: "Tape C: NASDAQ", +} + +EXCHANGE_ID_MAP = [ + { + "id": 1, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "NYSE American, LLC", + "acronym": "AMEX", + "mic": "XASE", + "operating_mic": "XNYS", + "participant_id": "A", + "url": "https://www.nyse.com/markets/nyse-american", + }, + { + "id": 2, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Nasdaq OMX BX, Inc.", + "mic": "XBOS", + "operating_mic": "XNAS", + "participant_id": "B", + "url": "https://www.nasdaq.com/solutions/nasdaq-bx-stock-market", + }, + { + "id": 3, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "NYSE National, Inc.", + "acronym": "NSX", + "mic": "XCIS", + "operating_mic": "XNYS", + "participant_id": "C", + "url": "https://www.nyse.com/markets/nyse-national", + }, + { + "id": 4, + "type": "TRF", + "asset_class": "stocks", + "locale": "us", + "name": "FINRA NYSE TRF", + "mic": "FINY", + "operating_mic": "XNYS", + "participant_id": "D", + "url": "https://www.finra.org", + }, + { + "id": 4, + "type": "TRF", + "asset_class": "stocks", + "locale": "us", + "name": "FINRA Nasdaq TRF Carteret", + "mic": "FINN", + "operating_mic": "FINR", + "participant_id": "D", + "url": "https://www.finra.org", + }, + { + "id": 4, + "type": "TRF", + "asset_class": "stocks", + "locale": "us", + "name": "FINRA Nasdaq TRF Chicago", + "mic": "FINC", + "operating_mic": "FINR", + "participant_id": "D", + "url": "https://www.finra.org", + }, + { + "id": 4, + "type": "TRF", + "asset_class": "stocks", + "locale": "us", + "name": "FINRA Alternative Display Facility", + "mic": "XADF", + "operating_mic": "FINR", + "participant_id": "D", + "url": "https://www.finra.org", + }, + { + "id": 5, + "type": "SIP", + "asset_class": "stocks", + "locale": "us", + "name": "Unlisted Trading Privileges", + "operating_mic": "XNAS", + "participant_id": "E", + "url": "https://www.utpplan.com", + }, + { + "id": 6, + "type": "TRF", + "asset_class": "stocks", + "locale": "us", + "name": "International Securities Exchange, LLC - Stocks", + "mic": "XISE", + "operating_mic": "XNAS", + "participant_id": "I", + "url": "https://nasdaq.com/solutions/nasdaq-ise", + }, + { + "id": 7, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Cboe EDGA", + "mic": "EDGA", + "operating_mic": "XCBO", + "participant_id": "J", + "url": "https://www.cboe.com/us/equities", + }, + { + "id": 8, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Cboe EDGX", + "mic": "EDGX", + "operating_mic": "XCBO", + "participant_id": "K", + "url": "https://www.cboe.com/us/equities", + }, + { + "id": 9, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "NYSE Chicago, Inc.", + "mic": "XCHI", + "operating_mic": "XNYS", + "participant_id": "M", + "url": "https://www.nyse.com/markets/nyse-chicago", + }, + { + "id": 10, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "New York Stock Exchange", + "mic": "XNYS", + "operating_mic": "XNYS", + "participant_id": "N", + "url": "https://www.nyse.com", + }, + { + "id": 11, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "NYSE Arca, Inc.", + "mic": "ARCX", + "operating_mic": "XNYS", + "participant_id": "P", + "url": "https://www.nyse.com/markets/nyse-arca", + }, + { + "id": 12, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Nasdaq", + "mic": "XNAS", + "operating_mic": "XNAS", + "participant_id": "T", + "url": "https://www.nasdaq.com", + }, + { + "id": 13, + "type": "SIP", + "asset_class": "stocks", + "locale": "us", + "name": "Consolidated Tape Association", + "operating_mic": "XNYS", + "participant_id": "S", + "url": "https://www.nyse.com/data/cta", + }, + { + "id": 14, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Long-Term Stock Exchange", + "mic": "LTSE", + "operating_mic": "LTSE", + "participant_id": "L", + "url": "https://www.ltse.com", + }, + { + "id": 15, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Investors Exchange", + "mic": "IEXG", + "operating_mic": "IEXG", + "participant_id": "V", + "url": "https://www.iextrading.com", + }, + { + "id": 16, + "type": "TRF", + "asset_class": "stocks", + "locale": "us", + "name": "Cboe Stock Exchange", + "mic": "CBSX", + "operating_mic": "XCBO", + "participant_id": "W", + "url": "https://www.cboe.com", + }, + { + "id": 17, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Nasdaq Philadelphia Exchange LLC", + "mic": "XPHL", + "operating_mic": "XNAS", + "participant_id": "X", + "url": "https://www.nasdaq.com/solutions/nasdaq-phlx", + }, + { + "id": 18, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Cboe BYX", + "mic": "BATY", + "operating_mic": "XCBO", + "participant_id": "Y", + "url": "https://www.cboe.com/us/equities", + }, + { + "id": 19, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Cboe BZX", + "mic": "BATS", + "operating_mic": "XCBO", + "participant_id": "Z", + "url": "https://www.cboe.com/us/equities", + }, + { + "id": 20, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "MIAX Pearl", + "mic": "EPRL", + "operating_mic": "MIHI", + "participant_id": "H", + "url": "https://www.miaxoptions.com/alerts/pearl-equities", + }, + { + "id": 21, + "type": "exchange", + "asset_class": "stocks", + "locale": "us", + "name": "Members Exchange", + "mic": "MEMX", + "operating_mic": "MEMX", + "participant_id": "U", + "url": "https://www.memx.com", + }, + { + "id": 62, + "type": "ORF", + "asset_class": "stocks", + "locale": "us", + "name": "OTC Equity Security", + "mic": "OOTC", + "operating_mic": "FINR", + "url": "https://www.finra.org/filing-reporting/over-the-counter-reporting-facility-orf", + }, +] + + +def map_exchanges(results): + to_map = ["ask_exchange", "bid_exchange"] + for result in results: + for map in to_map: + mapped_exchange_id = result.get(map) + for exchange in EXCHANGE_ID_MAP: + if exchange.get("id") == mapped_exchange_id: + result[map] = ( + exchange.get("name") + .replace(",", "") + .replace("Inc.", "") + .strip() + ) + break + + return results + + +def map_tape(results): + for result in results: + mapped_result = STOCK_TAPE_MAP[result["tape"]] + result["tape"] = mapped_result + return results diff --git a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml new file mode 100644 index 000000000000..9c0947f591d2 --- /dev/null +++ b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml @@ -0,0 +1,70 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + method: GET + uri: https://api.polygon.io/v3/quotes/SPY?apiKey=MOCK_API_KEY&limit=50 + response: + body: + string: !!binary | + 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 + nF5lv/8bAAD//7yrKwcaMwAA + headers: + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '1500' + Content-Type: + - application/json + Date: + - Fri, 27 Oct 2023 07:27:09 GMT + Link: + - ; + rel="next" + Server: + - nginx/1.19.2 + Strict-Transport-Security: + - max-age=15724800; includeSubDomains + Vary: + - Accept-Encoding + X-Item-Count: + - '50' + X-Request-Id: + - 4004e664bcb0e559d0b6920e1b8e88a3 + status: + code: 200 + message: OK +version: 1 diff --git a/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py b/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py index bc9d80711701..36d7061033a4 100644 --- a/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py +++ b/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py @@ -13,6 +13,7 @@ ) from openbb_polygon.models.stock_historical import PolygonStockHistoricalFetcher from openbb_polygon.models.stock_news import PolygonStockNewsFetcher +from openbb_polygon.models.stock_quote import PolygonStockQuoteFetcher test_credentials = UserService().default_user_settings.credentials.model_dump( mode="json" @@ -125,3 +126,12 @@ def test_polygon_forex_pairs_fetcher(credentials=test_credentials): fetcher = PolygonForexPairsFetcher() result = fetcher.test(params, credentials) assert result is None + + +@pytest.mark.record_http +def test_polygon_stock_quote_fetcher(credentials=test_credentials): + params = {"symbol": "SPY"} + + fetcher = PolygonStockQuoteFetcher() + result = fetcher.test(params, credentials) + assert result is None From af78a08f1a646e74b036a0a800c61ad5614a43bc Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Fri, 27 Oct 2023 09:58:02 -0700 Subject: [PATCH 03/21] limit cleanup --- .../polygon/openbb_polygon/models/stock_quote.py | 10 +++++----- .../providers/polygon/openbb_polygon/utils/helpers.py | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py index 120e5693acae..c5f6c97a2af6 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py @@ -146,9 +146,9 @@ def extract_data( query.symbol = symbols[0] records = 0 # Internal hard limit to prevent system overloads. - max = 1000000 - if query.limit is None or query.limit >= 50000: - max = query.limit if query.limit and query.limit < max else max + max = 10000000 + if query.timestamp or query.limit >= 50000: + max = query.limit if query.limit != 50 and query.limit < max else max query.limit = 50000 results = [] base_url = f"https://api.polygon.io/v3/quotes/{symbols[0]}" @@ -159,13 +159,13 @@ def extract_data( results = map_exchanges(results) results = map_tape(results) records += len(results) - if records == 50000 and "next_url" in data: + if query.timestamp and records == 50000 and "next_url" in data: while records < max and "next_url" in data and data["next_url"]: new_data = get_data_one(f"{data['next_url']}&apiKey={api_key}") records += len(new_data["results"]) + data = new_data new_data = map_tape(new_data["results"]) results.extend(map_exchanges(new_data)) - data = new_data return results diff --git a/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py b/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py index 8f0d6f075c7d..806d9c51dcc4 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py +++ b/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py @@ -436,6 +436,7 @@ def map_exchanges(results): def map_tape(results): for result in results: - mapped_result = STOCK_TAPE_MAP[result["tape"]] - result["tape"] = mapped_result + if "tape" in result: + mapped_result = STOCK_TAPE_MAP[result["tape"]] + result["tape"] = mapped_result return results From 72f458964d187f28da4d84c5b41ffab690dd1394 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:47:18 -0700 Subject: [PATCH 04/21] add greater/less than to params --- .../openbb_polygon/models/stock_quote.py | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py index c5f6c97a2af6..3848d9caed51 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py @@ -20,20 +20,53 @@ class PolygonStockQuoteQueryParams(StockQuoteQueryParams): description=QUERY_DESCRIPTIONS.get("symbol", "") + " If a list is supplied, only the first symbol will be processed." ) - limit: Optional[int] = Field( - default=50, + default=25, description=( QUERY_DESCRIPTIONS.get("interval", "") - + " Up to one million records will be returned. Pagination occurs in groups of 50,000." + + " Up to ten million records will be returned. Pagination occurs in groups of 50,000." + " Remaining limit values will always return 50,000 more records unless it is the last page." + " High volume tickers will require multiple max requests for a single day's NBBO records." - + " Expect stocks, like SPY, to require 5-10M rows, approaching 1GB in size, per day." + + " Expect stocks, like SPY, to approach 1GB in size, per day, as a raw CSV." + + " Splitting large requests into chunks is recommended for full-day requests of high-volume symbols." ), ) - timestamp: Optional[Union[str, int]] = Field( + timestamp: Optional[Union[datetime, str]] = Field( + default=None, + description=""" + Query by datetime. Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, + YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. + """, + ) + timestamp_lt: Optional[Union[datetime, str]] = Field( + default=None, + description=""" + Query by datetime, less than. Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, + YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. + """, + ) + timestamp_gt: Optional[Union[datetime, str]] = Field( + default=None, + description=""" + Query by datetime, greater than. Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, + YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. + """, + ) + timestamp_lte: Optional[Union[datetime, str]] = Field( + default=None, + description=""" + Query by datetime, less than or equal to. + Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, + YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. + """, + ) + timestamp_gte: Optional[Union[datetime, str]] = Field( default=None, - description="Query by timestamp. Either a date with the format YYYY-MM-DD or a nanosecond timestamp.", + description=""" + Query by datetime, greater than or equal to. + Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, + YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. + """, ) @@ -145,6 +178,7 @@ def extract_data( symbols = query.symbol.split(",") if "," in query.symbol else [query.symbol] query.symbol = symbols[0] records = 0 + # Internal hard limit to prevent system overloads. max = 10000000 if query.timestamp or query.limit >= 50000: @@ -152,7 +186,9 @@ def extract_data( query.limit = 50000 results = [] base_url = f"https://api.polygon.io/v3/quotes/{symbols[0]}" - query_str = get_querystring(query.model_dump(by_alias=True), ["symbol"]) + query_str = get_querystring( + query.model_dump(by_alias=True), ["symbol"] + ).replace("_", ".") url = f"{base_url}?{query_str}&apiKey={api_key}" data = get_data_one(url, **kwargs) results = data["results"] From 42fc75f1f11f82a85cab24c262a97932b0459fd2 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:12:27 -0700 Subject: [PATCH 05/21] limit param --- .../providers/polygon/openbb_polygon/models/stock_quote.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py index 3848d9caed51..6a26bc38b949 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py @@ -182,7 +182,7 @@ def extract_data( # Internal hard limit to prevent system overloads. max = 10000000 if query.timestamp or query.limit >= 50000: - max = query.limit if query.limit != 50 and query.limit < max else max + max = query.limit if query.limit != 25 and query.limit < max else max query.limit = 50000 results = [] base_url = f"https://api.polygon.io/v3/quotes/{symbols[0]}" @@ -195,8 +195,8 @@ def extract_data( results = map_exchanges(results) results = map_tape(results) records += len(results) - if query.timestamp and records == 50000 and "next_url" in data: - while records < max and "next_url" in data and data["next_url"]: + if records == 50000 and "next_url" in data: + while records < max and data["next_url"]: new_data = get_data_one(f"{data['next_url']}&apiKey={api_key}") records += len(new_data["results"]) data = new_data From 1880dbd497ac95ed15078105aaab41cd625a3b12 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:52:45 -0700 Subject: [PATCH 06/21] black --- .../openbb_polygon/models/stock_quote.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py index 6a26bc38b949..3656fd2aa9e1 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py @@ -181,7 +181,14 @@ def extract_data( # Internal hard limit to prevent system overloads. max = 10000000 - if query.timestamp or query.limit >= 50000: + if ( + query.timestamp + or query.timestamp_gt + or query.timestamp_gte + or query.timestamp_lt + or query.timestamp_lte + or query.limit >= 50000 + ): max = query.limit if query.limit != 25 and query.limit < max else max query.limit = 50000 results = [] @@ -195,8 +202,14 @@ def extract_data( results = map_exchanges(results) results = map_tape(results) records += len(results) - if records == 50000 and "next_url" in data: - while records < max and data["next_url"]: + if ( + query.timestamp + or query.timestamp_gt + or query.timestamp_gte + and records == 50000 + and "next_url" in data + ): + while records < max and "next_url" in data and data["next_url"]: new_data = get_data_one(f"{data['next_url']}&apiKey={api_key}") records += len(new_data["results"]) data = new_data From 844646d230f78144eca58459cf566f906dfe31e5 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:20:59 -0700 Subject: [PATCH 07/21] tests --- .../stocks/integration/test_stocks_api.py | 18 +- .../stocks/integration/test_stocks_python.py | 18 +- .../test_polygon_stock_quote_fetcher.yaml | 420 ++++++++++++++++-- .../polygon/tests/test_polygon_fetchers.py | 2 +- 4 files changed, 421 insertions(+), 37 deletions(-) diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_api.py b/openbb_platform/extensions/stocks/integration/test_stocks_api.py index dee9fdfb1654..fe0680a42c03 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_api.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_api.py @@ -920,7 +920,23 @@ def test_stocks_search(params, headers): [ ({"source": "iex", "provider": "intrinio", "symbol": "AAPL"}), ({"symbol": "AAPL", "provider": "fmp"}), - ({"symbol": "AAPL", "provider": "polygon"}), + ( + { + "symbol": "CLOV", + "provider": "polygon", + "timestamp": "2023-10-26", + "limit": 1000, + } + ), + ( + { + "symbol": "CLOV", + "provider": "polygon", + "timestamp_gt": "2023-10-26T15:20:00.000000000-04:00", + "timestamp_lt": "2023-10-26T15:30:00.000000000-04:00", + "limit": 5000, + } + ), ], ) @pytest.mark.integration diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_python.py b/openbb_platform/extensions/stocks/integration/test_stocks_python.py index fd3cf827f006..d3d3276120f7 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_python.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_python.py @@ -873,7 +873,23 @@ def test_stocks_search(params, obb): ({"symbol": "AAPL"}), ({"source": "iex", "provider": "intrinio", "symbol": "AAPL"}), ({"symbol": "AAPL", "provider": "fmp"}), - ({"symbol": "AAPL", "provider": "polygon"}), + ( + { + "symbol": "CLOV", + "timestamp": "2023-10-26", + "provider": "polygon", + "limit": 1000, + } + ), + ( + { + "symbol": "CLOV", + "provider": "polygon", + "timestamp_gt": "2023-10-26T15:20:00.000000000-04:00", + "timestamp_lt": "2023-10-26T15:30:00.000000000-04:00", + "limit": 5000, + } + ), ], ) @pytest.mark.integration diff --git a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml index 9c0947f591d2..c207417cc43a 100644 --- a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml +++ b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml @@ -9,61 +9,413 @@ interactions: Connection: - keep-alive method: GET - uri: https://api.polygon.io/v3/quotes/SPY?apiKey=MOCK_API_KEY&limit=50 + uri: https://api.polygon.io/v3/quotes/SPY?apiKey=MOCK_API_KEY&limit=1000 response: body: string: !!binary | - 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 - nF5lv/8bAAD//7yrKwcaMwAA + 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 + zoeaRNygP3r9jjz9YVEP1Vdu2gZvy4c8iIUOI2Gve/yQjCKzyQqkVvrhNlT8wZapnyiRP0j7AUEX + b7Uf6jKCFryDeD885BML8Cz1DzjJeW8IVy1A4wUCToaw+QLh0fv3tBNAKpyS9Uh53TpoVAtAKi4E + 0yXiF1EAAEMi2jAAdSIQMBSHTYM/wf9pN2CVAO0TSM38pBWfQvLQ/JhXSgB0Ud3mgFb8m77hG394 + OC/4tQtgvReMmr7hKz5sVB3ziQV4mgRwERNqKgSpaRq94cOOPwA3ngD6c/504AFApmzapFAzQmjD + 66hT1GUPgGRmAvcSsOmUt+JJM1slaCQB+VUv4E6/jQOS1gJwp6+9CTh0Abyaf68DNy0ibvL/22++ + /PjTtz/96ccvv/zyN//bv/qP//yf/bO/+pt/9e/+7Zdvvvxx+U/8+NPf/fDrL7/8wvwr0+++11// + +jvw78R/9d3332bir/g7sV//6j//5y/ffPnd9//w09/96Y+/+fLLL3//009/+PGXf/EX3/7hh1/8 + 4fe/+cf/8vvf/eKH3//F/81/8d/+9Pufvv/xL/7t3/zH//W7P/3xx9//8X/5j//hL/Ov//e/1H/9 + X/8d/vV//XfwL3/7z//73/zb//Tjt/8e//Drv4J/+Ou/+sv/51/+Nv/xP/2L/+Mf/+bf/5vffEd/ + /dvvKP/x13+F8O2/x998x//m73/1H/7yt7/+F7/56T/9h3/9/8kgMtyw3Ce3wiAqxKDS18WzyifE + tdwnxNM4zMWz3CvEz8kvJDnH18C10j/YxNQ/JLvSLyS03M8tX6kWEAAA//9OsOX62OIDAA== headers: Connection: - keep-alive Content-Encoding: - gzip - Content-Length: - - '1500' Content-Type: - application/json Date: - - Fri, 27 Oct 2023 07:27:09 GMT + - Fri, 27 Oct 2023 23:19:58 GMT Link: - - ; + - ; rel="next" Server: - nginx/1.19.2 Strict-Transport-Security: - max-age=15724800; includeSubDomains + Transfer-Encoding: + - chunked Vary: - Accept-Encoding X-Item-Count: - - '50' + - '1000' X-Request-Id: - - 4004e664bcb0e559d0b6920e1b8e88a3 + - 33b65ce5ddc07c47bcea991b3c46dbff status: code: 200 message: OK diff --git a/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py b/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py index 36d7061033a4..e161f4510f0d 100644 --- a/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py +++ b/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py @@ -130,7 +130,7 @@ def test_polygon_forex_pairs_fetcher(credentials=test_credentials): @pytest.mark.record_http def test_polygon_stock_quote_fetcher(credentials=test_credentials): - params = {"symbol": "SPY"} + params = {"symbol": "SPY", "limit": 1000} fetcher = PolygonStockQuoteFetcher() result = fetcher.test(params, credentials) From 6620f48f5980f7a5f487a83a61a9531efa254aba Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:39:54 -0700 Subject: [PATCH 08/21] fix tests --- .../extensions/stocks/integration/test_stocks_api.py | 9 ++++++++- .../extensions/stocks/integration/test_stocks_python.py | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_api.py b/openbb_platform/extensions/stocks/integration/test_stocks_api.py index fe0680a42c03..f8ad462cfffa 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_api.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_api.py @@ -923,9 +923,13 @@ def test_stocks_search(params, headers): ( { "symbol": "CLOV", - "provider": "polygon", "timestamp": "2023-10-26", + "provider": "polygon", "limit": 1000, + "timestamp_lte": None, + "timestamp_gte": None, + "timestamp_gt": None, + "timestamp_lt": None, } ), ( @@ -935,6 +939,9 @@ def test_stocks_search(params, headers): "timestamp_gt": "2023-10-26T15:20:00.000000000-04:00", "timestamp_lt": "2023-10-26T15:30:00.000000000-04:00", "limit": 5000, + "timestamp_gte": None, + "timestamp_lte": None, + "timestamp": None, } ), ], diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_python.py b/openbb_platform/extensions/stocks/integration/test_stocks_python.py index d3d3276120f7..7ca047463f14 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_python.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_python.py @@ -879,6 +879,10 @@ def test_stocks_search(params, obb): "timestamp": "2023-10-26", "provider": "polygon", "limit": 1000, + "timestamp_lte": None, + "timestamp_gte": None, + "timestamp_gt": None, + "timestamp_lt": None, } ), ( @@ -888,6 +892,9 @@ def test_stocks_search(params, obb): "timestamp_gt": "2023-10-26T15:20:00.000000000-04:00", "timestamp_lt": "2023-10-26T15:30:00.000000000-04:00", "limit": 5000, + "timestamp_gte": None, + "timestamp_lte": None, + "timestamp": None, } ), ], From 807f156aba769d345fd8c5313bc720cfddf49f8d Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:54:31 -0700 Subject: [PATCH 09/21] test_etf --- .../etf/integration/test_etf_api.py | 20 ++++++++++++++++--- .../etf/integration/test_etf_python.py | 20 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/openbb_platform/extensions/etf/integration/test_etf_api.py b/openbb_platform/extensions/etf/integration/test_etf_api.py index e07edd1835c1..846be558a1cc 100644 --- a/openbb_platform/extensions/etf/integration/test_etf_api.py +++ b/openbb_platform/extensions/etf/integration/test_etf_api.py @@ -20,7 +20,7 @@ def headers(): @pytest.mark.parametrize( "params", - [({})], + [({"query": None, "provider": "fmp"})], ) @pytest.mark.integration def test_etf_search(params, headers): @@ -36,8 +36,22 @@ def test_etf_search(params, headers): @pytest.mark.parametrize( "params", [ - ({"symbol": "IOO", "start_date": "2023-01-01", "end_date": "2023-06-06"}), - ({"symbol": "MISL", "start_date": "2023-01-01", "end_date": "2023-06-06"}), + ( + { + "symbol": "IOO", + "start_date": "2023-01-01", + "end_date": "2023-06-06", + "provider": "yfinance", + } + ), + ( + { + "symbol": "MISL", + "start_date": "2023-01-01", + "end_date": "2023-06-06", + "provider": "yfinance", + } + ), ], ) @pytest.mark.integration diff --git a/openbb_platform/extensions/etf/integration/test_etf_python.py b/openbb_platform/extensions/etf/integration/test_etf_python.py index fdc8769696b3..eed139e1bb76 100644 --- a/openbb_platform/extensions/etf/integration/test_etf_python.py +++ b/openbb_platform/extensions/etf/integration/test_etf_python.py @@ -19,7 +19,7 @@ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements @pytest.mark.parametrize( "params", [ - ({}), + ({"query": None, "provider": "fmp"}), ], ) @pytest.mark.integration @@ -35,8 +35,22 @@ def test_etf_search(params, obb): @pytest.mark.parametrize( "params", [ - ({"symbol": "IOO", "start_date": "2023-01-01", "end_date": "2023-06-06"}), - ({"symbol": "MISL", "start_date": "2023-01-01", "end_date": "2023-06-06"}), + ( + { + "symbol": "IOO", + "start_date": "2023-01-01", + "end_date": "2023-06-06", + "provider": "yfinance", + } + ), + ( + { + "symbol": "MISL", + "start_date": "2023-01-01", + "end_date": "2023-06-06", + "provider": "yfinance", + } + ), ], ) @pytest.mark.integration From 442f524dabceb160714868b1fb2bfeeb1679d9bb Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:36:13 -0700 Subject: [PATCH 10/21] rename model and function to NBBO --- .../stocks/integration/test_stocks_api.py | 64 ++++++++++++------- .../stocks/integration/test_stocks_python.py | 61 +++++++++++------- .../stocks/openbb_stocks/stocks_router.py | 11 ++++ .../polygon/openbb_polygon/__init__.py | 4 +- .../models/{stock_quote.py => stock_nbbo.py} | 24 +++---- ...l => test_polygon_stock_nbbo_fetcher.yaml} | 0 .../polygon/tests/test_polygon_fetchers.py | 6 +- 7 files changed, 105 insertions(+), 65 deletions(-) rename openbb_platform/providers/polygon/openbb_polygon/models/{stock_quote.py => stock_nbbo.py} (93%) rename openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/{test_polygon_stock_quote_fetcher.yaml => test_polygon_stock_nbbo_fetcher.yaml} (100%) diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_api.py b/openbb_platform/extensions/stocks/integration/test_stocks_api.py index 1b58f548bb71..53a41d93f301 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_api.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_api.py @@ -929,30 +929,6 @@ def test_stocks_search(params, headers): [ ({"source": "iex", "provider": "intrinio", "symbol": "AAPL"}), ({"symbol": "AAPL", "provider": "fmp"}), - ( - { - "symbol": "CLOV", - "timestamp": "2023-10-26", - "provider": "polygon", - "limit": 1000, - "timestamp_lte": None, - "timestamp_gte": None, - "timestamp_gt": None, - "timestamp_lt": None, - } - ), - ( - { - "symbol": "CLOV", - "provider": "polygon", - "timestamp_gt": "2023-10-26T15:20:00.000000000-04:00", - "timestamp_lt": "2023-10-26T15:30:00.000000000-04:00", - "limit": 5000, - "timestamp_gte": None, - "timestamp_lte": None, - "timestamp": None, - } - ), ], ) @pytest.mark.integration @@ -996,3 +972,43 @@ def test_stocks_ftd(params, headers): result = requests.get(url, headers=headers, timeout=10) assert isinstance(result, requests.Response) assert result.status_code == 200 + + +@pytest.mark.parametrize( + "params", + [ + ( + { + "symbol": "CLOV", + "timestamp": "2023-10-26", + "provider": "polygon", + "limit": 1000, + "timestamp_lte": None, + "timestamp_gte": None, + "timestamp_gt": None, + "timestamp_lt": None, + } + ), + ( + { + "symbol": "CLOV", + "provider": "polygon", + "timestamp_gt": "2023-10-26T15:20:00.000000000-04:00", + "timestamp_lt": "2023-10-26T15:30:00.000000000-04:00", + "limit": 5000, + "timestamp_gte": None, + "timestamp_lte": None, + "timestamp": None, + } + ), + ], +) +@pytest.mark.integration +def test_stocks_nbbo(params, headers): + params = {p: v for p, v in params.items() if v} + + query_str = get_querystring(params, []) + url = f"http://0.0.0.0:8000/api/v1/stocks/nbbo?{query_str}" + result = requests.get(url, headers=headers, timeout=10) + assert isinstance(result, requests.Response) + assert result.status_code == 200 diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_python.py b/openbb_platform/extensions/stocks/integration/test_stocks_python.py index d84cba7654fc..53520a10618b 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_python.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_python.py @@ -884,30 +884,6 @@ def test_stocks_search(params, obb): ({"symbol": "AAPL"}), ({"source": "iex", "provider": "intrinio", "symbol": "AAPL"}), ({"symbol": "AAPL", "provider": "fmp"}), - ( - { - "symbol": "CLOV", - "timestamp": "2023-10-26", - "provider": "polygon", - "limit": 1000, - "timestamp_lte": None, - "timestamp_gte": None, - "timestamp_gt": None, - "timestamp_lt": None, - } - ), - ( - { - "symbol": "CLOV", - "provider": "polygon", - "timestamp_gt": "2023-10-26T15:20:00.000000000-04:00", - "timestamp_lt": "2023-10-26T15:30:00.000000000-04:00", - "limit": 5000, - "timestamp_gte": None, - "timestamp_lte": None, - "timestamp": None, - } - ), ], ) @pytest.mark.integration @@ -943,3 +919,40 @@ def test_stocks_ftd(params, obb): assert result assert isinstance(result, OBBject) assert len(result.results) > 0 + + +@pytest.mark.parametrize( + "params", + [ + ( + { + "symbol": "CLOV", + "timestamp": "2023-10-26", + "provider": "polygon", + "limit": 1000, + "timestamp_lte": None, + "timestamp_gte": None, + "timestamp_gt": None, + "timestamp_lt": None, + } + ), + ( + { + "symbol": "CLOV", + "provider": "polygon", + "timestamp_gt": "2023-10-26T15:20:00.000000000-04:00", + "timestamp_lt": "2023-10-26T15:30:00.000000000-04:00", + "limit": 5000, + "timestamp_gte": None, + "timestamp_lte": None, + "timestamp": None, + } + ), + ], +) +@pytest.mark.integration +def test_stocks_nbbo(params, obb): + result = obb.stocks.nbbo(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 diff --git a/openbb_platform/extensions/stocks/openbb_stocks/stocks_router.py b/openbb_platform/extensions/stocks/openbb_stocks/stocks_router.py index 55eed3888af8..37d73a50f5f8 100644 --- a/openbb_platform/extensions/stocks/openbb_stocks/stocks_router.py +++ b/openbb_platform/extensions/stocks/openbb_stocks/stocks_router.py @@ -99,6 +99,17 @@ def info( return OBBject(results=Query(**locals()).execute()) +@router.command(model="StockNBBO") +def nbbo( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject[BaseModel]: + """Stock Quote. Load stock data for a specific ticker.""" + return OBBject(results=Query(**locals()).execute()) + + @router.command(model="StockFTD") def ftd( cc: CommandContext, diff --git a/openbb_platform/providers/polygon/openbb_polygon/__init__.py b/openbb_platform/providers/polygon/openbb_polygon/__init__.py index 232eb7ed498f..e126d0f9a40a 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/__init__.py +++ b/openbb_platform/providers/polygon/openbb_polygon/__init__.py @@ -9,8 +9,8 @@ PolygonMajorIndicesHistoricalFetcher, ) from openbb_polygon.models.stock_historical import PolygonStockHistoricalFetcher +from openbb_polygon.models.stock_nbbo import PolygonStockNBBOFetcher from openbb_polygon.models.stock_news import PolygonStockNewsFetcher -from openbb_polygon.models.stock_quote import PolygonStockQuoteFetcher from openbb_provider.abstract.provider import Provider polygon_provider = Provider( @@ -30,6 +30,6 @@ "MajorIndicesHistorical": PolygonMajorIndicesHistoricalFetcher, "ForexHistorical": PolygonForexHistoricalFetcher, "ForexPairs": PolygonForexPairsFetcher, - "StockQuote": PolygonStockQuoteFetcher, + "StockNBBO": PolygonStockNBBOFetcher, }, ) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py similarity index 93% rename from openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py rename to openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py index 3656fd2aa9e1..9b02ebe5d087 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py @@ -1,4 +1,4 @@ -"""Polygon Stock Quotes Model.""" +"""Polygon Stock NBBO Model.""" from datetime import datetime from typing import Any, Dict, List, Optional, Union @@ -13,8 +13,8 @@ from pydantic import Field, field_validator -class PolygonStockQuoteQueryParams(StockQuoteQueryParams): - """Polygon Stock Quote query params.""" +class PolygonStockNBBOQueryParams(StockQuoteQueryParams): + """Polygon Stock NBBO query params.""" symbol: str = Field( description=QUERY_DESCRIPTIONS.get("symbol", "") @@ -70,8 +70,8 @@ class PolygonStockQuoteQueryParams(StockQuoteQueryParams): ) -class PolygonStockQuoteData(Data): - """Polygon Stock Quote data.""" +class PolygonStockNBBOData(Data): + """Polygon Stock NBBO data.""" ask_exchange: Optional[Union[int, str]] = Field( default=None, @@ -157,17 +157,17 @@ def date_validate(cls, v): # pylint: disable=E0213 ) -class PolygonStockQuoteFetcher( - Fetcher[PolygonStockQuoteQueryParams, List[PolygonStockQuoteData]] +class PolygonStockNBBOFetcher( + Fetcher[PolygonStockNBBOQueryParams, List[PolygonStockNBBOData]] ): @staticmethod - def transform_query(params: Dict[str, Any]) -> PolygonStockQuoteQueryParams: + def transform_query(params: Dict[str, Any]) -> PolygonStockNBBOQueryParams: """Transform the query parameters.""" - return PolygonStockQuoteQueryParams(**params) + return PolygonStockNBBOQueryParams(**params) @staticmethod def extract_data( - query: PolygonStockQuoteQueryParams, + query: PolygonStockNBBOQueryParams, credentials: Optional[Dict[str, str]], **kwargs: Any, ) -> List[Dict]: @@ -222,6 +222,6 @@ def extract_data( def transform_data( data: List[Dict], **kwargs: Any, - ) -> List[PolygonStockQuoteData]: + ) -> List[PolygonStockNBBOData]: """Transform the data.""" - return [PolygonStockQuoteData.model_validate(d) for d in data] + return [PolygonStockNBBOData.model_validate(d) for d in data] diff --git a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_nbbo_fetcher.yaml similarity index 100% rename from openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_quote_fetcher.yaml rename to openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_nbbo_fetcher.yaml diff --git a/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py b/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py index e161f4510f0d..31f221f12bcb 100644 --- a/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py +++ b/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py @@ -12,8 +12,8 @@ PolygonMajorIndicesHistoricalFetcher, ) from openbb_polygon.models.stock_historical import PolygonStockHistoricalFetcher +from openbb_polygon.models.stock_nbbo import PolygonStockNBBOFetcher from openbb_polygon.models.stock_news import PolygonStockNewsFetcher -from openbb_polygon.models.stock_quote import PolygonStockQuoteFetcher test_credentials = UserService().default_user_settings.credentials.model_dump( mode="json" @@ -129,9 +129,9 @@ def test_polygon_forex_pairs_fetcher(credentials=test_credentials): @pytest.mark.record_http -def test_polygon_stock_quote_fetcher(credentials=test_credentials): +def test_polygon_stock_nbbo_fetcher(credentials=test_credentials): params = {"symbol": "SPY", "limit": 1000} - fetcher = PolygonStockQuoteFetcher() + fetcher = PolygonStockNBBOFetcher() result = fetcher.test(params, credentials) assert result is None From 04d99b1669290243ffdd3df2e81286315e07e88d Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:30:44 -0700 Subject: [PATCH 11/21] add standard model for nbbo --- .../standard_models/stock_nbbo.py | 29 +++++++++++++++++++ .../openbb_polygon/models/stock_nbbo.py | 16 +++++----- 2 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py diff --git a/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py b/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py new file mode 100644 index 000000000000..d7c9d2186b93 --- /dev/null +++ b/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py @@ -0,0 +1,29 @@ +"""Stock NBBO data model.""" + +from typing import List, Set, Union + +from pydantic import Field, field_validator + +from openbb_provider.abstract.data import Data +from openbb_provider.abstract.query_params import QueryParams +from openbb_provider.utils.descriptions import QUERY_DESCRIPTIONS + + +class StockNBBOQueryParams(QueryParams): + """Stock NBBO query model.""" + + symbol: str = Field( + description=QUERY_DESCRIPTIONS.get("symbol", "") + + " This function does not accept multiple symbols.", + ) + + @field_validator("symbol", mode="before", check_fields=False) + def upper_symbol(cls, v: Union[str, List[str], Set[str]]): + """Convert symbol to uppercase.""" + if isinstance(v, str): + return v.upper() + return ",".join([symbol.upper() for symbol in list(v)]) + + +class StockNBBOData(Data): + """Stock NBBO data.""" diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py index 9b02ebe5d087..204412808f47 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py @@ -4,26 +4,24 @@ from typing import Any, Dict, List, Optional, Union from openbb_polygon.utils.helpers import get_data_one, map_exchanges, map_tape -from openbb_provider.abstract.data import Data from openbb_provider.abstract.fetcher import Fetcher -from openbb_provider.standard_models.stock_quote import StockQuoteQueryParams +from openbb_provider.standard_models.stock_nbbo import ( + StockNBBOData, + StockNBBOQueryParams, +) from openbb_provider.utils.descriptions import QUERY_DESCRIPTIONS from openbb_provider.utils.helpers import get_querystring from pandas import to_datetime from pydantic import Field, field_validator -class PolygonStockNBBOQueryParams(StockQuoteQueryParams): +class PolygonStockNBBOQueryParams(StockNBBOQueryParams): """Polygon Stock NBBO query params.""" - symbol: str = Field( - description=QUERY_DESCRIPTIONS.get("symbol", "") - + " If a list is supplied, only the first symbol will be processed." - ) limit: Optional[int] = Field( default=25, description=( - QUERY_DESCRIPTIONS.get("interval", "") + QUERY_DESCRIPTIONS.get("limit", "") + " Up to ten million records will be returned. Pagination occurs in groups of 50,000." + " Remaining limit values will always return 50,000 more records unless it is the last page." + " High volume tickers will require multiple max requests for a single day's NBBO records." @@ -70,7 +68,7 @@ class PolygonStockNBBOQueryParams(StockQuoteQueryParams): ) -class PolygonStockNBBOData(Data): +class PolygonStockNBBOData(StockNBBOData): """Polygon Stock NBBO data.""" ask_exchange: Optional[Union[int, str]] = Field( From 57eee4ccf8554e62d36441a30c28a82f6407eefb Mon Sep 17 00:00:00 2001 From: hjoaquim Date: Tue, 7 Nov 2023 18:50:31 +0000 Subject: [PATCH 12/21] improve standardization --- .../standard_models/stock_nbbo.py | 24 +++++++ .../openbb_polygon/models/stock_nbbo.py | 68 +++++++------------ 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py b/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py index d7c9d2186b93..fc1e1658ca7c 100644 --- a/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py +++ b/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py @@ -18,6 +18,7 @@ class StockNBBOQueryParams(QueryParams): ) @field_validator("symbol", mode="before", check_fields=False) + @classmethod def upper_symbol(cls, v: Union[str, List[str], Set[str]]): """Convert symbol to uppercase.""" if isinstance(v, str): @@ -27,3 +28,26 @@ def upper_symbol(cls, v: Union[str, List[str], Set[str]]): class StockNBBOData(Data): """Stock NBBO data.""" + + ask_exchange: str = Field( + description="The exchange ID for the ask.", + ) + ask: float = Field( + description="The last ask price.", + ) + ask_size: int = Field( + description=""" + The ask size. This represents the number of round lot orders at the given ask price. + The normal round lot size is 100 shares. + An ask size of 2 means there are 200 shares available to purchase at the given ask price. + """, + ) + bid_size: int = Field( + description="The bid size in round lots.", + ) + bid: float = Field( + description="The last bid price.", + ) + bid_exchange: str = Field( + description="The exchange ID for the bid.", + ) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py index 204412808f47..ac25a097836c 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py @@ -1,6 +1,6 @@ """Polygon Stock NBBO Model.""" -from datetime import datetime +from datetime import timezone, datetime, timedelta from typing import Any, Dict, List, Optional, Union from openbb_polygon.utils.helpers import get_data_one, map_exchanges, map_tape @@ -11,12 +11,14 @@ ) from openbb_provider.utils.descriptions import QUERY_DESCRIPTIONS from openbb_provider.utils.helpers import get_querystring -from pandas import to_datetime from pydantic import Field, field_validator class PolygonStockNBBOQueryParams(StockNBBOQueryParams): - """Polygon Stock NBBO query params.""" + """Polygon Stock NBBO query params. + + Source: https://polygon.io/docs/stocks/get_v3_quotes__stockticker + """ limit: Optional[int] = Field( default=25, @@ -29,28 +31,28 @@ class PolygonStockNBBOQueryParams(StockNBBOQueryParams): + " Splitting large requests into chunks is recommended for full-day requests of high-volume symbols." ), ) - timestamp: Optional[Union[datetime, str]] = Field( + timestamp: Optional[datetime] = Field( default=None, description=""" Query by datetime. Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. """, ) - timestamp_lt: Optional[Union[datetime, str]] = Field( + timestamp_lt: Optional[datetime] = Field( default=None, description=""" Query by datetime, less than. Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. """, ) - timestamp_gt: Optional[Union[datetime, str]] = Field( + timestamp_gt: Optional[datetime] = Field( default=None, description=""" Query by datetime, greater than. Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. """, ) - timestamp_lte: Optional[Union[datetime, str]] = Field( + timestamp_lte: Optional[datetime] = Field( default=None, description=""" Query by datetime, less than or equal to. @@ -58,7 +60,7 @@ class PolygonStockNBBOQueryParams(StockNBBOQueryParams): YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. """, ) - timestamp_gte: Optional[Union[datetime, str]] = Field( + timestamp_gte: Optional[datetime] = Field( default=None, description=""" Query by datetime, greater than or equal to. @@ -71,35 +73,9 @@ class PolygonStockNBBOQueryParams(StockNBBOQueryParams): class PolygonStockNBBOData(StockNBBOData): """Polygon Stock NBBO data.""" - ask_exchange: Optional[Union[int, str]] = Field( - default=None, - description="The exchange ID for the ask. https://polygon.io/docs/stocks/get_v3_reference_exchanges", - alias="ask_exchange", - ) - ask: Optional[float] = Field( - default=None, description="The last ask price.", alias="ask_price" - ) - ask_size: Optional[int] = Field( - default=None, - description=""" - The ask size. This represents the number of round lot orders at the given ask price. - The normal round lot size is 100 shares. - An ask size of 2 means there are 200 shares available to purchase at the given ask price. - """, - alias="ask_size", - ) - bid_size: Optional[int] = Field( - default=None, description="The bid size in round lots.", alias="bid_size" - ) - bid: Optional[float] = Field( - default=None, description="The last bid price.", alias="bid_price" - ) - bid_exchange: Optional[Union[int, str]] = Field( - default=None, - description="The exchange ID for the bid. https://polygon.io/docs/stocks/get_v3_reference_exchanges", - alias="bid_exchange", - ) - tape: Optional[Union[int, str]] = Field( + __alias_dict__ = {"ask": "ask_price", "bid": "bid_price"} + + tape: Optional[str] = Field( default=None, description="The exchange tape.", alias="tape_integer" ) conditions: Optional[Union[str, List[int], List[str]]] = Field( @@ -117,21 +93,21 @@ class PolygonStockNBBOData(StockNBBOData): """, alias="sequence_number", ) - participant_timestamp: Optional[Union[int, datetime]] = Field( + participant_timestamp: Optional[datetime] = Field( default=None, description=""" The nanosecond accuracy Participant/Exchange Unix Timestamp. This is the timestamp of when the quote was actually generated at the exchange. """, ) - sip_timestamp: Optional[Union[int, datetime]] = Field( + sip_timestamp: Optional[datetime] = Field( default=None, description=""" The nanosecond accuracy SIP Unix Timestamp. This is the timestamp of when the SIP received this quote from the exchange which produced it. """, ) - trf_timestamp: Optional[Union[int, datetime]] = Field( + trf_timestamp: Optional[datetime] = Field( default=None, description=""" The nanosecond accuracy TRF (Trade Reporting Facility) Unix Timestamp. @@ -146,13 +122,15 @@ class PolygonStockNBBOData(StockNBBOData): mode="before", check_fields=False, ) + @classmethod def date_validate(cls, v): # pylint: disable=E0213 """Return formatted datetime.""" - return ( - to_datetime(v, unit="ns", origin="unix", utc=True).tz_convert("US/Eastern") - if v - else None - ) + + if v: + dlt = timedelta(hours=-5) + tz = timezone(dlt) + return datetime.fromtimestamp(v / 1000000000).astimezone(tz) + return None class PolygonStockNBBOFetcher( From 8ea3c9041e8dc82c1c5b05ee49de0d6e140e0863 Mon Sep 17 00:00:00 2001 From: hjoaquim Date: Tue, 7 Nov 2023 18:55:00 +0000 Subject: [PATCH 13/21] not redifining builtin max --- .../providers/polygon/openbb_polygon/models/stock_nbbo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py index ac25a097836c..ae6bc37017a2 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py @@ -156,7 +156,7 @@ def extract_data( records = 0 # Internal hard limit to prevent system overloads. - max = 10000000 + max_ = 10000000 if ( query.timestamp or query.timestamp_gt @@ -165,7 +165,7 @@ def extract_data( or query.timestamp_lte or query.limit >= 50000 ): - max = query.limit if query.limit != 25 and query.limit < max else max + max_ = query.limit if query.limit != 25 and query.limit < max_ else max_ query.limit = 50000 results = [] base_url = f"https://api.polygon.io/v3/quotes/{symbols[0]}" @@ -185,7 +185,7 @@ def extract_data( and records == 50000 and "next_url" in data ): - while records < max and "next_url" in data and data["next_url"]: + while records < max_ and "next_url" in data and data["next_url"]: new_data = get_data_one(f"{data['next_url']}&apiKey={api_key}") records += len(new_data["results"]) data = new_data From 1a05744904f8c2ec9da5473af3fc2246a13687d9 Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:46:48 -0800 Subject: [PATCH 14/21] fix input params --- .../openbb_polygon/models/stock_nbbo.py | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py index ae6bc37017a2..b7993601c0e9 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py @@ -1,6 +1,11 @@ """Polygon Stock NBBO Model.""" -from datetime import timezone, datetime, timedelta +from datetime import ( + date as dateType, + datetime, + timedelta, + timezone, +) from typing import Any, Dict, List, Optional, Union from openbb_polygon.utils.helpers import get_data_one, map_exchanges, map_tape @@ -31,28 +36,28 @@ class PolygonStockNBBOQueryParams(StockNBBOQueryParams): + " Splitting large requests into chunks is recommended for full-day requests of high-volume symbols." ), ) - timestamp: Optional[datetime] = Field( + date: Optional[dateType] = Field( default=None, - description=""" - Query by datetime. Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, - YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. - """, + description=( + QUERY_DESCRIPTIONS.get("date", "") + + " Use bracketed the timestamp parameters to specify exact time ranges." + ), ) - timestamp_lt: Optional[datetime] = Field( + timestamp_lt: Optional[Union[datetime, str]] = Field( default=None, description=""" Query by datetime, less than. Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. """, ) - timestamp_gt: Optional[datetime] = Field( + timestamp_gt: Optional[Union[datetime, str]] = Field( default=None, description=""" Query by datetime, greater than. Either a date with the format YYYY-MM-DD or a TZ-aware timestamp string, YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. """, ) - timestamp_lte: Optional[datetime] = Field( + timestamp_lte: Optional[Union[datetime, str]] = Field( default=None, description=""" Query by datetime, less than or equal to. @@ -60,7 +65,7 @@ class PolygonStockNBBOQueryParams(StockNBBOQueryParams): YYYY-MM-DDTH:M:S.000000000-04:00". Include all nanoseconds and the 'T' between the day and hour. """, ) - timestamp_gte: Optional[datetime] = Field( + timestamp_gte: Optional[Union[datetime, str]] = Field( default=None, description=""" Query by datetime, greater than or equal to. @@ -156,22 +161,24 @@ def extract_data( records = 0 # Internal hard limit to prevent system overloads. - max_ = 10000000 + _max_ = 10000000 if ( - query.timestamp + query.date or query.timestamp_gt or query.timestamp_gte or query.timestamp_lt or query.timestamp_lte or query.limit >= 50000 ): - max_ = query.limit if query.limit != 25 and query.limit < max_ else max_ - query.limit = 50000 + max_ = query.limit if query.limit != 25 and query.limit < _max_ else _max_ + query.limit = 50000 if max_ >= 50000 else query.limit results = [] base_url = f"https://api.polygon.io/v3/quotes/{symbols[0]}" - query_str = get_querystring( - query.model_dump(by_alias=True), ["symbol"] - ).replace("_", ".") + query_str = ( + get_querystring(query.model_dump(by_alias=True), ["symbol"]) + .replace("_", ".") + .replace("date", "timestamp") + ) url = f"{base_url}?{query_str}&apiKey={api_key}" data = get_data_one(url, **kwargs) results = data["results"] @@ -179,7 +186,7 @@ def extract_data( results = map_tape(results) records += len(results) if ( - query.timestamp + query.date or query.timestamp_gt or query.timestamp_gte and records == 50000 From ea1da413eeeaa25caebd70d8c4e5feab60da1ebe Mon Sep 17 00:00:00 2001 From: Danglewood <85772166+deeleeramone@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:57:33 -0800 Subject: [PATCH 15/21] integration test params --- .../extensions/stocks/integration/test_stocks_api.py | 4 ++-- .../extensions/stocks/integration/test_stocks_python.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_api.py b/openbb_platform/extensions/stocks/integration/test_stocks_api.py index 9bb444ec08d4..9df5e8685d9d 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_api.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_api.py @@ -1212,7 +1212,7 @@ def test_stocks_disc_filings(params, headers): ( { "symbol": "CLOV", - "timestamp": "2023-10-26", + "date": "2023-10-26", "provider": "polygon", "limit": 1000, "timestamp_lte": None, @@ -1230,7 +1230,7 @@ def test_stocks_disc_filings(params, headers): "limit": 5000, "timestamp_gte": None, "timestamp_lte": None, - "timestamp": None, + "date": None, } ), ], diff --git a/openbb_platform/extensions/stocks/integration/test_stocks_python.py b/openbb_platform/extensions/stocks/integration/test_stocks_python.py index 228ea7908e5a..c165c4190e5f 100644 --- a/openbb_platform/extensions/stocks/integration/test_stocks_python.py +++ b/openbb_platform/extensions/stocks/integration/test_stocks_python.py @@ -1136,7 +1136,7 @@ def test_stocks_disc_filings(params, obb): ( { "symbol": "CLOV", - "timestamp": "2023-10-26", + "date": "2023-10-26", "provider": "polygon", "limit": 1000, "timestamp_lte": None, @@ -1154,7 +1154,7 @@ def test_stocks_disc_filings(params, obb): "limit": 5000, "timestamp_gte": None, "timestamp_lte": None, - "timestamp": None, + "date": None, } ), ], From 85912c3160ff31d5b9f14c65f63e3f296c129b3b Mon Sep 17 00:00:00 2001 From: hjoaquim Date: Wed, 8 Nov 2023 10:40:44 +0000 Subject: [PATCH 16/21] removing unused import Co-authored-by: Pratyush Shukla --- .../openbb_polygon/models/stock_nbbo.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py index b7993601c0e9..d9ed6f719777 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py @@ -3,8 +3,6 @@ from datetime import ( date as dateType, datetime, - timedelta, - timezone, ) from typing import Any, Dict, List, Optional, Union @@ -16,6 +14,7 @@ ) from openbb_provider.utils.descriptions import QUERY_DESCRIPTIONS from openbb_provider.utils.helpers import get_querystring +from pandas import to_datetime from pydantic import Field, field_validator @@ -131,11 +130,11 @@ class PolygonStockNBBOData(StockNBBOData): def date_validate(cls, v): # pylint: disable=E0213 """Return formatted datetime.""" - if v: - dlt = timedelta(hours=-5) - tz = timezone(dlt) - return datetime.fromtimestamp(v / 1000000000).astimezone(tz) - return None + return ( + to_datetime(v, unit="ns", origin="unix", utc=True).tz_convert("US/Eastern") + if v + else None + ) class PolygonStockNBBOFetcher( @@ -155,13 +154,15 @@ def extract_data( """Extract the data from the Polygon endpoint.""" api_key = credentials.get("polygon_api_key") if credentials else "" + + # NOTE: Make it support multiple symbols using multithreading # This is to ensure that a list of symbols is not processed, only the first item will be passed. symbols = query.symbol.split(",") if "," in query.symbol else [query.symbol] query.symbol = symbols[0] records = 0 # Internal hard limit to prevent system overloads. - _max_ = 10000000 + max_ = 10000000 if ( query.date or query.timestamp_gt @@ -170,8 +171,9 @@ def extract_data( or query.timestamp_lte or query.limit >= 50000 ): - max_ = query.limit if query.limit != 25 and query.limit < _max_ else _max_ + max_ = query.limit if query.limit != 25 and query.limit < max_ else max_ query.limit = 50000 if max_ >= 50000 else query.limit + results = [] base_url = f"https://api.polygon.io/v3/quotes/{symbols[0]}" query_str = ( From 3bca25c7b13fb87c0d2a9792198ba167738fff01 Mon Sep 17 00:00:00 2001 From: Pratyush Shukla Date: Wed, 8 Nov 2023 18:21:13 +0530 Subject: [PATCH 17/21] revamped code --- .../standard_models/stock_nbbo.py | 15 +- .../openbb_polygon/models/stock_nbbo.py | 111 +++--- .../polygon/openbb_polygon/utils/helpers.py | 327 +----------------- 3 files changed, 74 insertions(+), 379 deletions(-) diff --git a/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py b/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py index fc1e1658ca7c..ec3fdbbd12f6 100644 --- a/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py +++ b/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py @@ -1,8 +1,6 @@ """Stock NBBO data model.""" -from typing import List, Set, Union - -from pydantic import Field, field_validator +from pydantic import Field from openbb_provider.abstract.data import Data from openbb_provider.abstract.query_params import QueryParams @@ -13,18 +11,9 @@ class StockNBBOQueryParams(QueryParams): """Stock NBBO query model.""" symbol: str = Field( - description=QUERY_DESCRIPTIONS.get("symbol", "") - + " This function does not accept multiple symbols.", + description=QUERY_DESCRIPTIONS.get("symbol", ""), ) - @field_validator("symbol", mode="before", check_fields=False) - @classmethod - def upper_symbol(cls, v: Union[str, List[str], Set[str]]): - """Convert symbol to uppercase.""" - if isinstance(v, str): - return v.upper() - return ",".join([symbol.upper() for symbol in list(v)]) - class StockNBBOData(Data): """Stock NBBO data.""" diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py index d9ed6f719777..6fd7eca11601 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py @@ -6,7 +6,7 @@ ) from typing import Any, Dict, List, Optional, Union -from openbb_polygon.utils.helpers import get_data_one, map_exchanges, map_tape +from openbb_polygon.utils.helpers import get_data_one, map_tape from openbb_provider.abstract.fetcher import Fetcher from openbb_provider.standard_models.stock_nbbo import ( StockNBBOData, @@ -24,8 +24,12 @@ class PolygonStockNBBOQueryParams(StockNBBOQueryParams): Source: https://polygon.io/docs/stocks/get_v3_quotes__stockticker """ - limit: Optional[int] = Field( - default=25, + __alias_dict__ = { + "date": "timestamp", + } + + limit: int = Field( + default=50000, description=( QUERY_DESCRIPTIONS.get("limit", "") + " Up to ten million records will be returned. Pagination occurs in groups of 50,000." @@ -73,6 +77,12 @@ class PolygonStockNBBOQueryParams(StockNBBOQueryParams): """, ) + @field_validator("limit", mode="before", check_fields=False) + @classmethod + def capping_limit(cls, v): + """Caps the number of records to 10 million.""" + return 10000000 if v > 10000000 else v + class PolygonStockNBBOData(StockNBBOData): """Polygon Stock NBBO data.""" @@ -85,7 +95,7 @@ class PolygonStockNBBOData(StockNBBOData): conditions: Optional[Union[str, List[int], List[str]]] = Field( default=None, description="A list of condition codes.", alias="conditions" ) - indicators: Optional[Any] = Field( + indicators: Optional[List] = Field( default=None, description="A list of indicator codes.", alias="indicators" ) sequence_num: Optional[int] = Field( @@ -129,7 +139,6 @@ class PolygonStockNBBOData(StockNBBOData): @classmethod def date_validate(cls, v): # pylint: disable=E0213 """Return formatted datetime.""" - return ( to_datetime(v, unit="ns", origin="unix", utc=True).tz_convert("US/Eastern") if v @@ -140,6 +149,8 @@ def date_validate(cls, v): # pylint: disable=E0213 class PolygonStockNBBOFetcher( Fetcher[PolygonStockNBBOQueryParams, List[PolygonStockNBBOData]] ): + """Transform the query, extract and transform the data from the Polygon endpoints.""" + @staticmethod def transform_query(params: Dict[str, Any]) -> PolygonStockNBBOQueryParams: """Transform the query parameters.""" @@ -152,59 +163,57 @@ def extract_data( **kwargs: Any, ) -> List[Dict]: """Extract the data from the Polygon endpoint.""" - api_key = credentials.get("polygon_api_key") if credentials else "" + data: List[Dict] = [] + base_url = "https://api.polygon.io/v3" - # NOTE: Make it support multiple symbols using multithreading - # This is to ensure that a list of symbols is not processed, only the first item will be passed. - symbols = query.symbol.split(",") if "," in query.symbol else [query.symbol] - query.symbol = symbols[0] - records = 0 - - # Internal hard limit to prevent system overloads. - max_ = 10000000 - if ( - query.date - or query.timestamp_gt - or query.timestamp_gte - or query.timestamp_lt - or query.timestamp_lte - or query.limit >= 50000 - ): - max_ = query.limit if query.limit != 25 and query.limit < max_ else max_ - query.limit = 50000 if max_ >= 50000 else query.limit - - results = [] - base_url = f"https://api.polygon.io/v3/quotes/{symbols[0]}" + query_str = get_querystring( + query.model_dump(by_alias=True), ["symbol", "limit"] + ) query_str = ( - get_querystring(query.model_dump(by_alias=True), ["symbol"]) - .replace("_", ".") - .replace("date", "timestamp") + f"{query_str}&limit={query.limit}" + if query.limit <= 50000 + else f"{query_str}&limit=50000" ) - url = f"{base_url}?{query_str}&apiKey={api_key}" - data = get_data_one(url, **kwargs) - results = data["results"] - results = map_exchanges(results) - results = map_tape(results) - records += len(results) - if ( - query.date - or query.timestamp_gt - or query.timestamp_gte - and records == 50000 - and "next_url" in data - ): - while records < max_ and "next_url" in data and data["next_url"]: - new_data = get_data_one(f"{data['next_url']}&apiKey={api_key}") - records += len(new_data["results"]) - data = new_data - new_data = map_tape(new_data["results"]) - results.extend(map_exchanges(new_data)) - - return results + query_str = query_str.replace("_", ".") + + url = f"{base_url}/quotes/{query.symbol}?{query_str}&apiKey={api_key}" + response = get_data_one(url, **kwargs) + next_url = response.get("next_url", None) + data.extend(response["results"]) + records = len(data) + + while records < query.limit and next_url: + url = f"{next_url}&apiKey={api_key}" + response = get_data_one(url, **kwargs) + next_url = response.get("next_url", None) + data.extend(response["results"]) + records += len(data) + + exchanges_url = f"{base_url}/reference/exchanges?asset_class=stocks&locale=us&apiKey={api_key}" + exchanges = get_data_one(exchanges_url, **kwargs) + exchanges = exchanges["results"] + exchange_id_map = {e["id"]: e for e in exchanges} + + data = [ + { + **d, + "ask_exchange": exchange_id_map.get(d["ask_exchange"], {}).get( + "name", "" + ), + "bid_exchange": exchange_id_map.get(d["bid_exchange"], {}).get( + "name", "" + ), + "tape": map_tape(d.get("tape", "")), + } + for d in data + ] + + return data @staticmethod def transform_data( + query: PolygonStockNBBOQueryParams, data: List[Dict], **kwargs: Any, ) -> List[PolygonStockNBBOData]: diff --git a/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py b/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py index 806d9c51dcc4..92c5e4b5362f 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py +++ b/openbb_platform/providers/polygon/openbb_polygon/utils/helpers.py @@ -2,7 +2,7 @@ import json from io import StringIO -from typing import Any, List, Optional, TypeVar, Union +from typing import Any, List, Optional, Tuple, TypeVar, Union import requests from openbb_provider import helpers @@ -18,7 +18,6 @@ class BasicResponse: def __init__(self, response: StringIO): """Initialize the BasicResponse class.""" - # Find a way to get the status code self.status_code = 200 response.seek(0) @@ -26,13 +25,12 @@ def __init__(self, response: StringIO): def json(self) -> dict: """Return the response as a dictionary.""" - return json.loads(self.text) def request(url: str) -> BasicResponse: """ - Request function for PyScript. Pass in Method and make sure to await! + Request function for PyScript. Pass in Method and make sure to await. Parameters: ----------- @@ -53,7 +51,6 @@ def request(url: str) -> BasicResponse: def get_data(url: str, **kwargs: Any) -> Union[list, dict]: """Get data from Polygon endpoint.""" - try: r: Union[requests.Response, BasicResponse] = helpers.make_request(url, **kwargs) except SSLError: @@ -102,7 +99,6 @@ def get_data_many( def get_data_one(url: str, **kwargs: Any) -> dict: """Get data from Polygon endpoint and convert to schema.""" - data = get_data(url, **kwargs) if isinstance(data, list): if len(data) == 0: @@ -116,7 +112,7 @@ def get_data_one(url: str, **kwargs: Any) -> dict: return data -def get_date_condition(date: str) -> str: +def get_date_condition(date: str) -> Tuple: """Get the date condition for the querystring.""" date_conditions = { "<": "lt", @@ -132,311 +128,12 @@ def get_date_condition(date: str) -> str: return date, "eq" -STOCK_TAPE_MAP = { - 1: "Tape A: NYSE", - 2: "Tape B: NYSE ARCA", - 3: "Tape C: NASDAQ", -} - -EXCHANGE_ID_MAP = [ - { - "id": 1, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "NYSE American, LLC", - "acronym": "AMEX", - "mic": "XASE", - "operating_mic": "XNYS", - "participant_id": "A", - "url": "https://www.nyse.com/markets/nyse-american", - }, - { - "id": 2, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Nasdaq OMX BX, Inc.", - "mic": "XBOS", - "operating_mic": "XNAS", - "participant_id": "B", - "url": "https://www.nasdaq.com/solutions/nasdaq-bx-stock-market", - }, - { - "id": 3, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "NYSE National, Inc.", - "acronym": "NSX", - "mic": "XCIS", - "operating_mic": "XNYS", - "participant_id": "C", - "url": "https://www.nyse.com/markets/nyse-national", - }, - { - "id": 4, - "type": "TRF", - "asset_class": "stocks", - "locale": "us", - "name": "FINRA NYSE TRF", - "mic": "FINY", - "operating_mic": "XNYS", - "participant_id": "D", - "url": "https://www.finra.org", - }, - { - "id": 4, - "type": "TRF", - "asset_class": "stocks", - "locale": "us", - "name": "FINRA Nasdaq TRF Carteret", - "mic": "FINN", - "operating_mic": "FINR", - "participant_id": "D", - "url": "https://www.finra.org", - }, - { - "id": 4, - "type": "TRF", - "asset_class": "stocks", - "locale": "us", - "name": "FINRA Nasdaq TRF Chicago", - "mic": "FINC", - "operating_mic": "FINR", - "participant_id": "D", - "url": "https://www.finra.org", - }, - { - "id": 4, - "type": "TRF", - "asset_class": "stocks", - "locale": "us", - "name": "FINRA Alternative Display Facility", - "mic": "XADF", - "operating_mic": "FINR", - "participant_id": "D", - "url": "https://www.finra.org", - }, - { - "id": 5, - "type": "SIP", - "asset_class": "stocks", - "locale": "us", - "name": "Unlisted Trading Privileges", - "operating_mic": "XNAS", - "participant_id": "E", - "url": "https://www.utpplan.com", - }, - { - "id": 6, - "type": "TRF", - "asset_class": "stocks", - "locale": "us", - "name": "International Securities Exchange, LLC - Stocks", - "mic": "XISE", - "operating_mic": "XNAS", - "participant_id": "I", - "url": "https://nasdaq.com/solutions/nasdaq-ise", - }, - { - "id": 7, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Cboe EDGA", - "mic": "EDGA", - "operating_mic": "XCBO", - "participant_id": "J", - "url": "https://www.cboe.com/us/equities", - }, - { - "id": 8, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Cboe EDGX", - "mic": "EDGX", - "operating_mic": "XCBO", - "participant_id": "K", - "url": "https://www.cboe.com/us/equities", - }, - { - "id": 9, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "NYSE Chicago, Inc.", - "mic": "XCHI", - "operating_mic": "XNYS", - "participant_id": "M", - "url": "https://www.nyse.com/markets/nyse-chicago", - }, - { - "id": 10, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "New York Stock Exchange", - "mic": "XNYS", - "operating_mic": "XNYS", - "participant_id": "N", - "url": "https://www.nyse.com", - }, - { - "id": 11, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "NYSE Arca, Inc.", - "mic": "ARCX", - "operating_mic": "XNYS", - "participant_id": "P", - "url": "https://www.nyse.com/markets/nyse-arca", - }, - { - "id": 12, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Nasdaq", - "mic": "XNAS", - "operating_mic": "XNAS", - "participant_id": "T", - "url": "https://www.nasdaq.com", - }, - { - "id": 13, - "type": "SIP", - "asset_class": "stocks", - "locale": "us", - "name": "Consolidated Tape Association", - "operating_mic": "XNYS", - "participant_id": "S", - "url": "https://www.nyse.com/data/cta", - }, - { - "id": 14, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Long-Term Stock Exchange", - "mic": "LTSE", - "operating_mic": "LTSE", - "participant_id": "L", - "url": "https://www.ltse.com", - }, - { - "id": 15, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Investors Exchange", - "mic": "IEXG", - "operating_mic": "IEXG", - "participant_id": "V", - "url": "https://www.iextrading.com", - }, - { - "id": 16, - "type": "TRF", - "asset_class": "stocks", - "locale": "us", - "name": "Cboe Stock Exchange", - "mic": "CBSX", - "operating_mic": "XCBO", - "participant_id": "W", - "url": "https://www.cboe.com", - }, - { - "id": 17, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Nasdaq Philadelphia Exchange LLC", - "mic": "XPHL", - "operating_mic": "XNAS", - "participant_id": "X", - "url": "https://www.nasdaq.com/solutions/nasdaq-phlx", - }, - { - "id": 18, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Cboe BYX", - "mic": "BATY", - "operating_mic": "XCBO", - "participant_id": "Y", - "url": "https://www.cboe.com/us/equities", - }, - { - "id": 19, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Cboe BZX", - "mic": "BATS", - "operating_mic": "XCBO", - "participant_id": "Z", - "url": "https://www.cboe.com/us/equities", - }, - { - "id": 20, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "MIAX Pearl", - "mic": "EPRL", - "operating_mic": "MIHI", - "participant_id": "H", - "url": "https://www.miaxoptions.com/alerts/pearl-equities", - }, - { - "id": 21, - "type": "exchange", - "asset_class": "stocks", - "locale": "us", - "name": "Members Exchange", - "mic": "MEMX", - "operating_mic": "MEMX", - "participant_id": "U", - "url": "https://www.memx.com", - }, - { - "id": 62, - "type": "ORF", - "asset_class": "stocks", - "locale": "us", - "name": "OTC Equity Security", - "mic": "OOTC", - "operating_mic": "FINR", - "url": "https://www.finra.org/filing-reporting/over-the-counter-reporting-facility-orf", - }, -] - - -def map_exchanges(results): - to_map = ["ask_exchange", "bid_exchange"] - for result in results: - for map in to_map: - mapped_exchange_id = result.get(map) - for exchange in EXCHANGE_ID_MAP: - if exchange.get("id") == mapped_exchange_id: - result[map] = ( - exchange.get("name") - .replace(",", "") - .replace("Inc.", "") - .strip() - ) - break - - return results - - -def map_tape(results): - for result in results: - if "tape" in result: - mapped_result = STOCK_TAPE_MAP[result["tape"]] - result["tape"] = mapped_result - return results +def map_tape(tape: int) -> str: + """Map the tape to a string.""" + STOCK_TAPE_MAP = { + 1: "Tape A: NYSE", + 2: "Tape B: NYSE ARCA", + 3: "Tape C: NASDAQ", + } + + return STOCK_TAPE_MAP.get(tape, "") if tape else "" From 748a15ef3a0ab0c38ce8a12dc0f290cda383ff33 Mon Sep 17 00:00:00 2001 From: Pratyush Shukla Date: Wed, 8 Nov 2023 18:24:26 +0530 Subject: [PATCH 18/21] re-recorded nbbo test --- .../test_polygon_stock_nbbo_fetcher.yaml | 741 +++++++++--------- 1 file changed, 359 insertions(+), 382 deletions(-) diff --git a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_nbbo_fetcher.yaml b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_nbbo_fetcher.yaml index c207417cc43a..bd47d5c27d2d 100644 --- a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_nbbo_fetcher.yaml +++ b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_stock_nbbo_fetcher.yaml @@ -13,385 +13,306 @@ interactions: response: body: string: !!binary | - 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 + ozcDAA== headers: Connection: - keep-alive @@ -400,9 +321,9 @@ interactions: Content-Type: - application/json Date: - - Fri, 27 Oct 2023 23:19:58 GMT + - Wed, 08 Nov 2023 12:53:48 GMT Link: - - ; + - ; rel="next" Server: - nginx/1.19.2 @@ -415,7 +336,63 @@ interactions: X-Item-Count: - '1000' X-Request-Id: - - 33b65ce5ddc07c47bcea991b3c46dbff + - 1f255662347fcedcbd2154973eb2547f + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + method: GET + uri: https://api.polygon.io/v3/reference/exchanges?apiKey=MOCK_API_KEY&asset_class=stocks&locale=us + response: + body: + string: !!binary | + H4sIAAAAAAAA/8yXbW/qNhTHv4rl10lzoTBW3oUUbrPxJMLd4E5TZYwBq46d2k4Luup3n/LQhBbD + FoVJ913jkvj3Pz7+n3N+QElUzLSC3b9+QLqG3YYF9SEisAvJHu8Q3xJoQaQU0Y+YIaVgFyot8JOC + FmQCI5b8NE6eOAqTv8fLoA/ckEiKEbfAcOglH8BS8EMIu9Ad9RfQgiHFsAsXbtCHFhQRkUhTvn3M + l8fLAFowQlJTTCPE9WOCBl1owVgy2IU7rSPVdZzX19cbflDkBovQCZF8Ilo5yYKNcgL4ZmXCmrWF + IbVGz2AyWoDewgI+xzelkN4kMApxjUJ6ZiHpBqkUJVisqeDKyRbt1d5O6exMZKHq9irHNUbJZogV + qsrzGgdHx+X5ZpXm4/IqHBfPCQphrULYfDaoqGngj2cuSJVlL2ecA3+8rIB/b8TfUC7RjZDba5Jm + mTWfDYCHpCaS6GPosQE6efHngd5RjLbimNn7KZldpolMUu2FgHuqIoYOYIAwZVQfjkzpfvA/0LcL + +sCfVqT/xhlVmqzBXKI15VswlfSFMrIlqoLr9I2ksY4ihnhyJQvWX2pE2ud5jJPrDAKCY0k1JQr0 + c4NKawKwQfD+mZzaP1MLzGL8EzGX7JMqUojr1LVMbyUI6N9/dQv2/OGE3etNTOy/GQ8Cr0RmjLFy + yHOcBq2A/vVa0Itj6EUF6N+rQ99dpTjl7vK54noPfgUzH1WoRTi3s3cZjS+1dZBXsBTyKcv54iaU + YjLq/ypmfFFMyX2lXk5i9Cn47swzps4Z3mmVvk1iVCq4UtN2FGi3Sp82/5c+rQS9reHunuBKMLpG + qcOjiABXKYFp6qEVwhxcDvMaaeRgfRTdVt3oDgXf2nMiw3OJPZwbTT1fPlEwNCpg+nNit+uC+/yF + KC2kOkX2+4uvBuR8+QT5DyMyJXudleqP4HUKa+rgZ+Ls9YIqXv7nRS8vcWuXyrxJnO4oQ2vCoh1F + BXw+Guak04dhhYu5qDxARTu2L4Vdp5z2lmU17blz43Bx5gSW1atpo3Y5zaC/f4A22uEZ6O/VoZu1 + a+fIdxdgSpBkZecynZmSZeSnLcEJ9oMRO6RoL6I0R1J6xIjUyomSnexTHbVr6YiEK2IynFF/ZLq7 + +fKJmm9mNSTcf2zhy9I5qWw1k7kH+kkIDu8NfDkeTSbzC8PdhUnI2VBG+daWJBIyedMRL0Taekds + LOJkaij/ZW/yqcwWcgPf/rag0kjHCfkkaUQleY6JyiPSvsOtTbOx3rRWX9aos7nt3DUR7rTIl0a7 + 07hrQQumG8Bus/32TwAAAP//mm/+CfISAAA= + headers: + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '995' + Content-Type: + - application/json + Date: + - Wed, 08 Nov 2023 12:53:49 GMT + Server: + - nginx/1.19.2 + Strict-Transport-Security: + - max-age=15724800; includeSubDomains + Vary: + - Accept-Encoding + X-Item-Count: + - '25' + X-Request-Id: + - 59c4f21df4b0da7f3792ac74e0157194 status: code: 200 message: OK From a100a26f2b5305e232d8ee8624c6d620e437ca76 Mon Sep 17 00:00:00 2001 From: Pratyush Shukla Date: Wed, 8 Nov 2023 18:29:15 +0530 Subject: [PATCH 19/21] added symbol validator --- .../openbb_provider/standard_models/stock_nbbo.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py b/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py index ec3fdbbd12f6..c007f3c671e9 100644 --- a/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py +++ b/openbb_platform/platform/provider/openbb_provider/standard_models/stock_nbbo.py @@ -1,6 +1,6 @@ """Stock NBBO data model.""" -from pydantic import Field +from pydantic import Field, field_validator from openbb_provider.abstract.data import Data from openbb_provider.abstract.query_params import QueryParams @@ -14,6 +14,12 @@ class StockNBBOQueryParams(QueryParams): description=QUERY_DESCRIPTIONS.get("symbol", ""), ) + @field_validator("symbol", mode="before", check_fields=False) + @classmethod + def upper_symbol(cls, v: str): + """Convert symbol to uppercase.""" + return v.upper() + class StockNBBOData(Data): """Stock NBBO data.""" From 96a89fec024a93c7aa505782a4e408847602d471 Mon Sep 17 00:00:00 2001 From: Pratyush Shukla Date: Wed, 8 Nov 2023 18:41:23 +0530 Subject: [PATCH 20/21] added alias in field --- .../providers/polygon/openbb_polygon/models/stock_nbbo.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py index 6fd7eca11601..7826960cca64 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py +++ b/openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py @@ -24,10 +24,6 @@ class PolygonStockNBBOQueryParams(StockNBBOQueryParams): Source: https://polygon.io/docs/stocks/get_v3_quotes__stockticker """ - __alias_dict__ = { - "date": "timestamp", - } - limit: int = Field( default=50000, description=( @@ -45,6 +41,7 @@ class PolygonStockNBBOQueryParams(StockNBBOQueryParams): QUERY_DESCRIPTIONS.get("date", "") + " Use bracketed the timestamp parameters to specify exact time ranges." ), + alias="timestamp", ) timestamp_lt: Optional[Union[datetime, str]] = Field( default=None, From 991e9e969e17e8e5fa6ea7bee02ac4ae17b39d78 Mon Sep 17 00:00:00 2001 From: Theodore Aptekarev Date: Wed, 8 Nov 2023 16:00:04 +0100 Subject: [PATCH 21/21] Stock news -> Company news --- openbb_platform/providers/polygon/openbb_polygon/__init__.py | 1 - .../providers/polygon/tests/test_polygon_fetchers.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/openbb_platform/providers/polygon/openbb_polygon/__init__.py b/openbb_platform/providers/polygon/openbb_polygon/__init__.py index 84602ae9304a..a87fadff56ab 100644 --- a/openbb_platform/providers/polygon/openbb_polygon/__init__.py +++ b/openbb_platform/providers/polygon/openbb_polygon/__init__.py @@ -11,7 +11,6 @@ ) from openbb_polygon.models.stock_historical import PolygonStockHistoricalFetcher from openbb_polygon.models.stock_nbbo import PolygonStockNBBOFetcher -from openbb_polygon.models.stock_news import PolygonStockNewsFetcher from openbb_provider.abstract.provider import Provider polygon_provider = Provider( diff --git a/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py b/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py index 2ccd9208c887..cd445d9f8ad1 100644 --- a/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py +++ b/openbb_platform/providers/polygon/tests/test_polygon_fetchers.py @@ -14,8 +14,6 @@ ) from openbb_polygon.models.stock_historical import PolygonStockHistoricalFetcher from openbb_polygon.models.stock_nbbo import PolygonStockNBBOFetcher -from openbb_polygon.models.stock_news import PolygonStockNewsFetcher - test_credentials = UserService().default_user_settings.credentials.model_dump( mode="json"