diff --git a/openbb_platform/providers/nasdaq/README.md b/openbb_platform/providers/nasdaq/README.md index bfeb10ef038..cae00d9da24 100644 --- a/openbb_platform/providers/nasdaq/README.md +++ b/openbb_platform/providers/nasdaq/README.md @@ -10,4 +10,67 @@ To install the extension: pip install openbb-nasdaq ``` -Documentation available [here](https://docs.openbb.co/platform/developer_guide/contributing). +## Coverage + +The following endpoints are covered by this extension: + +- `obb.economy.calendar` +- `obb.equity.calendar.dividend` +- `obb.equity.calendar.earnings` +- `obb.equity.calendar.ipo` +- `obb.equity.search` +- `obb.equity.screener` +- `obb.equity.fundamental.dividend` +- `obb.equity.fundamental.filings` + +Router extensions are required (`openbb-economy` & `openbb-equity`) to populate endpoints, but fetchers can be used independent of the Python and API interfaces. + +```python +from openbb_nasdaq import nasdaq_provider + +nasdaq_provider.fetcher_dict +``` + +```python +{'CalendarDividend': openbb_nasdaq.models.calendar_dividend.NasdaqCalendarDividendFetcher, +'CalendarEarnings': openbb_nasdaq.models.calendar_earnings.NasdaqCalendarEarningsFetcher, +'CalendarIpo': openbb_nasdaq.models.calendar_ipo.NasdaqCalendarIpoFetcher, +'CompanyFilings': openbb_nasdaq.models.company_filings.NasdaqCompanyFilingsFetcher, +'EconomicCalendar': openbb_nasdaq.models.economic_calendar.NasdaqEconomicCalendarFetcher, +'EquitySearch': openbb_nasdaq.models.equity_search.NasdaqEquitySearchFetcher, +'EquityScreener': openbb_nasdaq.models.equity_screener.NasdaqEquityScreenerFetcher, +'HistoricalDividends': openbb_nasdaq.models.historical_dividends.NasdaqHistoricalDividendsFetcher, +'TopRetail': openbb_nasdaq.models.top_retail.NasdaqTopRetailFetcher} +``` + +## OpenBB Workspace App + +This package includes a standalone Workspace application for viewing market calendars and company filings. + +Launch it from the command line, with the environment active, by entering: + +```sh +openbb-api --app openbb_nasdaq.app:app --factory +``` + +This will start the FastAPI application via Uvicorn and serve the configuration files to Workspace when added as a backend data source. + +To see all the launch arguments, use: + +```sh +openbb-api --help +``` + +The application is served by a factory function, and the FastAPI instance can be utilized by calling the `main` function. + +```python +from openbb_nasdaq.app import main + +app = main() +``` + +## OpenBB Documentation + +OpenBB Platform documentation is available [here](https://docs.openbb.co/platform). + +OpenBB Workspace documentation is available [here](https://docs.openbb.co/workspace). diff --git a/openbb_platform/providers/nasdaq/openbb_nasdaq/app.py b/openbb_platform/providers/nasdaq/openbb_nasdaq/app.py new file mode 100644 index 00000000000..75c2ce6b22e --- /dev/null +++ b/openbb_platform/providers/nasdaq/openbb_nasdaq/app.py @@ -0,0 +1,727 @@ +"""OpenBB Workspace Application. + +This includes widgets and endpoints for fetching and displaying +SEC filings, as well as calendars for earnings, dividends, ipos, +and global economic events from the public Nasdaq website. + +To use this app, launch it with: + +```bash +openbb-api --app openbb_nasdaq.app:main --factory +``` + +Then navigate to the OpenBB Workspace and add the server as a backend data source. + +Open the server's URL in your browser for more information on connecting to the OpenBB Workspace. +""" + +# pylint: disable=too-many-statements, too-many-locals, line-too-long + + +def main(): + """Return a FastAPI app instance.""" + # pylint: disable=import-outside-toplevel + from datetime import datetime + from typing import Annotated, Literal, Optional + + from async_lru import alru_cache + from fastapi import Depends, FastAPI + from openbb_nasdaq.models.calendar_dividend import NasdaqCalendarDividendFetcher + from openbb_nasdaq.models.calendar_earnings import NasdaqCalendarEarningsFetcher + from openbb_nasdaq.models.calendar_ipo import NasdaqCalendarIpoFetcher + from openbb_nasdaq.models.company_filings import NasdaqCompanyFilingsFetcher + from openbb_nasdaq.models.economic_calendar import NasdaqEconomicCalendarFetcher + from openbb_nasdaq.models.equity_search import NasdaqEquitySearchFetcher + from pandas import DataFrame + + listings = DataFrame() + + current_year = int(datetime.now().year) + years = sorted( + [{"label": str(i), "value": i} for i in range(1994, current_year + 1)], + key=lambda x: x["value"], # type: ignore + reverse=True, + ) + + app = FastAPI() + + async def get_listings() -> DataFrame: + """Get the Nasdaq listings.""" + return listings + + Nasdaqlistings = Annotated[ + DataFrame, + Depends(get_listings), + ] + + async def startup_event(): + """Startup event for the FastAPI app.""" + nonlocal listings + fetcher = NasdaqEquitySearchFetcher() + + directory = await fetcher.fetch_data({}, {}) + + listings = DataFrame([d.model_dump() for d in directory]).query( # type: ignore + "test_issue == 'N' and etf == 'N'" + " and not name.str.contains('%')" + " and not name.str.contains('Unit')" + " and not name.str.contains('Rights')" + " and not name.str.contains('Warrant')" + " and not name.str.contains('Preferred')" + ) + + app.add_event_handler("startup", startup_event) + + @app.get("/get_symbol_choices", include_in_schema=False) + async def get_symbol_choices( + directory: Nasdaqlistings, # type: ignore + symbol: Optional[str] = None, + year: Optional[int] = None, + form_group: Optional[str] = None, + ) -> list: + """Get symbol choices for the Nasdaq listings.""" + + if symbol and year and form_group: + return await get_document_choices(symbol, year, form_group) + + items = directory.set_index("symbol")["name"].to_dict() + + return [ + {"value": k, "label": k, "extraInfo": {"description": v}} + for k, v in items.items() + ] + + @alru_cache + async def get_document_choices( + symbol: Optional[str] = None, + year: Optional[int] = None, + form_group: Literal[ + "annual", + "quarterly", + "proxy", + "insider", + "8k", + "registration", + "comment", + ] = "8k", + ) -> list: + """Get document choices for the given symbol.""" + fetcher = NasdaqCompanyFilingsFetcher() + + params = { + "symbol": symbol, + "year": year, + "form_group": form_group, + } + + items = await fetcher.fetch_data(params, {}) + docs: list = [] + + for item in items: + form = item.model_dump() # type: ignore + title = f"{form.get('filing_date', '')} - {'8-K' if form_group == '8k' else form_group.title()}" + url = form.get("pdf_url") + + if not url: + continue + docs.append({"label": title, "value": url}) + + return docs + + @app.get( + "/open_document", + openapi_extra={ + "widget_config": { + "type": "multi_file_viewer", + "name": "SEC Filings", + "description": "View SEC filings by company.", + "refetchInterval": False, + "widgetId": "nasdaq_company_filings", + "params": [ + { + "paramName": "document_url", + "label": "Document URL", + "description": "Select the document to open.", + "type": "endpoint", + "optionsEndpoint": "/get_symbol_choices", + "optionsParams": { + "symbol": "$symbol", + "year": "$year", + "form_group": "$form_group", + }, + "show": False, + "roles": ["fileSelector"], + "multiSelect": True, + }, + { + "paramName": "symbol", + "label": "Symbol", + "description": ( + "Ticker symbol for the company." + " Foreign (dual-listed) companies may not be required to file with the SEC." + ), + "type": "endpoint", + "value": "AAPL", + "optionsEndpoint": "/get_symbol_choices", + "style": {"popupWidth": 850}, + }, + { + "paramName": "year", + "label": "Calendar Year", + "description": "Calendar year for the filings.", + "value": years[0]["value"], + "type": "number", + "options": years, + }, + { + "paramName": "form_group", + "label": "Form Group", + "description": "Form group for the filings.", + "value": "8k", + "type": "text", + "options": [ + {"label": "Annual", "value": "annual"}, + {"label": "Quarterly", "value": "quarterly"}, + {"label": "Proxy", "value": "proxy"}, + {"label": "Insider", "value": "insider"}, + {"label": "8-K", "value": "8k"}, + {"label": "Registration", "value": "registration"}, + {"label": "Comment", "value": "comment"}, + ], + }, + ], + "gridData": {"w": 40, "h": 30}, + } + }, + ) + async def open_document( + document_url: str, + ) -> dict: + """Open the selected PDF filing document.""" + import base64 # noqa + from openbb_core.provider.utils.helpers import make_request + + encoded_content: str = "" + + if document_url and document_url.startswith("http"): + response = make_request(document_url) + response.raise_for_status() + encoded_content = base64.b64encode(response.content).decode("utf-8") + + return { + "data_format": { + "data_type": "pdf", + "filename": document_url.split("/")[-1], + }, + "content": encoded_content, + } + + @app.get( + "/earnings_calendar", + openapi_extra={ + "widget_config": { + "name": "Earnings Calendar", + "description": "Upcoming, and historical, company earnings releases (US Markets).", + "category": "Equity", + "subCategory": "Calendar", + "type": "table", + "searchCategory": "Equity", + "widgetId": "nasdaq_earnings_calendar", + "refetchInterval": False, + "params": [ + { + "label": "Start Date", + "description": "Start date of the data.", + "type": "date", + "value": None, + "paramName": "start_date", + }, + { + "label": "End Date", + "description": "End date of the data.", + "type": "date", + "value": None, + "paramName": "end_date", + }, + ], + "gridData": {"w": 40, "h": 20}, + "data": { + "table": { + "columnsDefs": [ + { + "field": "surprise_percent", + "formatterFn": "normalizedPercent", + "cellDataType": "number", + "renderFn": "greenRed", + }, + ] + } + }, + }, + }, + ) + async def earnings_calendar( + start_date: Optional[str] = None, + end_date: Optional[str] = None, + ) -> list[NasdaqCalendarEarningsFetcher.data_type]: + """Get the earnings calendar.""" + fetcher = NasdaqCalendarEarningsFetcher() + + params = { + "start_date": start_date, + "end_date": end_date, + } + + return await fetcher.fetch_data(params, {}) # type: ignore + + @app.get( + "/dividend_calendar", + openapi_extra={ + "widget_config": { + "name": "Dividend Calendar", + "description": "Upcoming, and historical, dividend payments (US Markets).", + "category": "Equity", + "subCategory": "Calendar", + "type": "table", + "searchCategory": "Equity", + "widgetId": "nasdaq_dividends_calendar", + "refetchInterval": False, + "params": [ + { + "label": "Start Date", + "description": "Start date of the data.", + "type": "date", + "value": None, + "paramName": "start_date", + }, + { + "label": "End Date", + "description": "End date of the data.", + "type": "date", + "value": None, + "paramName": "end_date", + }, + ], + "gridData": {"w": 40, "h": 20}, + } + }, + ) + async def dividend_calendar( + start_date: Optional[str] = None, + end_date: Optional[str] = None, + ) -> list[NasdaqCalendarDividendFetcher.data_type]: + """Get the dividend calendar.""" + fetcher = NasdaqCalendarDividendFetcher() + + params = { + "start_date": start_date, + "end_date": end_date, + } + + return await fetcher.fetch_data(params, {}) # type: ignore + + @app.get( + "/ipo_calendar", + openapi_extra={ + "widget_config": { + "name": "IPO Calendar", + "description": "IPO announcements and calendar.", + "category": "Equity", + "subCategory": "Calendar", + "type": "table", + "searchCategory": "Equity", + "widgetId": "nasdaq_ipo_calendar", + "refetchInterval": False, + "params": [ + { + "label": "Start Date", + "description": "Start date of the data, in YYYY-MM-DD format.", + "optional": True, + "type": "date", + "value": None, + "show": True, + "paramName": "start_date", + }, + { + "label": "End Date", + "description": "End date of the data, in YYYY-MM-DD format.", + "optional": True, + "type": "date", + "value": None, + "show": True, + "paramName": "end_date", + }, + { + "label": "Status", + "description": "Status of the IPO. [upcoming, priced, or withdrawn]", + "optional": True, + "type": "text", + "value": None, + "show": True, + "options": [ + {"label": "upcoming", "value": "upcoming"}, + {"label": "priced", "value": "priced"}, + {"label": "filed", "value": "filed"}, + {"label": "withdrawn", "value": "withdrawn"}, + ], + "paramName": "status", + }, + { + "label": "Is SPO", + "description": "If True, returns data for secondary public offerings (SPOs).", + "optional": True, + "type": "boolean", + "value": False, + "show": True, + "paramName": "is_spo", + }, + ], + "gridData": {"w": 40, "h": 20}, + } + }, + ) + async def ipo_calendar( + start_date: Optional[str] = None, + end_date: Optional[str] = None, + ) -> list[NasdaqCalendarIpoFetcher.data_type]: + """Get the IPO calendar.""" + fetcher = NasdaqCalendarIpoFetcher() + + params = { + "start_date": start_date, + "end_date": end_date, + } + + return await fetcher.fetch_data(params, {}) # type: ignore + + @app.get( + "/economic_calendar", + openapi_extra={ + "widget_config": { + "name": "Economic Calendar", + "description": "Upcoming, and historical, macroeconomic events.", + "category": "Economy", + "subCategory": "Calendar", + "type": "table", + "searchCategory": "Equity", + "widgetId": "nasdaq_economic_calendar", + "refetchInterval": False, + "params": [ + { + "paramName": "country", + "options": [ + {"label": "United States", "value": "united_states"}, + {"label": "China", "value": "china"}, + {"label": "Hong Kong", "value": "hong_kong"}, + {"label": "Japan", "value": "japan"}, + {"label": "Singapore", "value": "signapore"}, + {"label": "South Korea", "value": "south_korea"}, + {"label": "India", "value": "india"}, + {"label": "Russia", "value": "russia"}, + {"label": "Euro Zone", "value": "euro_zone"}, + {"label": "Germany", "value": "germany"}, + {"label": "United Kingdom", "value": "united_kingdom"}, + {"label": "France", "value": "france"}, + {"label": "Spain", "value": "spain"}, + {"label": "Italy", "value": "italy"}, + {"label": "Switzerland", "value": "switzerland"}, + {"label": "Canada", "value": "canada"}, + {"label": "Australia", "value": "australia"}, + {"label": "New Zealand", "value": "new_zealand"}, + {"label": "South Africa", "value": "south_africa"}, + {"label": "Brazil", "value": "brazil"}, + ], + }, + ], + "gridData": {"w": 40, "h": 20}, + "data": { + "table": { + "showAll": False, + "columnsDefs": [ + { + "field": "date", + "pinned": "left", + "formatterFn": None, + "headerName": "Date", + "headerTooltip": "The date of the data.", + "cellDataType": "date", + }, + { + "field": "country", + "formatterFn": None, + "headerName": "Country", + "headerTooltip": "Country of event.", + "cellDataType": "text", + }, + { + "field": "event", + "formatterFn": None, + "renderFn": "hoverCard", + "renderFnParams": { + "hoverCard": { + "cellField": "value", + "markdown": "{description}", + } + }, + "headerName": "Event", + "headerTooltip": "Event name.", + "cellDataType": "text", + }, + { + "field": "consensus", + "formatterFn": None, + "headerName": "Consensus", + "headerTooltip": "Average forecast among a representative group of economists.", + "cellDataType": "number", + }, + { + "field": "actual", + "formatterFn": None, + "headerName": "Actual", + "headerTooltip": "Latest released value.", + "cellDataType": "number", + }, + { + "field": "previous", + "formatterFn": None, + "headerName": "Previous", + "headerTooltip": ( + "Value for the previous period after" + + " the revision (if revision is applicable)." + ), + "cellDataType": "number", + }, + { + "field": "revised", + "formatterFn": None, + "headerName": "Revised", + "headerTooltip": "Revised previous value, if applicable.", + "cellDataType": "number", + }, + ], + } + }, + } + }, + ) + async def economic_calendar( + start_date: Optional[str] = None, + end_date: Optional[str] = None, + country: Optional[str] = None, + ): + """Get the dividend calendar.""" + fetcher = NasdaqEconomicCalendarFetcher() + + params = { + "start_date": start_date, + "end_date": end_date, + } + if country: + params["country"] = country + + data: list[NasdaqEconomicCalendarFetcher.data_type] = await fetcher.fetch_data(params, {}) # type: ignore + output: list = [] + + for row in data: + markdown = ( + f"### **{row.event}**\n\n{row.description}" + if row.description + else row.event + ) + output.append( + { + "date": row.date.strftime("%Y-%m-%d"), + "country": row.country, + "event": { + "value": row.event, + "description": markdown, + }, + "consensus": row.consensus, + "actual": row.actual, + "previous": row.previous, + "revised": row.revised, + } + ) + + return output + + @app.get( + "/apps.json", + include_in_schema=False, + ) + async def get_apps() -> dict: + return { + "name": "Company Filings & Market Calendars", + "img": "https://logosandtypes.com/wp-content/uploads/2020/07/nasdaq.svg", + "img_dark": "", + "img_light": "", + "description": ( + "View company filings and market calendars, downloaded from the public Nasdaq website." + " This app is not affiliated with, or authoriazed by, Nasdaq." + ), + "allowCustomization": True, + "tabs": { + "earnings": { + "id": "earnings", + "name": "Earnings Calendar & Filings", + "layout": [ + { + "i": "nasdaq_earnings_calendar", + "x": 0, + "y": 2, + "w": 40, + "h": 10, + "state": { + "chartView": {"enabled": False, "chartType": "line"}, + "columnState": { + "default": { + "sort": { + "sortModel": [ + {"colId": "market_cap", "sort": "desc"} + ] + }, + "columnPinning": { + "leftColIds": [ + "symbol", + "name", + "period_ending", + ], + "rightColIds": [], + }, + "columnOrder": { + "orderedColIds": [ + "report_date", + "reporting_time", + "symbol", + "name", + "market_cap", + "eps_previous", + "eps_consensus", + "num_estimates", + "eps_actual", + "surprise_percent", + "period_ending", + "previous_report_date", + ] + }, + } + }, + }, + }, + { + "i": "nasdaq_company_filings", + "x": 0, + "y": 12, + "w": 40, + "h": 30, + }, + ], + }, + "dividend-calendar": { + "id": "dividend-calendar", + "name": "Dividend Calendar", + "layout": [ + { + "i": "nasdaq_dividends_calendar", + "x": 0, + "y": 2, + "w": 40, + "h": 20, + "state": { + "chartView": {"enabled": False, "chartType": "line"}, + "columnState": { + "default": { + "sort": { + "sortModel": [ + { + "colId": "ex_dividend_date", + "sort": "asc", + }, + {"colId": "amount", "sort": "desc"}, + { + "colId": "annualized_amount", + "sort": "desc", + }, + ] + }, + "columnPinning": { + "leftColIds": ["symbol", "name"], + "rightColIds": [], + }, + "columnOrder": { + "orderedColIds": [ + "ex_dividend_date", + "symbol", + "amount", + "name", + "record_date", + "payment_date", + "declaration_date", + "annualized_amount", + ] + }, + } + }, + }, + } + ], + }, + "ipo-calendar": { + "id": "ipo-calendar", + "name": "IPO Calendar", + "layout": [ + { + "i": "nasdaq_ipo_calendar", + "x": 0, + "y": 2, + "w": 40, + "h": 21, + "state": { + "chartView": {"enabled": False, "chartType": "line"}, + "columnState": { + "default": { + "columnPinning": { + "leftColIds": [ + "ipo_date", + "symbol", + "name", + ], + "rightColIds": [], + }, + "columnOrder": { + "orderedColIds": [ + "ipo_date", + "symbol", + "name", + "offer_amount", + "share_count", + "share_price", + "deal_status", + "exchange", + "id", + ] + }, + } + }, + }, + } + ], + }, + "economic-calendar": { + "id": "economic-calendar", + "name": "Economic Calendar", + "layout": [ + { + "i": "nasdaq_economic_calendar", + "x": 0, + "y": 2, + "w": 40, + "h": 20, + } + ], + }, + }, + "groups": [], + } + + return app diff --git a/openbb_platform/providers/nasdaq/poetry.lock b/openbb_platform/providers/nasdaq/poetry.lock index 2ced7b33597..a8dfb27302b 100644 --- a/openbb_platform/providers/nasdaq/poetry.lock +++ b/openbb_platform/providers/nasdaq/poetry.lock @@ -166,6 +166,21 @@ doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] +[[package]] +name = "async-lru" +version = "2.0.5" +description = "Simple LRU cache for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943"}, + {file = "async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb"}, +] + +[package.dependencies] +typing_extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + [[package]] name = "async-timeout" version = "5.0.1" @@ -370,6 +385,30 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "deepdiff" +version = "8.5.0" +description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "deepdiff-8.5.0-py3-none-any.whl", hash = "sha256:d4599db637f36a1c285f5fdfc2cd8d38bde8d8be8636b65ab5e425b67c54df26"}, + {file = "deepdiff-8.5.0.tar.gz", hash = "sha256:a4dd3529fa8d4cd5b9cbb6e3ea9c95997eaa919ba37dac3966c1b8f872dc1cd1"}, +] + +[package.dependencies] +orderly-set = ">=5.4.1,<6" + +[package.extras] +cli = ["click (>=8.1.0,<8.2.0)", "pyyaml (>=6.0.0,<6.1.0)"] +coverage = ["coverage (>=7.6.0,<7.7.0)"] +dev = ["bump2version (>=1.0.0,<1.1.0)", "ipdb (>=0.13.0,<0.14.0)", "jsonpickle (>=4.0.0,<4.1.0)", "nox (==2025.5.1)", "numpy (>=2.0,<3.0) ; python_version < \"3.10\"", "numpy (>=2.2.0,<2.3.0) ; python_version >= \"3.10\"", "orjson (>=3.10.0,<3.11.0)", "pandas (>=2.2.0,<2.3.0)", "polars (>=1.21.0,<1.22.0)", "python-dateutil (>=2.9.0,<2.10.0)", "tomli (>=2.2.0,<2.3.0)", "tomli-w (>=1.2.0,<1.3.0)"] +docs = ["Sphinx (>=6.2.0,<6.3.0)", "sphinx-sitemap (>=2.6.0,<2.7.0)", "sphinxemoji (>=0.3.0,<0.4.0)"] +optimize = ["orjson"] +static = ["flake8 (>=7.1.0,<7.2.0)", "flake8-pyproject (>=1.2.3,<1.3.0)", "pydantic (>=2.10.0,<2.11.0)"] +test = ["pytest (>=8.3.0,<8.4.0)", "pytest-benchmark (>=5.1.0,<5.2.0)", "pytest-cov (>=6.0.0,<6.1.0)", "python-dotenv (>=1.0.0,<1.1.0)"] + [[package]] name = "distro" version = "1.9.0" @@ -934,6 +973,34 @@ uuid7 = ">=0.1.0,<0.2.0" uvicorn = ">=0.34.2,<0.35.0" websockets = ">=15.0,<16.0" +[[package]] +name = "openbb-platform-api" +version = "1.1.10" +description = "OpenBB Platform API: Launch script and widgets builder for the OpenBB Platform API and Workspace Backend Connector." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_platform_api-1.1.10-py3-none-any.whl", hash = "sha256:18da196dac5b2be9e7fc8632eaa495826ae1bcfd3365d319c5c8550219919e2c"}, + {file = "openbb_platform_api-1.1.10.tar.gz", hash = "sha256:8a0cb7dd9f183587a262965cfaa7b7b4098e9fa112614ebdcb5eb40d35e70c84"}, +] + +[package.dependencies] +deepdiff = "*" +openbb-core = ">=1.4.7,<2.0.0" + +[[package]] +name = "orderly-set" +version = "5.4.1" +description = "Orderly set" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "orderly_set-5.4.1-py3-none-any.whl", hash = "sha256:b5e21d21680bd9ef456885db800c5cb4f76a03879880c0175e1b077fb166fd83"}, + {file = "orderly_set-5.4.1.tar.gz", hash = "sha256:a1fb5a4fdc5e234e9e8d8e5c1bbdbc4540f4dfe50d12bf17c8bc5dbf1c9c878d"}, +] + [[package]] name = "pandas" version = "2.2.3" @@ -1788,4 +1855,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9.21,<3.13" -content-hash = "e87dd4d308fbc419916e8f08f30702b1ea7005caf3424a3220cafac9139c31b3" +content-hash = "8d6ad6be9cd05a1d7c1ec900e35fafc7551844a836386799eafbb4a2641e185b" diff --git a/openbb_platform/providers/nasdaq/pyproject.toml b/openbb_platform/providers/nasdaq/pyproject.toml index 4e78332c5a8..d4591137cb9 100644 --- a/openbb_platform/providers/nasdaq/pyproject.toml +++ b/openbb_platform/providers/nasdaq/pyproject.toml @@ -9,7 +9,9 @@ packages = [{ include = "openbb_nasdaq" }] [tool.poetry.dependencies] python = ">=3.9.21,<3.13" -openbb-core = "^1.4.6" +openbb-core = "^1.4.7" +openbb-platform-api = "^1.1.10" +async-lru = "^2.0.5" random-user-agent = "^1.0.1" nasdaq-data-link = "^1.0.4" diff --git a/openbb_platform/providers/nasdaq/tests/record/expected_apps.json b/openbb_platform/providers/nasdaq/tests/record/expected_apps.json new file mode 100644 index 00000000000..dc92c5cf18e --- /dev/null +++ b/openbb_platform/providers/nasdaq/tests/record/expected_apps.json @@ -0,0 +1,188 @@ +{ + "name": "Company Filings & Market Calendars", + "img": "https://logosandtypes.com/wp-content/uploads/2020/07/nasdaq.svg", + "img_dark": "", + "img_light": "", + "description": "View company filings and market calendars, downloaded from the public Nasdaq website. This app is not affiliated with, or authoriazed by, Nasdaq.", + "allowCustomization": true, + "tabs": { + "earnings": { + "id": "earnings", + "name": "Earnings Calendar & Filings", + "layout": [ + { + "i": "nasdaq_earnings_calendar", + "x": 0, + "y": 2, + "w": 40, + "h": 10, + "state": { + "chartView": { + "enabled": false, + "chartType": "line" + }, + "columnState": { + "default": { + "sort": { + "sortModel": [ + { + "colId": "market_cap", + "sort": "desc" + } + ] + }, + "columnPinning": { + "leftColIds": [ + "symbol", + "name", + "period_ending" + ], + "rightColIds": [] + }, + "columnOrder": { + "orderedColIds": [ + "report_date", + "reporting_time", + "symbol", + "name", + "market_cap", + "eps_previous", + "eps_consensus", + "num_estimates", + "eps_actual", + "surprise_percent", + "period_ending", + "previous_report_date" + ] + } + } + } + } + }, + { + "i": "nasdaq_company_filings", + "x": 0, + "y": 12, + "w": 40, + "h": 30 + } + ] + }, + "dividend-calendar": { + "id": "dividend-calendar", + "name": "Dividend Calendar", + "layout": [ + { + "i": "nasdaq_dividends_calendar", + "x": 0, + "y": 2, + "w": 40, + "h": 20, + "state": { + "chartView": { + "enabled": false, + "chartType": "line" + }, + "columnState": { + "default": { + "sort": { + "sortModel": [ + { + "colId": "ex_dividend_date", + "sort": "asc" + }, + { + "colId": "amount", + "sort": "desc" + }, + { + "colId": "annualized_amount", + "sort": "desc" + } + ] + }, + "columnPinning": { + "leftColIds": [ + "symbol", + "name" + ], + "rightColIds": [] + }, + "columnOrder": { + "orderedColIds": [ + "ex_dividend_date", + "symbol", + "amount", + "name", + "record_date", + "payment_date", + "declaration_date", + "annualized_amount" + ] + } + } + } + } + } + ] + }, + "ipo-calendar": { + "id": "ipo-calendar", + "name": "IPO Calendar", + "layout": [ + { + "i": "nasdaq_ipo_calendar", + "x": 0, + "y": 2, + "w": 40, + "h": 21, + "state": { + "chartView": { + "enabled": false, + "chartType": "line" + }, + "columnState": { + "default": { + "columnPinning": { + "leftColIds": [ + "ipo_date", + "symbol", + "name" + ], + "rightColIds": [] + }, + "columnOrder": { + "orderedColIds": [ + "ipo_date", + "symbol", + "name", + "offer_amount", + "share_count", + "share_price", + "deal_status", + "exchange", + "id" + ] + } + } + } + } + } + ] + }, + "economic-calendar": { + "id": "economic-calendar", + "name": "Economic Calendar", + "layout": [ + { + "i": "nasdaq_economic_calendar", + "x": 0, + "y": 2, + "w": 40, + "h": 20 + } + ] + } + }, + "groups": [] +} \ No newline at end of file diff --git a/openbb_platform/providers/nasdaq/tests/record/expected_widgets.json b/openbb_platform/providers/nasdaq/tests/record/expected_widgets.json new file mode 100644 index 00000000000..0af63ef4d2a --- /dev/null +++ b/openbb_platform/providers/nasdaq/tests/record/expected_widgets.json @@ -0,0 +1,807 @@ +{ + "nasdaq_company_filings": { + "name": "SEC Filings", + "description": "View SEC filings by company.", + "category": "Open Document", + "type": "multi_file_viewer", + "searchCategory": "Open Document", + "widgetId": "nasdaq_company_filings", + "params": [ + { + "label": "Document URL", + "description": "Select the document to open.", + "optional": false, + "type": "endpoint", + "value": null, + "show": false, + "paramName": "document_url", + "optionsEndpoint": "/get_symbol_choices", + "optionsParams": { + "symbol": "$symbol", + "year": "$year", + "form_group": "$form_group" + }, + "roles": [ + "fileSelector" + ], + "multiSelect": true + }, + { + "paramName": "symbol", + "label": "Symbol", + "description": "Ticker symbol for the company. Foreign (dual-listed) companies may not be required to file with the SEC.", + "type": "endpoint", + "value": "AAPL", + "optionsEndpoint": "/get_symbol_choices", + "style": { + "popupWidth": 850 + } + }, + { + "paramName": "year", + "label": "Calendar Year", + "description": "Calendar year for the filings.", + "value": 2025, + "type": "number", + "options": [ + { + "label": "2025", + "value": 2025 + }, + { + "label": "2024", + "value": 2024 + }, + { + "label": "2023", + "value": 2023 + }, + { + "label": "2022", + "value": 2022 + }, + { + "label": "2021", + "value": 2021 + }, + { + "label": "2020", + "value": 2020 + }, + { + "label": "2019", + "value": 2019 + }, + { + "label": "2018", + "value": 2018 + }, + { + "label": "2017", + "value": 2017 + }, + { + "label": "2016", + "value": 2016 + }, + { + "label": "2015", + "value": 2015 + }, + { + "label": "2014", + "value": 2014 + }, + { + "label": "2013", + "value": 2013 + }, + { + "label": "2012", + "value": 2012 + }, + { + "label": "2011", + "value": 2011 + }, + { + "label": "2010", + "value": 2010 + }, + { + "label": "2009", + "value": 2009 + }, + { + "label": "2008", + "value": 2008 + }, + { + "label": "2007", + "value": 2007 + }, + { + "label": "2006", + "value": 2006 + }, + { + "label": "2005", + "value": 2005 + }, + { + "label": "2004", + "value": 2004 + }, + { + "label": "2003", + "value": 2003 + }, + { + "label": "2002", + "value": 2002 + }, + { + "label": "2001", + "value": 2001 + }, + { + "label": "2000", + "value": 2000 + }, + { + "label": "1999", + "value": 1999 + }, + { + "label": "1998", + "value": 1998 + }, + { + "label": "1997", + "value": 1997 + }, + { + "label": "1996", + "value": 1996 + }, + { + "label": "1995", + "value": 1995 + }, + { + "label": "1994", + "value": 1994 + } + ] + }, + { + "paramName": "form_group", + "label": "Form Group", + "description": "Form group for the filings.", + "value": "8k", + "type": "text", + "options": [ + { + "label": "Annual", + "value": "annual" + }, + { + "label": "Quarterly", + "value": "quarterly" + }, + { + "label": "Proxy", + "value": "proxy" + }, + { + "label": "Insider", + "value": "insider" + }, + { + "label": "8-K", + "value": "8k" + }, + { + "label": "Registration", + "value": "registration" + }, + { + "label": "Comment", + "value": "comment" + } + ] + } + ], + "endpoint": "open_document", + "runButton": false, + "gridData": { + "w": 40, + "h": 30 + }, + "data": { + "dataKey": "", + "table": { + "showAll": true + } + }, + "source": [ + "Custom" + ], + "refetchInterval": false + }, + "nasdaq_earnings_calendar": { + "name": "Earnings Calendar", + "description": "Upcoming, and historical, company earnings releases (US Markets).", + "category": "Equity", + "type": "table", + "searchCategory": "Equity", + "widgetId": "nasdaq_earnings_calendar", + "params": [ + { + "label": "Start Date", + "description": "Start date of the data.", + "optional": true, + "type": "date", + "value": null, + "show": true, + "paramName": "start_date" + }, + { + "label": "End Date", + "description": "End date of the data.", + "optional": true, + "type": "date", + "value": null, + "show": true, + "paramName": "end_date" + } + ], + "endpoint": "earnings_calendar", + "runButton": false, + "gridData": { + "w": 40, + "h": 20 + }, + "data": { + "dataKey": "", + "table": { + "showAll": true, + "columnsDefs": [ + { + "field": "report_date", + "formatterFn": null, + "headerName": "Report Date", + "headerTooltip": "The date of the earnings report.", + "cellDataType": "date" + }, + { + "field": "symbol", + "pinned": "left", + "formatterFn": null, + "headerName": "Symbol", + "headerTooltip": "Symbol representing the entity requested in the data.", + "cellDataType": "text" + }, + { + "field": "name", + "pinned": "left", + "formatterFn": null, + "headerName": "Name", + "headerTooltip": "Name of the entity.", + "cellDataType": "text" + }, + { + "field": "eps_previous", + "formatterFn": null, + "headerName": "EPS Previous", + "headerTooltip": "The earnings-per-share from the same previously reported period.", + "cellDataType": "number" + }, + { + "field": "eps_consensus", + "formatterFn": null, + "headerName": "EPS Consensus", + "headerTooltip": "The analyst conesus earnings-per-share estimate.", + "cellDataType": "number" + }, + { + "field": "eps_actual", + "formatterFn": null, + "headerName": "EPS Actual", + "headerTooltip": "The actual earnings per share (USD) announced.", + "cellDataType": "number" + }, + { + "field": "surprise_percent", + "formatterFn": "normalizedPercent", + "headerName": "Surprise Percent", + "headerTooltip": "The earnings surprise as normalized percentage points.", + "cellDataType": "number", + "renderFn": "greenRed" + }, + { + "field": "num_estimates", + "formatterFn": null, + "headerName": "Num Estimates", + "headerTooltip": "The number of analysts providing estimates for the consensus.", + "cellDataType": "number" + }, + { + "field": "period_ending", + "pinned": "left", + "formatterFn": null, + "headerName": "Period Ending", + "headerTooltip": "The fiscal period end date.", + "cellDataType": "text" + }, + { + "field": "previous_report_date", + "formatterFn": null, + "headerName": "Previous Report Date", + "headerTooltip": "The previous report date for the same period last year.", + "cellDataType": "date" + }, + { + "field": "reporting_time", + "formatterFn": null, + "headerName": "Reporting Time", + "headerTooltip": "The reporting time - e.g. after market close.", + "cellDataType": "text" + }, + { + "field": "market_cap", + "formatterFn": null, + "headerName": "Market Cap", + "headerTooltip": "The market cap (USD) of the reporting entity.", + "cellDataType": "number" + } + ] + } + }, + "source": [ + "Custom" + ], + "subCategory": "Calendar", + "refetchInterval": false + }, + "nasdaq_dividends_calendar": { + "name": "Dividend Calendar", + "description": "Upcoming, and historical, dividend payments (US Markets).", + "category": "Equity", + "type": "table", + "searchCategory": "Equity", + "widgetId": "nasdaq_dividends_calendar", + "params": [ + { + "label": "Start Date", + "description": "Start date of the data.", + "optional": true, + "type": "date", + "value": null, + "show": true, + "paramName": "start_date" + }, + { + "label": "End Date", + "description": "End date of the data.", + "optional": true, + "type": "date", + "value": null, + "show": true, + "paramName": "end_date" + } + ], + "endpoint": "dividend_calendar", + "runButton": false, + "gridData": { + "w": 40, + "h": 20 + }, + "data": { + "dataKey": "", + "table": { + "showAll": true, + "columnsDefs": [ + { + "field": "ex_dividend_date", + "formatterFn": null, + "headerName": "Ex Dividend Date", + "headerTooltip": "The ex-dividend date - the date on which the stock begins trading without rights to the dividend.", + "cellDataType": "date" + }, + { + "field": "symbol", + "pinned": "left", + "formatterFn": null, + "headerName": "Symbol", + "headerTooltip": "Symbol representing the entity requested in the data.", + "cellDataType": "text" + }, + { + "field": "amount", + "formatterFn": null, + "headerName": "Amount", + "headerTooltip": "The dividend amount per share.", + "cellDataType": "number" + }, + { + "field": "name", + "pinned": "left", + "formatterFn": null, + "headerName": "Name", + "headerTooltip": "Name of the entity.", + "cellDataType": "text" + }, + { + "field": "record_date", + "formatterFn": null, + "headerName": "Record Date", + "headerTooltip": "The record date of ownership for eligibility.", + "cellDataType": "date" + }, + { + "field": "payment_date", + "formatterFn": null, + "headerName": "Payment Date", + "headerTooltip": "The payment date of the dividend.", + "cellDataType": "date" + }, + { + "field": "declaration_date", + "formatterFn": null, + "headerName": "Declaration Date", + "headerTooltip": "Declaration date of the dividend.", + "cellDataType": "date" + }, + { + "field": "annualized_amount", + "formatterFn": null, + "headerName": "Annualized Amount", + "headerTooltip": "The indicated annualized dividend amount.", + "cellDataType": "number" + } + ] + } + }, + "source": [ + "Custom" + ], + "subCategory": "Calendar", + "refetchInterval": false + }, + "nasdaq_ipo_calendar": { + "name": "IPO Calendar", + "description": "IPO announcements and calendar.", + "category": "Equity", + "type": "table", + "searchCategory": "Equity", + "widgetId": "nasdaq_ipo_calendar", + "params": [ + { + "label": "Start Date", + "description": "Start date of the data, in YYYY-MM-DD format.", + "optional": true, + "type": "date", + "value": null, + "show": true, + "paramName": "start_date" + }, + { + "label": "End Date", + "description": "End date of the data, in YYYY-MM-DD format.", + "optional": true, + "type": "date", + "value": null, + "show": true, + "paramName": "end_date" + }, + { + "label": "Status", + "description": "Status of the IPO. [upcoming, priced, or withdrawn]", + "optional": true, + "type": "text", + "show": true, + "options": [ + { + "label": "upcoming", + "value": "upcoming" + }, + { + "label": "priced", + "value": "priced" + }, + { + "label": "filed", + "value": "filed" + }, + { + "label": "withdrawn", + "value": "withdrawn" + } + ], + "paramName": "status" + }, + { + "label": "Is SPO", + "description": "If True, returns data for secondary public offerings (SPOs).", + "optional": true, + "type": "boolean", + "value": false, + "show": true, + "paramName": "is_spo" + } + ], + "endpoint": "ipo_calendar", + "runButton": false, + "gridData": { + "w": 40, + "h": 20 + }, + "data": { + "dataKey": "", + "table": { + "showAll": true, + "columnsDefs": [ + { + "field": "symbol", + "pinned": "left", + "formatterFn": null, + "headerName": "Symbol", + "headerTooltip": "Symbol representing the entity requested in the data.", + "cellDataType": "text" + }, + { + "field": "ipo_date", + "formatterFn": null, + "headerName": "IPO Date", + "headerTooltip": "The date of the IPO, when the stock first trades on a major exchange.", + "cellDataType": "date" + }, + { + "field": "name", + "pinned": "left", + "formatterFn": null, + "headerName": "Name", + "headerTooltip": "The name of the company.", + "cellDataType": "text" + }, + { + "field": "offer_amount", + "formatterFn": null, + "headerName": "Offer Amount", + "headerTooltip": "The dollar value of the shares offered.", + "cellDataType": "number" + }, + { + "field": "share_count", + "formatterFn": null, + "headerName": "Share Count", + "headerTooltip": "The number of shares offered.", + "cellDataType": "number" + }, + { + "field": "expected_price_date", + "formatterFn": null, + "headerName": "Expected Price Date", + "headerTooltip": "The date the pricing is expected.", + "cellDataType": "date" + }, + { + "field": "filed_date", + "formatterFn": null, + "headerName": "Filed Date", + "headerTooltip": "The date the IPO was filed.", + "cellDataType": "date" + }, + { + "field": "withdraw_date", + "formatterFn": null, + "headerName": "Withdraw Date", + "headerTooltip": "The date the IPO was withdrawn.", + "cellDataType": "date" + }, + { + "field": "deal_status", + "formatterFn": null, + "headerName": "Deal Status", + "headerTooltip": "The status of the deal.", + "cellDataType": "text" + } + ] + } + }, + "source": [ + "Custom" + ], + "subCategory": "Calendar", + "refetchInterval": false + }, + "nasdaq_economic_calendar": { + "name": "Economic Calendar", + "description": "Upcoming, and historical, macroeconomic events.", + "category": "Economy", + "type": "table", + "searchCategory": "Equity", + "widgetId": "nasdaq_economic_calendar", + "params": [ + { + "label": "Start Date", + "description": "Start Date", + "optional": true, + "type": "date", + "value": null, + "show": true, + "paramName": "start_date" + }, + { + "label": "End Date", + "description": "End Date", + "optional": true, + "type": "date", + "value": null, + "show": true, + "paramName": "end_date" + }, + { + "label": "Country", + "description": "Country", + "optional": true, + "type": "text", + "value": null, + "show": true, + "paramName": "country", + "options": [ + { + "label": "United States", + "value": "united_states" + }, + { + "label": "China", + "value": "china" + }, + { + "label": "Hong Kong", + "value": "hong_kong" + }, + { + "label": "Japan", + "value": "japan" + }, + { + "label": "Singapore", + "value": "signapore" + }, + { + "label": "South Korea", + "value": "south_korea" + }, + { + "label": "India", + "value": "india" + }, + { + "label": "Russia", + "value": "russia" + }, + { + "label": "Euro Zone", + "value": "euro_zone" + }, + { + "label": "Germany", + "value": "germany" + }, + { + "label": "United Kingdom", + "value": "united_kingdom" + }, + { + "label": "France", + "value": "france" + }, + { + "label": "Spain", + "value": "spain" + }, + { + "label": "Italy", + "value": "italy" + }, + { + "label": "Switzerland", + "value": "switzerland" + }, + { + "label": "Canada", + "value": "canada" + }, + { + "label": "Australia", + "value": "australia" + }, + { + "label": "New Zealand", + "value": "new_zealand" + }, + { + "label": "South Africa", + "value": "south_africa" + }, + { + "label": "Brazil", + "value": "brazil" + } + ] + } + ], + "endpoint": "economic_calendar", + "runButton": false, + "gridData": { + "w": 40, + "h": 20 + }, + "data": { + "dataKey": "", + "table": { + "showAll": false, + "columnsDefs": [ + { + "field": "date", + "pinned": "left", + "headerName": "Date", + "headerTooltip": "The date of the data.", + "cellDataType": "date" + }, + { + "field": "country", + "headerName": "Country", + "headerTooltip": "Country of event.", + "cellDataType": "text" + }, + { + "field": "event", + "renderFn": "hoverCard", + "renderFnParams": { + "hoverCard": { + "cellField": "value", + "markdown": "{description}" + } + }, + "headerName": "Event", + "headerTooltip": "Event name.", + "cellDataType": "text" + }, + { + "field": "consensus", + "headerName": "Consensus", + "headerTooltip": "Average forecast among a representative group of economists.", + "cellDataType": "number" + }, + { + "field": "actual", + "headerName": "Actual", + "headerTooltip": "Latest released value.", + "cellDataType": "number" + }, + { + "field": "previous", + "headerName": "Previous", + "headerTooltip": "Value for the previous period after the revision (if revision is applicable).", + "cellDataType": "number" + }, + { + "field": "revised", + "headerName": "Revised", + "headerTooltip": "Revised previous value, if applicable.", + "cellDataType": "number" + } + ] + } + }, + "source": [ + "Custom" + ], + "subCategory": "Calendar", + "refetchInterval": false + } +} \ No newline at end of file diff --git a/openbb_platform/providers/nasdaq/tests/test_nasdaq_app.py b/openbb_platform/providers/nasdaq/tests/test_nasdaq_app.py new file mode 100644 index 00000000000..476b3d3bd1e --- /dev/null +++ b/openbb_platform/providers/nasdaq/tests/test_nasdaq_app.py @@ -0,0 +1,63 @@ +"""App tests for Nasdaq provider.""" + +# flake8: noqa:E501 +# pylint: disable=redefined-outer-name, unused-argument, line-too-long +import json +import os +from pathlib import Path + +import pytest +from fastapi import FastAPI +from openbb_nasdaq.app import main +from openbb_platform_api.utils.widgets import build_json + + +@pytest.fixture(scope="module") +def nasdaq_app(): + """Fixture to serve FastAPI app instance.""" + app = main() + yield app + + +@pytest.fixture(scope="module") +def expected_apps(): + """Load expected apps.json data.""" + current_dir = Path(os.path.dirname(os.path.abspath(__file__))) + with open(current_dir / "record" / "expected_apps.json", encoding="utf-8") as f: + return json.load(f) + + +@pytest.fixture(scope="module") +def expected_widgets(): + """Load expected widgets.json data.""" + current_dir = Path(os.path.dirname(os.path.abspath(__file__))) + with open(current_dir / "record" / "expected_widgets.json", encoding="utf-8") as f: + return json.load(f) + + +def test_app_is_fastapi_instance(nasdaq_app): + """Test that the factory function returns a FastAPI instance.""" + assert isinstance(nasdaq_app, FastAPI) + + +def test_app_has_routes(nasdaq_app): + """Test that the app has at least one route.""" + assert len(nasdaq_app.routes) > 0 + + +@pytest.mark.asyncio +async def test_apps_json(nasdaq_app, expected_apps): + """Test the /apps.json endpoint. This looks for changes and verifies the endpoint works.""" + route = [d for d in nasdaq_app.routes if d.path == "/apps.json"][0] + response = await route.endpoint() + assert isinstance(response, dict) + assert response == expected_apps + + +def test_widgets_json(nasdaq_app, expected_widgets): + """Test the /widgets.json endpoint. This looks for changes and verifies that the widgets are being generated correctly.""" + openapi_json: dict = nasdaq_app.openapi() + assert isinstance(openapi_json, dict) + response = build_json(openapi_json, []) + assert isinstance(response, dict) + assert response == expected_widgets