summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-02-18 09:24:28 -0800
committerGitHub <noreply@github.com>2024-02-18 17:24:28 +0000
commit9f1891931cca50ea43587bc38dfeb49006222a65 (patch)
treec464c21a4e9903dd551fe26e57152ad8842ad0a7
parent60a93c32669a81c1092a6ed8c118a0d8d89d36d9 (diff)
[Enhancement] Add `analyst_search` to the Equity/Estimates Router (#6088)
* standardize price_target * add analyst search * black * more black * ruff * linter, test parmas * last missing param * get rid of convert to upper * ruff * pylint
-rw-r--r--openbb_platform/core/openbb_core/provider/standard_models/analyst_search.py50
-rw-r--r--openbb_platform/core/openbb_core/provider/standard_models/price_target.py74
-rw-r--r--openbb_platform/extensions/equity/integration/test_equity_api.py39
-rw-r--r--openbb_platform/extensions/equity/integration/test_equity_python.py36
-rw-r--r--openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py47
-rw-r--r--openbb_platform/openbb/package/equity_estimates.py315
-rw-r--r--openbb_platform/openbb/package/module_map.json1
-rw-r--r--openbb_platform/providers/benzinga/openbb_benzinga/__init__.py2
-rw-r--r--openbb_platform/providers/benzinga/openbb_benzinga/models/analyst_search.py393
-rw-r--r--openbb_platform/providers/benzinga/openbb_benzinga/models/price_target.py319
-rw-r--r--openbb_platform/providers/benzinga/tests/record/http/test_benzinga_fetchers/test_benzinga_analyst_search_fetcher.yaml282
-rw-r--r--openbb_platform/providers/benzinga/tests/record/http/test_benzinga_fetchers/test_benzinga_price_target_fetcher.yaml293
-rw-r--r--openbb_platform/providers/benzinga/tests/test_benzinga_fetchers.py10
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/equity_profile.py2
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/price_target.py44
15 files changed, 1528 insertions, 379 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/analyst_search.py b/openbb_platform/core/openbb_core/provider/standard_models/analyst_search.py
new file mode 100644
index 00000000000..c4b13b4067c
--- /dev/null
+++ b/openbb_platform/core/openbb_core/provider/standard_models/analyst_search.py
@@ -0,0 +1,50 @@
+"""Analyst Search Standard Model."""
+
+from datetime import (
+ datetime,
+)
+from typing import Optional
+
+from pydantic import Field
+
+from openbb_core.provider.abstract.data import Data
+from openbb_core.provider.abstract.query_params import QueryParams
+
+
+class AnalystSearchQueryParams(QueryParams):
+ """Analyst Search Query."""
+
+ analyst_name: Optional[str] = Field(
+ default=None,
+ description="A comma separated list of analyst names to bring back."
+ + " Omitting will bring back all available analysts.",
+ )
+ firm_name: Optional[str] = Field(
+ default=None,
+ description="A comma separated list of firm names to bring back."
+ + " Omitting will bring back all available firms.",
+ )
+
+
+class AnalystSearchData(Data):
+ """Analyst Search data."""
+
+ last_updated: Optional[datetime] = Field(
+ default=None,
+ description="Date of the last update.",
+ )
+ firm_name: Optional[str] = Field(
+ default=None,
+ description="Firm name of the analyst.",
+ )
+ name_first: Optional[str] = Field(
+ default=None,
+ description="Analyst first name.",
+ )
+ name_last: Optional[str] = Field(
+ default=None,
+ description="Analyst last name.",
+ )
+ name_full: str = Field(
+ description="Analyst full name.",
+ )
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/price_target.py b/openbb_platform/core/openbb_core/provider/standard_models/price_target.py
index 6c8babd0d3a..5481ec9ba5b 100644
--- a/openbb_platform/core/openbb_core/provider/standard_models/price_target.py
+++ b/openbb_platform/core/openbb_core/provider/standard_models/price_target.py
@@ -1,7 +1,11 @@
"""Price Target Standard Model."""
-from datetime import datetime
-from typing import List, Optional, Set, Union
+from datetime import (
+ date as dateType,
+ datetime,
+ time,
+)
+from typing import Optional, Union
from pydantic import Field, NonNegativeInt, field_validator
@@ -16,49 +20,67 @@ from openbb_core.provider.utils.descriptions import (
class PriceTargetQueryParams(QueryParams):
"""Price Target Query."""
- symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
+ symbol: Optional[str] = Field(
+ default=None, description=QUERY_DESCRIPTIONS.get("symbol", "")
+ )
limit: NonNegativeInt = Field(
- default=100, description=QUERY_DESCRIPTIONS.get("limit", "")
+ default=200, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
- def upper_symbol(cls, v: str) -> str:
+ def upper_symbol(cls, v: str):
"""Convert symbol to uppercase."""
- return v.upper()
+ return v.upper() if v else None
class PriceTargetData(Data):
"""Price Target Data."""
+ published_date: Union[dateType, datetime] = Field(
+ description="Published date of the price target."
+ )
+ published_time: Optional[time] = Field(
+ default=None, description="Time of the original rating, UTC."
+ )
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
- published_date: datetime = Field(description="Published date of the price target.")
- news_url: Optional[str] = Field(
- default=None, description="News URL of the price target."
+ exchange: Optional[str] = Field(
+ default=None, description="Exchange where the company is traded."
)
- news_title: Optional[str] = Field(
- default=None, description="News title of the price target."
+ company_name: Optional[str] = Field(
+ default=None, description="Name of company that is the subject of rating."
)
analyst_name: Optional[str] = Field(default=None, description="Analyst name.")
- analyst_company: Optional[str] = Field(default=None, description="Analyst company.")
- price_target: Optional[float] = Field(default=None, description="Price target.")
+ analyst_firm: Optional[str] = Field(
+ default=None,
+ description="Name of the analyst firm that published the price target.",
+ )
+ currency: Optional[str] = Field(
+ default=None, description="Currency the data is denominated in."
+ )
+ price_target: Optional[float] = Field(
+ default=None, description="The current price target."
+ )
adj_price_target: Optional[float] = Field(
- default=None, description="Adjusted price target."
+ default=None,
+ description="Adjusted price target for splits and stock dividends.",
+ )
+ price_target_previous: Optional[float] = Field(
+ default=None, description="Previous price target."
+ )
+ previous_adj_price_target: Optional[float] = Field(
+ default=None, description="Previous adjusted price target."
)
price_when_posted: Optional[float] = Field(
default=None, description="Price when posted."
)
- news_publisher: Optional[str] = Field(
- default=None, description="News publisher of the price target."
+ rating_current: Optional[str] = Field(
+ default=None, description="The analyst's rating for the company."
)
- news_base_url: Optional[str] = Field(
- default=None, description="News base URL of the price target."
+ rating_previous: Optional[str] = Field(
+ default=None, description="Previous analyst rating for the company."
+ )
+ action: Optional[str] = Field(
+ default=None,
+ description="Description of the change in rating from firm's last rating.",
)
-
- @field_validator("symbol", mode="before", check_fields=False)
- @classmethod
- def upper_symbol(cls, v: Union[str, List[str], Set[str]]):
- """Convert symbol to uppercase."""
- if isinstance(v, str):
- return v.upper()
- return ",".join([symbol.upper() for symbol in list(v)])
diff --git a/openbb_platform/extensions/equity/integration/test_equity_api.py b/openbb_platform/extensions/equity/integration/test_equity_api.py
index 4a69d38954a..30dddf91234 100644
--- a/openbb_platform/extensions/equity/integration/test_equity_api.py
+++ b/openbb_platform/extensions/equity/integration/test_equity_api.py
@@ -627,7 +627,7 @@ def test_equity_ownership_major_holders(params, headers):
@parametrize(
"params",
[
- ({"symbol": "AAPL", "limit": 10, "provider": "fmp"}),
+ ({"symbol": "AAPL", "limit": 10, "provider": "fmp", "with_grade": True}),
({"symbol": "AAPL", "provider": "finviz"}),
(
{
@@ -637,13 +637,14 @@ def test_equity_ownership_major_holders(params, headers):
# optional provider params
"fields": None,
"date": None,
- "date_from": None,
- "date_to": None,
+ "start_date": None,
+ "end_date": None,
"importance": None,
"updated": None,
"action": None,
- "analyst": None,
- "firm": None,
+ "analyst_ids": None,
+ "firm_ids": None,
+ "page": 0,
}
),
],
@@ -662,6 +663,34 @@ def test_equity_estimates_price_target(params, headers):
@parametrize(
"params",
[
+ (
+ {
+ "limit": 10,
+ "provider": "benzinga",
+ # optional provider params
+ "fields": None,
+ "analyst_ids": None,
+ "firm_ids": None,
+ "firm_name": "Barclays",
+ "analyst_name": None,
+ }
+ ),
+ ],
+)
+@pytest.mark.integration
+def test_equity_estimates_analyst_search(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/equity/estimates/analyst_search?{query_str}"
+ result = requests.get(url, headers=headers, timeout=10)
+ assert isinstance(result, requests.Response)
+ assert result.status_code == 200
+
+
+@parametrize(
+ "params",
+ [
({"symbol": "AAPL", "provider": "fmp"}),
({"symbol": "AAPL,AMZN,RELIANCE.NS", "provider": "yfinance"}),
],
diff --git a/openbb_platform/extensions/equity/integration/test_equity_python.py b/openbb_platform/extensions/equity/integration/test_equity_python.py
index 88bb010785b..d7f4127da35 100644
--- a/openbb_platform/extensions/equity/integration/test_equity_python.py
+++ b/openbb_platform/extensions/equity/integration/test_equity_python.py
@@ -586,7 +586,7 @@ def test_equity_ownership_major_holders(params, obb):
@parametrize(
"params",
[
- ({"symbol": "AAPL", "limit": 10, "provider": "fmp"}),
+ ({"symbol": "AAPL", "limit": 10, "provider": "fmp", "with_grade": True}),
(
{
"symbol": "AAPL",
@@ -595,13 +595,14 @@ def test_equity_ownership_major_holders(params, obb):
# optional provider params
"fields": None,
"date": None,
- "date_from": None,
- "date_to": None,
+ "start_date": None,
+ "end_date": None,
"importance": None,
"updated": None,
"action": None,
- "analyst": None,
- "firm": None,
+ "analyst_ids": None,
+ "firm_ids": None,
+ "page": 0,
}
),
({"symbol": "AAPL", "provider": "finviz"}),
@@ -618,6 +619,31 @@ def test_equity_estimates_price_target(params, obb):
@parametrize(
"params",
[
+ (
+ {
+ "limit": 10,
+ "provider": "benzinga",
+ # optional provider params
+ "fields": None,
+ "analyst_ids": None,
+ "firm_ids": None,
+ "firm_name": "Barclays",
+ "analyst_name": None,
+ }
+ ),
+ ],
+)
+@pytest.mark.integration
+def test_equity_estimates_analyst_search(params, obb):
+ result = obb.equity.estimates.analyst_search(**params)
+ assert result
+ assert isinstance(result, OBBject)
+ assert result.results is not None
+
+
+@parametrize(
+ "params",
+ [
({"symbol": "AAPL", "provider": "fmp"}),
({"symbol": "AAPL,AMZN,RELIANCE.NS", "provider": "yfinance"}),
],
diff --git a/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py b/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py
index b02585e8e05..b1fff34159f 100644
--- a/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py
+++ b/openbb_platform/extensions/equity/openbb_equity/estimates/estimates_router.py
@@ -15,34 +15,69 @@ router = Router(prefix="/estimates")
# pylint: disable=unused-argument
-@router.command(model="PriceTarget")
+@router.command(
+ model="PriceTarget",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.equity.estimates.price_target(start_date="2020-01-01", end_date="2024-02-16",limit=10, symbol="msft", provider="benzinga",action="downgrades").to_df()' # noqa: E501 pylint: disable=line-too-long
+ ],
+)
async def price_target(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
- """Price Target. Price target data."""
+ """Analyst price targets by company."""
return await OBBject.from_query(Query(**locals()))
-@router.command(model="AnalystEstimates")
+@router.command(
+ model="AnalystEstimates",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.equity.estimates.historical("AAPL", period="quarter", provider="fmp").to_df()',
+ ],
+)
async def historical(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
- """Historical Analyst Estimates. Analyst stock recommendations."""
+ """Historical analyst estimates for earnings and revenue."""
return await OBBject.from_query(Query(**locals()))
-@router.command(model="PriceTargetConsensus")
+@router.command(
+ model="PriceTargetConsensus",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.equity.estimates.consensus("AAPL,MSFT", provider="yfinance").to_df()'
+ ],
+)
async def consensus(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
- """Price Target Consensus. Price target consensus data."""
+ """Consensus price target and recommendation."""
+ return await OBBject.from_query(Query(**locals()))
+
+
+@router.command(
+ model="AnalystSearch",
+ exclude_auto_examples=True,
+ examples=[
+ 'obb.equity.estimates.analyst_search(firm_name="Wedbush", provider="benzinga").to_df()',
+ ],
+)
+async def analyst_search(
+ cc: CommandContext,
+ provider_choices: ProviderChoices,
+ standard_params: StandardParams,
+ extra_params: ExtraParams,
+) -> OBBject:
+ """Search for specific analysts and get their forecast track record."""
return await OBBject.from_query(Query(**locals()))
diff --git a/openbb_platform/openbb/package/equity_estimates.py b/openbb_platform/openbb/package/equity_estimates.py
index 4d318e341f9..f27dae4eaa3 100644
--- a/openbb_platform/openbb/package/equity_estimates.py
+++ b/openbb_platform/openbb/package/equity_estimates.py
@@ -12,6 +12,7 @@ from typing_extensions import Annotated
class ROUTER_equity_estimates(Container):
"""/equity/estimates
+ analyst_search
consensus
historical
price_target
@@ -21,6 +22,176 @@ class ROUTER_equity_estimates(Container):
return self.__doc__ or ""
@validate
+ def analyst_search(
+ self,
+ analyst_name: Annotated[
+ Optional[str],
+ OpenBBCustomParameter(
+ description="A comma separated list of analyst names to bring back. Omitting will bring back all available analysts."
+ ),
+ ] = None,
+ firm_name: Annotated[
+ Optional[str],
+ OpenBBCustomParameter(
+ description="A comma separated list of firm names to bring back. Omitting will bring back all available firms."
+ ),
+ ] = None,
+ provider: Optional[Literal["benzinga"]] = None,
+ **kwargs
+ ) -> OBBject:
+ """Search for specific analysts and get their forecast track record.
+
+ Parameters
+ ----------
+ analyst_name : Optional[str]
+ A comma separated list of analyst names to bring back. Omitting will bring back all available analysts.
+ firm_name : Optional[str]
+ A comma separated list of firm names to bring back. Omitting will bring back all available firms.
+ provider : Optional[Literal['benzinga']]
+ The provider to use for the query, by default None.
+ If None, the provider specified in defaults is selected or 'benzinga' if there is
+ no default.
+ analyst_ids : Optional[Union[str, List[str]]]
+ A comma separated list of analyst IDs to bring back. (provider: benzinga)
+ firm_ids : Optional[Union[str, List[str]]]
+ A comma separated list of firm IDs to bring back. (provider: benzinga)
+ limit : Optional[int]
+ Number of results returned. Limit 1000. (provider: benzinga)
+ page : Optional[int]
+ Page offset. For optimization, performance and technical reasons, page offsets are limited from 0 - 100000. Limit the query results by other parameters such as date. (provider: benzinga)
+ fields : Optional[Union[str, List[str]]]
+ Comma-separated list of fields to include in the response. See https://docs.benzinga.io/benzinga-apis/calendar/get-ratings to learn about the available fields. (provider: benzinga)
+
+ Returns
+ -------
+ OBBject
+ results : List[AnalystSearch]
+ Serializable results.
+ provider : Optional[Literal['benzinga']]
+ Provider name.
+ warnings : Optional[List[Warning_]]
+ List of warnings.
+ chart : Optional[Chart]
+ Chart object.
+ extra: Dict[str, Any]
+ Extra info.
+
+ AnalystSearch
+ -------------
+ last_updated : Optional[datetime]
+ Date of the last update.
+ firm_name : Optional[str]
+ Firm name of the analyst.
+ name_first : Optional[str]
+ Analyst first name.
+ name_last : Optional[str]
+ Analyst last name.
+ name_full : str
+ Analyst full name.
+ analyst_id : Optional[str]
+ ID of the analyst. (provider: benzinga)
+ firm_id : Optional[str]
+ ID of the analyst firm. (provider: benzinga)
+ smart_score : Optional[float]
+ A weighted average of the total_ratings_percentile, overall_avg_return_percentile, and overall_success_rate (provider: benzinga)
+ overall_success_rate : Optional[float]
+ The percentage (normalized) of gain/loss ratings that resulted in a gain overall. (provider: benzinga)
+ overall_avg_return_percentile : Optional[float]
+ The percentile (normalized) of this analyst's overall average return per rating in comparison to other analysts' overall average returns per rating. (provider: benzinga)
+ total_ratings_percentile : Optional[float]
+ The percentile (normalized) of this analyst's total number of ratings in comparison to the total number of ratings published by all other analysts (provider: benzinga)
+ total_ratings : Optional[int]
+ Number of recommendations made by this analyst. (provider: benzinga)
+ overall_gain_count : Optional[int]
+ The number of ratings that have gained value since the date of recommendation (provider: benzinga)
+ overall_loss_count : Optional[int]
+ The number of ratings that have lost value since the date of recommendation (provider: benzinga)
+ overall_average_return : Optional[float]
+ The average percent (normalized) price difference per rating since the date of recommendation (provider: benzinga)
+ overall_std_dev : Optional[float]
+ The standard deviation in percent (normalized) price difference in the analyst's ratings since the date of recommendation (provider: benzinga)
+ gain_count_1m : Optional[int]
+ The number of ratings that have gained value over the last month (provider: benzinga)
+ loss_count_1m : Optional[int]
+ The number of ratings that have lost value over the last month (provider: benzinga)
+ average_return_1m : Optional[float]
+ The average percent (normalized) price difference per rating over the last month (provider: benzinga)
+ std_dev_1m : Optional[float]
+ The standard deviation in percent (normalized) price difference in the analyst's ratings over the last month (provider: benzinga)
+ gain_count_3m : Optional[int]
+ The number of ratings that have gained value over the last 3 months (provider: benzinga)
+ loss_count_3m : Optional[int]
+ The number of ratings that have lost value over the last 3 months (provider: benzinga)
+ average_return_3m : Optional[float]
+ The average percent (normalized) price difference per rating over the last 3 months (provider: benzinga)
+ std_dev_3m : Optional[float]
+ The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 3 months (provider: benzinga)
+ gain_count_6m : Optional[int]
+ The number of ratings that have gained value over the last 6 months (provider: benzinga)
+ loss_count_6m : Optional[int]
+ The number of ratings that have lost value over the last 6 months (provider: benzinga)
+ average_return_6m : Optional[float]
+ The average percent (normalized) price difference per rating over the last 6 months (provider: benzinga)
+ std_dev_6m : Optional[float]
+ The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 6 months (provider: benzinga)
+ gain_count_9m : Optional[int]
+ The number of ratings that have gained value over the last 9 months (provider: benzinga)
+ loss_count_9m : Optional[int]
+ The number of ratings that have lost value over the last 9 months (provider: benzinga)
+ average_return_9m : Optional[float]
+ The average percent (normalized) price difference per rating over the last 9 months (provider: benzinga)
+ std_dev_9m : Optional[float]
+ The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 9 months (provider: benzinga)
+ gain_count_1y : Optional[int]
+ The number of ratings that have gained value over the last 1 year (provider: benzinga)
+ loss_count_1y : Optional[int]
+ The number of ratings that have lost value over the last 1 year (provider: benzinga)
+ average_return_1y : Optional[float]
+ The average percent (normalized) price difference per rating over the last 1 year (provider: benzinga)
+ std_dev_1y : Optional[float]
+ The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 1 year (provider: benzinga)
+ gain_count_2y : Optional[int]
+ The number of ratings that have gained value over the last 2 years (provider: benzinga)
+ loss_count_2y : Optional[int]
+ The number of ratings that have lost value over the last 2 years (provider: benzinga)
+ average_return_2y : Optional[float]
+ The average percent (normalized) price difference per rating over the last 2 years (provider: benzinga)
+ std_dev_2y : Optional[float]
+ The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 2 years (provider: benzinga)
+ gain_count_3y : Optional[int]
+ The number of ratings that have gained value over the last 3 years (provider: benzinga)
+ loss_count_3y : Optional[int]
+ The number of ratings that have lost value over the last 3 years (provider: benzinga)
+ average_return_3y : Optional[float]
+ The average percent (normalized) price difference per rating over the last 3 years (provider: benzinga)
+ std_dev_3y : Optional[float]
+ The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 3 years (provider: benzinga)
+
+ Example
+ -------
+ >>> from openbb import obb
+ >>> obb.equity.estimates.analyst_search(firm_name="Wedbush", provider="benzinga").to_df()
+ """ # noqa: E501
+
+ return self._run(
+ "/equity/estimates/analyst_search",
+ **filter_inputs(
+ provider_choices={
+ "provider": self._get_provider(
+ provider,
+ "/equity/estimates/analyst_search",
+ ("benzinga",),
+ )
+ },
+ standard_params={
+ "analyst_name": analyst_name,
+ "firm_name": firm_name,
+ },
+ extra_params=kwargs,
+ )
+ )
+
+ @validate
def consensus(
self,
symbol: Annotated[
@@ -32,7 +203,7 @@ class ROUTER_equity_estimates(Container):
provider: Optional[Literal["fmp", "yfinance"]] = None,
**kwargs
) -> OBBject:
- """Price Target Consensus. Price target consensus data.
+ """Consensus price target and recommendation.
Parameters
----------
@@ -83,7 +254,7 @@ class ROUTER_equity_estimates(Container):
Example
-------
>>> from openbb import obb
- >>> obb.equity.estimates.consensus(symbol="AAPL")
+ >>> obb.equity.estimates.consensus("AAPL,MSFT", provider="yfinance").to_df()
""" # noqa: E501
return self._run(
@@ -121,7 +292,7 @@ class ROUTER_equity_estimates(Container):
provider: Optional[Literal["fmp"]] = None,
**kwargs
) -> OBBject:
- """Historical Analyst Estimates. Analyst stock recommendations.
+ """Historical analyst estimates for earnings and revenue.
Parameters
----------
@@ -200,7 +371,7 @@ class ROUTER_equity_estimates(Container):
Example
-------
>>> from openbb import obb
- >>> obb.equity.estimates.historical(symbol="AAPL", period="annual", limit=30)
+ >>> obb.equity.estimates.historical("AAPL", period="quarter", provider="fmp").to_df()
""" # noqa