Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/polygon-stock-quote: Add NBBO Quotes, with historical, to obb.stocks.quote() #5617

Merged
merged 32 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
433970f
coerce string type, add default=None
deeleeramone Oct 12, 2023
d17b5a3
Merge branch 'feature/openbb-sdk-v4' of https://github.com/OpenBB-fin…
deeleeramone Oct 26, 2023
cef85f5
Merge branch 'feature/openbb-sdk-v4' of https://github.com/OpenBB-fin…
deeleeramone Oct 27, 2023
56a8c80
add polygon stock quote
deeleeramone Oct 27, 2023
af78a08
limit cleanup
deeleeramone Oct 27, 2023
e783f84
Merge branch 'feature/openbb-sdk-v4' of https://github.com/OpenBB-fin…
deeleeramone Oct 27, 2023
72f4589
add greater/less than to params
deeleeramone Oct 27, 2023
42fc75f
limit param
deeleeramone Oct 27, 2023
1880dbd
black
deeleeramone Oct 27, 2023
844646d
tests
deeleeramone Oct 27, 2023
6620f48
fix tests
deeleeramone Oct 27, 2023
807f156
test_etf
deeleeramone Oct 27, 2023
70a7f40
merge branch feature/openbb-sdk-v4
deeleeramone Oct 30, 2023
442f524
rename model and function to NBBO
deeleeramone Oct 30, 2023
90fae9f
merge branch feature/openbb-sdk-v4
deeleeramone Oct 30, 2023
04d99b1
add standard model for nbbo
deeleeramone Oct 30, 2023
20a02bc
merge branch feature/openbb-sdk-v4
deeleeramone Oct 31, 2023
f85dc97
merge branch feature/openbb-sdk-v4
deeleeramone Oct 31, 2023
e268aba
Merge branch 'feature/openbb-sdk-v4' into feature/polygon-stock-quote
hjoaquim Nov 7, 2023
57eee4c
improve standardization
hjoaquim Nov 7, 2023
8ea3c90
not redifining builtin max
hjoaquim Nov 7, 2023
1a05744
fix input params
deeleeramone Nov 8, 2023
ea1da41
integration test params
deeleeramone Nov 8, 2023
be17091
Merge branch 'feature/openbb-sdk-v4' of https://github.com/OpenBB-fin…
deeleeramone Nov 8, 2023
85912c3
removing unused import
hjoaquim Nov 8, 2023
3bca25c
revamped code
the-praxs Nov 8, 2023
748a15e
re-recorded nbbo test
the-praxs Nov 8, 2023
a100a26
added symbol validator
the-praxs Nov 8, 2023
96a89fe
added alias in field
the-praxs Nov 8, 2023
f38ad35
Merge branch 'feature/openbb-sdk-v4' into feature/polygon-stock-quote
piiq Nov 8, 2023
991e9e9
Stock news -> Company news
piiq Nov 8, 2023
8f28049
Merge branch 'feature/openbb-sdk-v4' into feature/polygon-stock-quote
piiq Nov 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions openbb_platform/extensions/etf/integration/test_etf_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def headers():

@pytest.mark.parametrize(
"params",
[({})],
[({"query": None, "provider": "fmp"})],
)
@pytest.mark.integration
def test_etf_search(params, headers):
Expand All @@ -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
Expand Down
20 changes: 17 additions & 3 deletions openbb_platform/extensions/etf/integration/test_etf_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
@pytest.mark.parametrize(
"params",
[
({}),
({"query": None, "provider": "fmp"}),
],
)
@pytest.mark.integration
Expand All @@ -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
Expand Down
24 changes: 24 additions & 0 deletions openbb_platform/extensions/stocks/integration/test_stocks_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,30 @@ 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,30 @@ 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
2 changes: 2 additions & 0 deletions openbb_platform/providers/polygon/openbb_polygon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -29,5 +30,6 @@
"MajorIndicesHistorical": PolygonMajorIndicesHistoricalFetcher,
"ForexHistorical": PolygonForexHistoricalFetcher,
"ForexPairs": PolygonForexPairsFetcher,
"StockQuote": PolygonStockQuoteFetcher,
},
)
227 changes: 227 additions & 0 deletions openbb_platform/providers/polygon/openbb_polygon/models/stock_quote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
"""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=25,
description=(
QUERY_DESCRIPTIONS.get("interval", "")
+ " 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 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[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 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.
""",
)


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 = 10000000
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 = []
base_url = f"https://api.polygon.io/v3/quotes/{symbols[0]}"
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"]
results = map_exchanges(results)
results = map_tape(results)
records += len(results)
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
new_data = map_tape(new_data["results"])
results.extend(map_exchanges(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]
Loading
Loading