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 all 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
40 changes: 40 additions & 0 deletions openbb_platform/extensions/stocks/integration/test_stocks_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,46 @@ def test_stocks_disc_filings(params, headers):
assert result.status_code == 200


@pytest.mark.parametrize(
"params",
[
(
{
"symbol": "CLOV",
"date": "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,
"date": 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


@pytest.mark.parametrize(
"params",
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,43 @@ def test_stocks_disc_filings(params, obb):
assert len(result.results) > 0


@pytest.mark.parametrize(
"params",
[
(
{
"symbol": "CLOV",
"date": "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,
"date": 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


@pytest.mark.parametrize(
"params",
[
Expand Down
11 changes: 11 additions & 0 deletions openbb_platform/extensions/stocks/openbb_stocks/stocks_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Stock NBBO data model."""

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", ""),
)

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

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.",
)
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 @@ -11,6 +11,7 @@
)
from openbb_polygon.models.market_snapshots import PolygonMarketSnapshotsFetcher
from openbb_polygon.models.stock_historical import PolygonStockHistoricalFetcher
from openbb_polygon.models.stock_nbbo import PolygonStockNBBOFetcher
from openbb_provider.abstract.provider import Provider

polygon_provider = Provider(
Expand All @@ -27,6 +28,7 @@
"CryptoHistorical": PolygonCryptoHistoricalFetcher,
"ForexHistorical": PolygonForexHistoricalFetcher,
"ForexPairs": PolygonForexPairsFetcher,
"StockNBBO": PolygonStockNBBOFetcher,
"IncomeStatement": PolygonIncomeStatementFetcher,
"MajorIndicesHistorical": PolygonMajorIndicesHistoricalFetcher,
"StockHistorical": PolygonStockHistoricalFetcher,
Expand Down
218 changes: 218 additions & 0 deletions openbb_platform/providers/polygon/openbb_polygon/models/stock_nbbo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"""Polygon Stock NBBO Model."""

from datetime import (
date as dateType,
datetime,
)
from typing import Any, Dict, List, Optional, Union

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,
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(StockNBBOQueryParams):
"""Polygon Stock NBBO query params.

Source: https://polygon.io/docs/stocks/get_v3_quotes__stockticker
"""

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."
+ " 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."
),
)
date: Optional[dateType] = Field(
default=None,
description=(
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,
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.
""",
)

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

__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(
default=None, description="A list of condition codes.", alias="conditions"
)
indicators: Optional[List] = 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[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[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[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,
)
@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
)


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."""
return PolygonStockNBBOQueryParams(**params)

@staticmethod
def extract_data(
query: PolygonStockNBBOQueryParams,
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 ""
data: List[Dict] = []
base_url = "https://api.polygon.io/v3"

query_str = get_querystring(
query.model_dump(by_alias=True), ["symbol", "limit"]
)
query_str = (
f"{query_str}&limit={query.limit}"
if query.limit <= 50000
else f"{query_str}&limit=50000"
)
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]:
"""Transform the data."""
return [PolygonStockNBBOData.model_validate(d) for d in data]
Loading
Loading