summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-01-09 02:55:29 -0800
committerGitHub <noreply@github.com>2024-01-09 10:55:29 +0000
commit873e9a50e34eef0554d1daba144e0b9b83532dba (patch)
tree6e950048d3706c595b2672d472b5064e393f65e0
parentaf8dbe452b32a4fa710b3baa4a2fa2a168a4234f (diff)
feature/standardize-quote: Proposal to Standardize EquityQuote (#5922)
* standardize EquityQuote * intrinio reference * rogue comma * pylint --------- Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
-rw-r--r--openbb_platform/core/openbb_core/provider/standard_models/equity_quote.py125
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/equity_quote.py93
-rw-r--r--openbb_platform/providers/intrinio/openbb_intrinio/models/equity_quote.py105
-rw-r--r--openbb_platform/providers/intrinio/openbb_intrinio/utils/references.py24
4 files changed, 227 insertions, 120 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/equity_quote.py b/openbb_platform/core/openbb_core/provider/standard_models/equity_quote.py
index d7c8354e2eb..7c7168a3a5e 100644
--- a/openbb_platform/core/openbb_core/provider/standard_models/equity_quote.py
+++ b/openbb_platform/core/openbb_core/provider/standard_models/equity_quote.py
@@ -18,7 +18,7 @@ class EquityQuoteQueryParams(QueryParams):
symbol: str = Field(
description=QUERY_DESCRIPTIONS.get("symbol", "")
- + " In this case, the comma separated list of symbols."
+ + " This endpoint will accept multiple symbols separated by commas."
)
@field_validator("symbol", mode="before", check_fields=False)
@@ -33,14 +33,125 @@ class EquityQuoteQueryParams(QueryParams):
class EquityQuoteData(Data):
"""Equity Quote Data."""
- day_low: Optional[float] = Field(
+ symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
+ asset_type: Optional[str] = Field(
+ default=None, description="Type of asset - i.e, stock, ETF, etc."
+ )
+ name: Optional[str] = Field(
+ default=None, description="Name of the company or asset."
+ )
+ exchange: Optional[str] = Field(
+ default=None,
+ description="The name or symbol of the venue where the data is from.",
+ )
+ bid: Optional[float] = Field(
+ default=None, description="Price of the top bid order."
+ )
+ bid_size: Optional[int] = Field(
+ default=None,
+ description="This represents the number of round lot orders at the given price."
+ + " The normal round lot size is 100 shares."
+ + " A size of 2 means there are 200 shares available at the given price.",
+ )
+ bid_exchange: Optional[str] = Field(
+ default=None,
+ description="The specific trading venue where the purchase order was placed.",
+ )
+ ask: Optional[float] = Field(
+ default=None, description="Price of the top ask order."
+ )
+ ask_size: Optional[int] = Field(
+ default=None,
+ description="This represents the number of round lot orders at the given price."
+ + " The normal round lot size is 100 shares."
+ + " A size of 2 means there are 200 shares available at the given price.",
+ )
+ ask_exchange: Optional[str] = Field(
+ default=None,
+ description="The specific trading venue where the sale order was placed.",
+ )
+ quote_conditions: Optional[Union[str, int, List[str], List[int]]] = Field(
+ default=None,
+ description="Conditions or condition codes applicable to the quote.",
+ )
+ quote_indicators: Optional[Union[str, int, List[str], List[int]]] = Field(
+ default=None,
+ description="Indicators or indicator codes applicable to the participant"
+ + " quote related to the price bands for the issue, or the affect the quote has"
+ + " on the NBBO.",
+ )
+ sales_conditions: Optional[Union[str, int, List[str], List[int]]] = Field(
+ default=None,
+ description="Conditions or condition codes applicable to the sale.",
+ )
+ sequence_number: 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).",
+ )
+ market_center: Optional[str] = Field(
+ default=None,
+ description="The ID of the UTP participant that originated the message.",
+ )
+ participant_timestamp: Optional[datetime] = Field(
default=None,
- description="Lowest price of the stock in the current trading day.",
+ description="Timestamp for when the quote was generated by the exchange.",
)
- day_high: Optional[float] = Field(
+ trf_timestamp: Optional[datetime] = Field(
default=None,
- description="Highest price of the stock in the current trading day.",
+ description="Timestamp for when the TRF (Trade Reporting Facility) received the message.",
+ )
+ sip_timestamp: Optional[datetime] = Field(
+ default=None,
+ description="Timestamp for when the SIP (Security Information Processor)"
+ + " received the message from the exchange.",
+ )
+ last_price: Optional[float] = Field(
+ default=None, description="Price of the last trade."
+ )
+ last_tick: Optional[str] = Field(
+ default=None, description="Whether the last sale was an up or down tick."
+ )
+ last_size: Optional[int] = Field(
+ default=None, description="Size of the last trade."
+ )
+ last_timestamp: Optional[datetime] = Field(
+ default=None, description="Date and Time when the last price was recorded."
+ )
+ open: Optional[float] = Field(
+ default=None, description=DATA_DESCRIPTIONS.get("open", "")
+ )
+ high: Optional[float] = Field(
+ default=None, description=DATA_DESCRIPTIONS.get("high", "")
+ )
+ low: Optional[float] = Field(
+ default=None, description=DATA_DESCRIPTIONS.get("low", "")
+ )
+ close: Optional[float] = Field(
+ default=None, description=DATA_DESCRIPTIONS.get("close", "")
+ )
+ volume: Optional[Union[int, float]] = Field(
+ default=None, description=DATA_DESCRIPTIONS.get("volume", "")
+ )
+ exchange_volume: Optional[Union[int, float]] = Field(
+ default=None,
+ description="Volume of shares exchanged during the trading day on the specific exchange.",
+ )
+ prev_close: Optional[float] = Field(
+ default=None, description=DATA_DESCRIPTIONS.get("prev_close", "")
+ )
+ change: Optional[float] = Field(
+ default=None, description="Change in price from previous close."
+ )
+ change_percent: Optional[float] = Field(
+ default=None,
+ description="Change in price as a normalized percentage.",
+ json_schema_extra={"x-frontendmultiply": 100},
+ )
+ year_high: Optional[float] = Field(
+ default=None, description="The one year high (52W High)."
)
- date: Optional[datetime] = Field(
- description=DATA_DESCRIPTIONS.get("date", ""), default=None
+ year_low: Optional[float] = Field(
+ default=None, description="The one year low (52W Low)."
)
diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/equity_quote.py b/openbb_platform/providers/fmp/openbb_fmp/models/equity_quote.py
index e3eda38e790..882eb26ea98 100644
--- a/openbb_platform/providers/fmp/openbb_fmp/models/equity_quote.py
+++ b/openbb_platform/providers/fmp/openbb_fmp/models/equity_quote.py
@@ -1,7 +1,7 @@
"""FMP Equity Quote Model."""
-from datetime import datetime
-from typing import Any, Dict, List, Optional
+from datetime import datetime, timezone
+from typing import Any, Dict, List, Optional, Union
from openbb_core.provider.abstract.data import ForceInt
from openbb_core.provider.abstract.fetcher import Fetcher
@@ -27,71 +27,58 @@ class FMPEquityQuoteData(EquityQuoteData):
__alias_dict__ = {
"price_avg50": "priceAvg50",
"price_avg200": "priceAvg200",
- "date": "timestamp",
+ "last_timestamp": "timestamp",
+ "high": "dayHigh",
+ "low": "dayLow",
+ "last_price": "price",
+ "change_percent": "changesPercentage",
+ "prev_close": "previousClose",
}
-
- symbol: Optional[str] = Field(default=None, description="Symbol of the company.")
- name: Optional[str] = Field(default=None, description="Name of the company.")
- price: Optional[float] = Field(
- default=None, description="Current trading price of the equity."
- )
- changes_percentage: Optional[float] = Field(
- default=None, description="Change percentage of the equity price."
- )
- change: Optional[float] = Field(
- default=None, description="Change in the equity price."
- )
- year_high: Optional[float] = Field(
- default=None, description="Highest price of the equity in the last 52 weeks."
- )
- year_low: Optional[float] = Field(
- default=None, description="Lowest price of the equity in the last 52 weeks."
- )
- market_cap: Optional[float] = Field(
- default=None, description="Market cap of the company."
- )
price_avg50: Optional[float] = Field(
- default=None, description="50 days average price of the equity."
+ default=None, description="50 day moving average price."
)
price_avg200: Optional[float] = Field(
- default=None, description="200 days average price of the equity."
- )
- volume: Optional[ForceInt] = Field(
- default=None,
- description="Volume of the equity in the current trading day.",
+ default=None, description="200 day moving average price."
)
avg_volume: Optional[ForceInt] = Field(
default=None,
- description="Average volume of the equity in the last 10 trading days.",
- )
- exchange: Optional[str] = Field(
- default=None, description="Exchange the equity is traded on."
+ description="Average volume over the last 10 trading days.",
)
- open: Optional[float] = Field(
- default=None,
- description="Opening price of the equity in the current trading day.",
- )
- previous_close: Optional[float] = Field(
- default=None, description="Previous closing price of the equity."
- )
- eps: Optional[float] = Field(
- default=None, description="Earnings per share of the equity."
- )
- pe: Optional[float] = Field(
- default=None, description="Price earnings ratio of the equity."
- )
- earnings_announcement: Optional[str] = Field(
- default=None, description="Earnings announcement date of the equity."
+ market_cap: Optional[float] = Field(
+ default=None, description="Market cap of the company."
)
shares_outstanding: Optional[ForceInt] = Field(
- default=None, description="Number of shares outstanding of the equity."
+ default=None, description="Number of shares outstanding."
+ )
+ eps: Optional[float] = Field(default=None, description="Earnings per share.")
+ pe: Optional[float] = Field(default=None, description="Price earnings ratio.")
+ earnings_announcement: Optional[Union[datetime, str]] = Field(
+ default=None, description="Upcoming earnings announcement date."
)
- @field_validator("timestamp", mode="before", check_fields=False)
+ @field_validator("last_timestamp", mode="before", check_fields=False)
@classmethod
- def date_validate(cls, v): # pylint: disable=E0213
+ def validate_last_timestamp(cls, v): # pylint: disable=E0213
"""Return the date as a datetime object."""
- return datetime.strptime(v, "%Y-%m-%d")
+ v = int(v) if isinstance(v, str) else v
+ return datetime.utcfromtimestamp(int(v)).replace(tzinfo=timezone.utc)
+
+ @field_validator("earnings_announcement", mode="before", check_fields=False)
+ @classmethod
+ def timestamp_validate(cls, v): # pylint: disable=E0213
+ """Return the datetime string as a datetime object."""
+ if v:
+ dt = datetime.strptime(v, "%Y-%m-%dT%H:%M:%S.%f%z")
+ dt = dt.replace(microsecond=0)
+ timestamp = dt.timestamp()
+ return datetime.fromtimestamp(timestamp, tz=timezone.utc)
+ return None
+
+ @field_validator("change_percent", mode="after", check_fields=False)
+ @classmethod
+ def normalize_percent(cls, v): # pylint: disable=E0213
+ """Return the percent value as a normalized value."""
+ return float(v) / 100 if v else None
class FMPEquityQuoteFetcher(
diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_quote.py b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_quote.py
index 8e3c0676cdf..6cdedf19460 100644
--- a/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_quote.py
+++ b/openbb_platform/providers/intrinio/openbb_intrinio/models/equity_quote.py
@@ -1,5 +1,6 @@
"""Intrinio Equity Quote Model."""
-
+# pylint: disable=unused-argument
+import warnings
from datetime import datetime
from typing import Any, Dict, List, Optional
@@ -12,9 +13,11 @@ from openbb_core.provider.utils.helpers import (
ClientResponse,
amake_requests,
)
-from openbb_intrinio.utils.references import SOURCES
+from openbb_intrinio.utils.references import SOURCES, VENUES, IntrinioSecurity
from pydantic import Field, field_validator
+_warn = warnings.warn
+
class IntrinioEquityQuoteQueryParams(EquityQuoteQueryParams):
"""Intrinio Equity Quote Query.
@@ -32,68 +35,32 @@ class IntrinioEquityQuoteData(EquityQuoteData):
"""Intrinio Equity Quote Data."""
__alias_dict__ = {
- "day_low": "low_price",
- "day_high": "high_price",
- "date": "last_time",
+ "exchange": "listing_venue",
+ "market_center": "market_center_code",
+ "bid": "bid_price",
+ "ask": "ask_price",
+ "open": "open_price",
+ "close": "close_price",
+ "low": "low_price",
+ "high": "high_price",
+ "last_timestamp": "last_time",
+ "volume": "market_volume",
}
-
- last_price: float = Field(description="Price of the last trade.")
- last_time: datetime = Field(
- description="Date and Time when the last trade occurred.", alias="date"
- )
- last_size: Optional[int] = Field(description="Size of the last trade.")
- bid_price: float = Field(description="Price of the top bid order.")
- bid_size: int = Field(description="Size of the top bid order.")
- ask_price: float = Field(description="Price of the top ask order.")
- ask_size: int = Field(description="Size of the top ask order.")
- open_price: float = Field(description="Open price for the trading day.")
- close_price: Optional[float] = Field(
- default=None, description="Closing price for the trading day (IEX source only)."
- )
- high_price: float = Field(
- description="High Price for the trading day.", alias="day_high"
- )
- low_price: float = Field(
- description="Low Price for the trading day.", alias="day_low"
- )
- exchange_volume: Optional[int] = Field(
- default=None,
- description="Number of shares exchanged during the trading day on the exchange.",
+ is_darkpool: Optional[bool] = Field(
+ default=None, description="Whether or not the current trade is from a darkpool."
)
- market_volume: Optional[int] = Field(
- default=None,
- description="Number of shares exchanged during the trading day for the whole market.",
+ source: Optional[str] = Field(
+ default=None, description="Source of the Intrinio data."
)
updated_on: datetime = Field(
description="Date and Time when the data was last updated."
)
- source: str = Field(description="Source of the data.")
- listing_venue: Optional[str] = Field(
- default=None,
- description="Listing venue where the trade took place (SIP source only).",
- )
- sales_conditions: Optional[str] = Field(
- default=None,
- description="Indicates any sales condition modifiers associated with the trade.",
- )
- quote_conditions: Optional[str] = Field(
- default=None,
- description="Indicates any quote condition modifiers associated with the trade.",
- )
- market_center_code: Optional[str] = Field(
- default=None, description="Market center character code."
- )
- is_darkpool: Optional[bool] = Field(
- default=None, description="Whether or not the current trade is from a darkpool."
- )
- messages: Optional[List[str]] = Field(
- default=None, description="Messages associated with the endpoint."
- )
- security: Optional[Dict[str, Any]] = Field(
+ security: Optional[IntrinioSecurity] = Field(
default=None, description="Security details related to the quote."
)
@field_validator("last_time", "updated_on", mode="before", check_fields=False)
+ @classmethod
def date_validate(cls, v): # pylint: disable=E0213
"""Return the date as a datetime object."""
return (
@@ -102,6 +69,25 @@ class IntrinioEquityQuoteData(EquityQuoteData):
else datetime.fromisoformat(v)
)
+ @field_validator("sales_conditions", mode="before", check_fields=False)
+ @classmethod
+ def validate_sales_conditions(cls, v):
+ """Validate sales conditions and remove empty strings."""
+ if v == "\u0017 ":
+ return None
+ if v == "" or v is None:
+ return None
+ v = v.strip()
+ return v
+
+ @field_validator("exchange", "market_center", mode="before", check_fields=False)
+ @classmethod
+ def validate_listing_venue(cls, v):
+ """Validate listing venue and remove empty strings."""
+ if v:
+ return VENUES[v] if v in VENUES else v
+ return None
+
class IntrinioEquityQuoteFetcher(
Fetcher[
@@ -133,20 +119,21 @@ class IntrinioEquityQuoteFetcher(
return {}
response_data = await response.json()
- response_data["symbol"] = response.url.parts[-2]
-
- return response_data
+ response_data["symbol"] = response_data["security"].get("ticker", None) # type: ignore
+ if "messages" in response_data and response_data.get("messages"): # type: ignore
+ _message = list(response_data.pop("messages")) # type: ignore
+ _warn(str(",".join(_message)))
+ return response_data # type: ignore
urls = [
f"{base_url}/securities/{s.strip()}/prices/realtime?source={query.source}&api_key={api_key}"
for s in query.symbol.split(",")
]
-
return await amake_requests(urls, callback, **kwargs)
@staticmethod
def transform_data(
- query: IntrinioEquityQuoteQueryParams, data: dict, **kwargs: Any
+ query: IntrinioEquityQuoteQueryParams, data: List[Dict], **kwargs: Any
) -> List[IntrinioEquityQuoteData]:
"""Return the transformed data."""
return [IntrinioEquityQuoteData.model_validate(d) for d in data]
diff --git a/openbb_platform/providers/intrinio/openbb_intrinio/utils/references.py b/openbb_platform/providers/intrinio/openbb_intrinio/utils/references.py
index 876350ae34f..fcd17a128dc 100644
--- a/openbb_platform/providers/intrinio/openbb_intrinio/utils/references.py
+++ b/openbb_platform/providers/intrinio/openbb_intrinio/utils/references.py
@@ -17,6 +17,28 @@ SOURCES = Literal[
"delayed_sip",
]
+VENUES = {
+ "A": "NYSE MKT LLC",
+ "B": "NASDAQ OMX BX, Inc.",
+ "C": "National Stock Exchange Inc. (NSX)",
+ "D": "FINRA ADF",
+ "I": "International Securities Exchange, LLC",
+ "J": "Bats EDGA Exchange, INC",
+ "K": "Bats EDGX Exchange, Inc.",
+ "M": "Chicago Stock Exchange, Inc. (CHX)",
+ "N": "New York Stock Exchange LLC",
+ "P": "NYSE Arca, Inc.",
+ "S": "Consolidated Tape System",
+ "T": "NASDAQ (Tape A, B securities)",
+ "Q": "NASDAQ (Tape C securities)",
+ "V": "The Investors' Exchange, LLC (IEX)",
+ "W": "Chicago Broad Options Exchange, Inc. (CBOE)",
+ "X": "NASDAQ OMX PSX, Inc. LLC",
+ "Y": "Bats BYX Exchange, Inc.",
+ "Z": "Bats BZX Exchange, Inc.",
+ "u": "Other OTC Markets",
+}
+
class IntrinioCompany(Data):
"""Intrinio Company Data."""
@@ -65,7 +87,7 @@ class IntrinioSecurity(Data):
description="The country-composite OpenFIGI identifier.", default=None
)
share_class_figi: Optional[str] = Field(
- description="The global-composite OpenFIGI identifier.",
+ description="The global-composite OpenFIGI identifier.", default=None
)
primary_listing: Optional[bool] = Field(
description="""