[Feature] Intrinio ETF Price Performance (#6220)

* remap to etf router

* add the intrinio files this time

* update standard model

* the rest of the changes

* static files

* black

* pylint

* test thing

* black

* fix test?

* try another way?

* ruff

* maybe?

* new cassette?
This commit is contained in:
Danglewood
2024-03-15 05:24:55 -07:00
committed by GitHub
parent 9b0d34076f
commit 46f4834cb2
14 changed files with 724 additions and 65 deletions

View File

@@ -6,7 +6,10 @@ from pydantic import Field, field_validator
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
class RecentPerformanceQueryParams(QueryParams):
@@ -22,23 +25,88 @@ class RecentPerformanceQueryParams(QueryParams):
class RecentPerformanceData(Data):
"""Recent Performance Data."""
"""Recent Performance Data. All returns are normalized percents."""
one_day: Optional[float] = Field(description="One-day return.", default=None)
wtd: Optional[float] = Field(description="Week to date return.", default=None)
one_week: Optional[float] = Field(description="One-week return.", default=None)
mtd: Optional[float] = Field(description="Month to date return.", default=None)
one_month: Optional[float] = Field(description="One-month return.", default=None)
qtd: Optional[float] = Field(description="Quarter to date return.", default=None)
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
one_day: Optional[float] = Field(
default=None,
description="One-day return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
wtd: Optional[float] = Field(
default=None,
description="Week to date return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
one_week: Optional[float] = Field(
default=None,
description="One-week return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
mtd: Optional[float] = Field(
default=None,
description="Month to date return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
one_month: Optional[float] = Field(
default=None,
description="One-month return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
qtd: Optional[float] = Field(
default=None,
description="Quarter to date return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
three_month: Optional[float] = Field(
description="Three-month return.", default=None
default=None,
description="Three-month return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
six_month: Optional[float] = Field(
default=None,
description="Six-month return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
ytd: Optional[float] = Field(
default=None,
description="Year to date return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
one_year: Optional[float] = Field(
default=None,
description="One-year return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
two_year: Optional[float] = Field(
default=None,
description="Two-year return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
three_year: Optional[float] = Field(
default=None,
description="Three-year return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
four_year: Optional[float] = Field(
default=None,
description="Four-year",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
five_year: Optional[float] = Field(
default=None,
description="Five-year return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
ten_year: Optional[float] = Field(
default=None,
description="Ten-year return.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
six_month: Optional[float] = Field(description="Six-month return.", default=None)
ytd: Optional[float] = Field(description="Year to date return.", default=None)
one_year: Optional[float] = Field(description="One-year return.", default=None)
three_year: Optional[float] = Field(description="Three-year return.", default=None)
five_year: Optional[float] = Field(description="Five-year return.", default=None)
ten_year: Optional[float] = Field(description="Ten-year return.", default=None)
max: Optional[float] = Field(
description="Return from the beginning of the time series.", default=None
default=None,
description="Return from the beginning of the time series.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)

View File

@@ -351,6 +351,14 @@ def test_etf_holdings(params, headers):
[
({"symbol": "SPY,VOO,QQQ,IWM,IWN,GOVT,JNK", "provider": "fmp"}),
({"symbol": "SPY,VOO,QQQ,IWM,IWN,GOVT,JNK", "provider": "finviz"}),
(
{
"symbol": "SPY,VOO,QQQ,IWM,IWN,GOVT,JNK",
"return_type": "trailing",
"adjustment": "splits_and_dividends",
"provider": "intrinio",
}
),
],
)
@pytest.mark.integration

View File

@@ -342,6 +342,14 @@ def test_etf_holdings(params, obb):
[
({"symbol": "SPY,VOO,QQQ,IWM,IWN,GOVT,JNK", "provider": "fmp"}),
({"symbol": "SPY,VOO,QQQ,IWM,IWN,GOVT,JNK", "provider": "finviz"}),
(
{
"symbol": "SPY,VOO,QQQ,IWM,IWN,GOVT,JNK",
"return_type": "trailing",
"adjustment": "splits_and_dividends",
"provider": "intrinio",
}
),
],
)
@pytest.mark.integration

View File

@@ -116,7 +116,7 @@ async def countries(
@router.command(
model="PricePerformance",
model="EtfPricePerformance",
examples=[
APIEx(parameters={"symbol": "QQQ", "provider": "fmp"}),
APIEx(parameters={"symbol": "SPY,QQQ,IWM,DJIA", "provider": "fmp"}),
@@ -128,7 +128,7 @@ async def price_performance(
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Price performance as a return, over different periods. This is a proxy for `equity.price.performance`."""
"""Price performance as a return, over different periods."""
return await OBBject.from_query(Query(**locals()))

View File

@@ -17729,6 +17729,13 @@
},
"data": {
"standard": [
{
"name": "symbol",
"type": "str",
"description": "Symbol representing the entity requested in the data.",
"default": null,
"optional": true
},
{
"name": "one_day",
"type": "float",
@@ -17799,6 +17806,13 @@
"default": null,
"optional": true
},
{
"name": "two_year",
"type": "float",
"description": "Two-year return.",
"default": null,
"optional": true
},
{
"name": "three_year",
"type": "float",
@@ -17806,6 +17820,13 @@
"default": null,
"optional": true
},
{
"name": "four_year",
"type": "float",
"description": "Four-year",
"default": null,
"optional": true
},
{
"name": "five_year",
"type": "float",
@@ -17828,15 +17849,7 @@
"optional": true
}
],
"fmp": [
{
"name": "symbol",
"type": "str",
"description": "The ticker symbol.",
"default": "",
"optional": false
}
]
"fmp": []
},
"model": "PricePerformance"
},
@@ -21252,37 +21265,53 @@
"flag": null,
"message": null
},
"description": "Price performance as a return, over different periods. This is a proxy for `equity.price.performance`.",
"description": "Price performance as a return, over different periods.",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.etf.price_performance(symbol='QQQ', provider='fmp')\nobb.etf.price_performance(symbol='SPY,QQQ,IWM,DJIA', provider='fmp')\n```\n\n",
"parameters": {
"standard": [
{
"name": "symbol",
"type": "Union[str, List[str]]",
"description": "Symbol to get data for. Multiple items allowed for provider(s): fmp.",
"description": "Symbol to get data for. Multiple items allowed for provider(s): fmp, intrinio.",
"default": "",
"optional": false
},
{
"name": "provider",
"type": "Literal['fmp']",
"type": "Literal['fmp', 'intrinio']",
"description": "The provider to use for the query, by default None. If None, the provider specified in defaults is selected or 'fmp' if there is no default.",
"default": "fmp",
"optional": true
}
],
"fmp": []
"fmp": [],
"intrinio": [
{
"name": "return_type",
"type": "Literal['trailing', 'calendar']",
"description": "The type of returns to return, a trailing or calendar window.",
"default": "trailing",
"optional": true
},
{
"name": "adjustment",
"type": "Literal['splits_only', 'splits_and_dividends']",
"description": "The adjustment factor, 'splits_only' will return pure price performance.",
"default": "splits_and_dividends",
"optional": true
}
]
},
"returns": {
"OBBject": [
{
"name": "results",
"type": "List[PricePerformance]",
"type": "List[EtfPricePerformance]",
"description": "Serializable results."
},
{
"name": "provider",
"type": "Optional[Literal['fmp']]",
"type": "Optional[Literal['fmp', 'intrinio']]",
"description": "Provider name."
},
{
@@ -21304,6 +21333,13 @@
},
"data": {
"standard": [
{
"name": "symbol",
"type": "str",
"description": "Symbol representing the entity requested in the data.",
"default": null,
"optional": true
},
{
"name": "one_day",
"type": "float",
@@ -21374,6 +21410,13 @@
"default": null,
"optional": true
},
{
"name": "two_year",
"type": "float",
"description": "Two-year return.",
"default": null,
"optional": true
},
{
"name": "three_year",
"type": "float",
@@ -21381,6 +21424,13 @@
"default": null,
"optional": true
},
{
"name": "four_year",
"type": "float",
"description": "Four-year",
"default": null,
"optional": true
},
{
"name": "five_year",
"type": "float",
@@ -21403,17 +21453,116 @@
"optional": true
}
],
"fmp": [
"fmp": [],
"intrinio": [
{
"name": "symbol",
"type": "str",
"description": "The ticker symbol.",
"default": "",
"optional": false
"name": "max_annualized",
"type": "float",
"description": "Annualized rate of return from inception.",
"default": null,
"optional": true
},
{
"name": "volatility_one_year",
"type": "float",
"description": "Trailing one-year annualized volatility.",
"default": null,
"optional": true
},
{
"name": "volatility_three_year",
"type": "float",
"description": "Trailing three-year annualized volatility.",
"default": null,
"optional": true
},
{
"name": "volatility_five_year",
"type": "float",
"description": "Trailing five-year annualized volatility.",
"default": null,
"optional": true
},
{
"name": "volume",
"type": "int",
"description": "The trading volume.",
"default": null,
"optional": true
},
{
"name": "volume_avg_30",
"type": "float",
"description": "The one-month average daily volume.",
"default": null,
"optional": true
},
{
"name": "volume_avg_90",
"type": "float",
"description": "The three-month average daily volume.",
"default": null,
"optional": true
},
{
"name": "volume_avg_180",
"type": "float",
"description": "The six-month average daily volume.",
"default": null,
"optional": true
},
{
"name": "beta",
"type": "float",
"description": "Beta compared to the S&P 500.",
"default": null,
"optional": true
},
{
"name": "nav",
"type": "float",
"description": "Net asset value per share.",
"default": null,
"optional": true
},
{
"name": "year_high",
"type": "float",
"description": "The 52-week high price.",
"default": null,
"optional": true
},
{
"name": "year_low",
"type": "float",
"description": "The 52-week low price.",
"default": null,
"optional": true
},
{
"name": "market_cap",
"type": "float",
"description": "The market capitalization.",
"default": null,
"optional": true
},
{
"name": "shares_outstanding",
"type": "int",
"description": "The number of shares outstanding.",
"default": null,
"optional": true
},
{
"name": "updated",
"type": "date",
"description": "The date of the data.",
"default": null,
"optional": true
}
]
},
"model": "PricePerformance"
"model": "EtfPricePerformance"
},
"/etf/holdings": {
"deprecated": {
@@ -22317,6 +22466,13 @@
},
"data": {
"standard": [
{
"name": "symbol",
"type": "str",
"description": "Symbol representing the entity requested in the data.",
"default": null,
"optional": true
},
{
"name": "one_day",
"type": "float",
@@ -22387,6 +22543,13 @@
"default": null,
"optional": true
},
{
"name": "two_year",
"type": "float",
"description": "Two-year return.",
"default": null,
"optional": true
},
{
"name": "three_year",
"type": "float",
@@ -22394,6 +22557,13 @@
"default": null,
"optional": true
},
{
"name": "four_year",
"type": "float",
"description": "Four-year",
"default": null,
"optional": true
},
{
"name": "five_year",
"type": "float",
@@ -22416,15 +22586,7 @@
"optional": true
}
],
"fmp": [
{
"name": "symbol",
"type": "str",
"description": "The ticker symbol.",
"default": "",
"optional": false
}
]
"fmp": []
},
"model": "EtfHoldingsPerformance"
},

View File

@@ -716,6 +716,8 @@ class ROUTER_etf(Container):
EtfHoldingsPerformance
----------------------
symbol : Optional[str]
Symbol representing the entity requested in the data.
one_day : Optional[float]
One-day return.
wtd : Optional[float]
@@ -736,16 +738,18 @@ class ROUTER_etf(Container):
Year to date return.
one_year : Optional[float]
One-year return.
two_year : Optional[float]
Two-year return.
three_year : Optional[float]
Three-year return.
four_year : Optional[float]
Four-year
five_year : Optional[float]
Five-year return.
ten_year : Optional[float]
Ten-year return.
max : Optional[float]
Return from the beginning of the time series.
symbol : Optional[str]
The ticker symbol. (provider: fmp)
Examples
--------
@@ -1170,34 +1174,38 @@ class ROUTER_etf(Container):
symbol: Annotated[
Union[str, List[str]],
OpenBBCustomParameter(
description="Symbol to get data for. Multiple items allowed for provider(s): fmp."
description="Symbol to get data for. Multiple items allowed for provider(s): fmp, intrinio."
),
],
provider: Annotated[
Optional[Literal["fmp"]],
Optional[Literal["fmp", "intrinio"]],
OpenBBCustomParameter(
description="The provider to use for the query, by default None.\n If None, the provider specified in defaults is selected or 'fmp' if there is\n no default."
),
] = None,
**kwargs
) -> OBBject:
"""Price performance as a return, over different periods. This is a proxy for `equity.price.performance`.
"""Price performance as a return, over different periods.
Parameters
----------
symbol : Union[str, List[str]]
Symbol to get data for. Multiple items allowed for provider(s): fmp.
provider : Optional[Literal['fmp']]
Symbol to get data for. Multiple items allowed for provider(s): fmp, intrinio.
provider : Optional[Literal['fmp', 'intrinio']]
The provider to use for the query, by default None.
If None, the provider specified in defaults is selected or 'fmp' if there is
no default.
return_type : Literal['trailing', 'calendar']
The type of returns to return, a trailing or calendar window. (provider: intrinio)
adjustment : Literal['splits_only', 'splits_and_dividends']
The adjustment factor, 'splits_only' will return pure price performance. (provider: intrinio)
Returns
-------
OBBject
results : List[PricePerformance]
results : List[EtfPricePerformance]
Serializable results.
provider : Optional[Literal['fmp']]
provider : Optional[Literal['fmp', 'intrinio']]
Provider name.
warnings : Optional[List[Warning_]]
List of warnings.
@@ -1206,8 +1214,10 @@ class ROUTER_etf(Container):
extra : Dict[str, Any]
Extra info.
PricePerformance
----------------
EtfPricePerformance
-------------------
symbol : Optional[str]
Symbol representing the entity requested in the data.
one_day : Optional[float]
One-day return.
wtd : Optional[float]
@@ -1228,16 +1238,48 @@ class ROUTER_etf(Container):
Year to date return.
one_year : Optional[float]
One-year return.
two_year : Optional[float]
Two-year return.
three_year : Optional[float]
Three-year return.
four_year : Optional[float]
Four-year
five_year : Optional[float]
Five-year return.
ten_year : Optional[float]
Ten-year return.
max : Optional[float]
Return from the beginning of the time series.
symbol : Optional[str]
The ticker symbol. (provider: fmp)
max_annualized : Optional[float]
Annualized rate of return from inception. (provider: intrinio)
volatility_one_year : Optional[float]
Trailing one-year annualized volatility. (provider: intrinio)
volatility_three_year : Optional[float]
Trailing three-year annualized volatility. (provider: intrinio)
volatility_five_year : Optional[float]
Trailing five-year annualized volatility. (provider: intrinio)
volume : Optional[int]
The trading volume. (provider: intrinio)
volume_avg_30 : Optional[float]
The one-month average daily volume. (provider: intrinio)
volume_avg_90 : Optional[float]
The three-month average daily volume. (provider: intrinio)
volume_avg_180 : Optional[float]
The six-month average daily volume. (provider: intrinio)
beta : Optional[float]
Beta compared to the S&P 500. (provider: intrinio)
nav : Optional[float]
Net asset value per share. (provider: intrinio)
year_high : Optional[float]
The 52-week high price. (provider: intrinio)
year_low : Optional[float]
The 52-week low price. (provider: intrinio)
market_cap : Optional[float]
The market capitalization. (provider: intrinio)
shares_outstanding : Optional[int]
The number of shares outstanding. (provider: intrinio)
updated : Optional[date]
The date of the data. (provider: intrinio)
Examples
--------
@@ -1253,14 +1295,14 @@ class ROUTER_etf(Container):
"provider": self._get_provider(
provider,
"/etf/price_performance",
("fmp",),
("fmp", "intrinio"),
)
},
standard_params={
"symbol": symbol,
},
extra_params=kwargs,
extra_info={"symbol": {"multiple_items_allowed": ["fmp"]}},
extra_info={"symbol": {"multiple_items_allowed": ["fmp", "intrinio"]}},
)
)

View File

@@ -14,6 +14,7 @@ finviz_provider = Provider(
credentials=None,
fetcher_dict={
"CompareGroups": FinvizCompareGroupsFetcher,
"EtfPricePerformance": FinvizPricePerformanceFetcher,
"EquityInfo": FinvizEquityProfileFetcher,
"KeyMetrics": FinvizKeyMetricsFetcher,
"PricePerformance": FinvizPricePerformanceFetcher,

View File

@@ -2,6 +2,7 @@
# pylint: disable=unused-argument
from typing import Any, Dict, List, Optional
from warnings import warn
from finvizfinance.screener import performance
from openbb_core.provider.abstract.fetcher import Fetcher
@@ -108,7 +109,17 @@ class FinvizPricePerformanceFetcher(
screen_df = screen_df.fillna("N/A").replace("N/A", None) # type: ignore
except Exception as e:
raise e from e
return screen_df.to_dict(orient="records")
symbols = query.symbol.split(",")
# Check for missing symbols and warn of the missing symbols.
for symbol in symbols:
if symbol not in screen_df["Ticker"].tolist():
warn(f"Symbol Error: {symbol} was not found.")
return sorted(
screen_df.to_dict(orient="records"),
key=(lambda item: (symbols.index(item.get("Ticker", len(symbols))))),
)
@staticmethod
def transform_data(

View File

@@ -103,6 +103,7 @@ fmp_provider = Provider(
"EtfHoldingsDate": FMPEtfHoldingsDateFetcher,
"EtfHoldingsPerformance": FMPEtfHoldingsPerformanceFetcher,
"EtfInfo": FMPEtfInfoFetcher,
"EtfPricePerformance": FMPPricePerformanceFetcher,
"EtfSearch": FMPEtfSearchFetcher,
"EtfSectors": FMPEtfSectorsFetcher,
"ExecutiveCompensation": FMPExecutiveCompensationFetcher,

View File

@@ -12,6 +12,9 @@ from openbb_intrinio.models.equity_info import IntrinioEquityInfoFetcher
from openbb_intrinio.models.equity_quote import IntrinioEquityQuoteFetcher
from openbb_intrinio.models.equity_search import IntrinioEquitySearchFetcher
from openbb_intrinio.models.etf_info import IntrinioEtfInfoFetcher
from openbb_intrinio.models.etf_price_performance import (
IntrinioEtfPricePerformanceFetcher,
)
from openbb_intrinio.models.etf_search import IntrinioEtfSearchFetcher
from openbb_intrinio.models.financial_ratios import IntrinioFinancialRatiosFetcher
from openbb_intrinio.models.fred_series import IntrinioFredSeriesFetcher
@@ -58,6 +61,7 @@ intrinio_provider = Provider(
"EquitySearch": IntrinioEquitySearchFetcher,
"EtfHistorical": IntrinioEquityHistoricalFetcher,
"EtfInfo": IntrinioEtfInfoFetcher,
"EtfPricePerformance": IntrinioEtfPricePerformanceFetcher,
"EtfSearch": IntrinioEtfSearchFetcher,
"FinancialRatios": IntrinioFinancialRatiosFetcher,
"FredSeries": IntrinioFredSeriesFetcher,

View File

@@ -0,0 +1,227 @@
"""Intrinio ETF Performance Model."""
# pylint: disable=unused-argument
import asyncio
from datetime import date as dateType
from typing import Any, Dict, List, Literal, Optional
from warnings import warn
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.recent_performance import (
RecentPerformanceData,
RecentPerformanceQueryParams,
)
from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request
from openbb_intrinio.utils.references import ETF_PERFORMANCE_MAP
from pydantic import Field
class IntrinioEtfPricePerformanceQueryParams(RecentPerformanceQueryParams):
"""
Intrinio ETF Performance Query Params.
Source: https://docs.intrinio.com/documentation/web_api/get_etf_stats_v2
Source: https://docs.intrinio.com/documentation/web_api/get_etf_analytics_v2
"""
__json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
return_type: Literal["trailing", "calendar"] = Field(
default="trailing",
description="The type of returns to return, a trailing or calendar window.",
)
adjustment: Literal["splits_only", "splits_and_dividends"] = Field(
default="splits_and_dividends",
description="The adjustment factor, 'splits_only' will return pure price performance.",
)
class IntrinioEtfPricePerformanceData(RecentPerformanceData):
"""Intrinio ETF Performance Data."""
__alias_dict__ = {
"updated": "date",
"year_high": "fifty_two_week_high",
"year_low": "fifty_two_week_low",
"volume": "volume_traded",
"volume_avg_30": "average_daily_volume_one_month",
"volume_avg_90": "average_daily_volume_three_month",
"volume_avg_180": "average_daily_volume_six_month",
}
max_annualized: Optional[float] = Field(
default=None,
description="Annualized rate of return from inception.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
volatility_one_year: Optional[float] = Field(
default=None,
description="Trailing one-year annualized volatility.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
volatility_three_year: Optional[float] = Field(
default=None,
description="Trailing three-year annualized volatility.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
volatility_five_year: Optional[float] = Field(
default=None,
description="Trailing five-year annualized volatility.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
volume: Optional[int] = Field(
default=None,
description=DATA_DESCRIPTIONS.get("volume", ""),
)
volume_avg_30: Optional[float] = Field(
default=None,
description="The one-month average daily volume.",
)
volume_avg_90: Optional[float] = Field(
default=None,
description="The three-month average daily volume.",
)
volume_avg_180: Optional[float] = Field(
default=None,
description="The six-month average daily volume.",
)
beta: Optional[float] = Field(
default=None,
description="Beta compared to the S&P 500.",
)
nav: Optional[float] = Field(
default=None,
description="Net asset value per share.",
)
year_high: Optional[float] = Field(
default=None,
description="The 52-week high price.",
)
year_low: Optional[float] = Field(
default=None,
description="The 52-week low price.",
)
market_cap: Optional[float] = Field(
default=None,
description="The market capitalization.",
)
shares_outstanding: Optional[int] = Field(
default=None,
description="The number of shares outstanding.",
)
updated: Optional[dateType] = Field(
default=None,
description=DATA_DESCRIPTIONS.get("date", ""),
)
class IntrinioEtfPricePerformanceFetcher(
Fetcher[
IntrinioEtfPricePerformanceQueryParams, List[IntrinioEtfPricePerformanceData]
]
):
"""Intrinio ETF Performance Fetcher."""
@staticmethod
def transform_query(
params: Dict[str, Any]
) -> IntrinioEtfPricePerformanceQueryParams:
"""Transform query."""
return IntrinioEtfPricePerformanceQueryParams(**params)
@staticmethod
async def aextract_data(
query: IntrinioEtfPricePerformanceQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the Intrinio endpoint."""
api_key = credentials.get("intrinio_api_key") if credentials else ""
base_url = "https://api-v2.intrinio.com/etfs/"
symbols = query.symbol.split(",")
symbols = [
symbol + ":US" if ":" not in symbol else symbol for symbol in symbols
]
adjustment = (
"split_only"
if query.adjustment == "splits_and_dividends"
else "split_and_dividend"
)
return_type = "trailing" if query.return_type == "calendar" else "calendar"
results = []
async def get_one(symbol: str, **kwargs):
"""Get data for one symbol."""
url = f"{base_url}{symbol}/stats?api_key={api_key}"
result = await amake_request(url, **kwargs)
if "message" in result and result["message"] != []: # type: ignore
warn(f"Symbol Error: {symbol} - {result['message']}") # type: ignore
return
_ = result.pop("message", None) # type: ignore
_ = result.pop("messages", None) # type: ignore
data = {}
etf = result.pop("etf", {}) # type: ignore
data["symbol"] = etf.get("ticker")
# These items will be kept regardless of the adjustment and return_type.
keep = ["volatility", "month", "year_to_date"]
for k, v in result.copy().items(): # type: ignore
if not any(substring in k for substring in keep):
_ = result.pop(k, None) if adjustment in k else None # type: ignore
_ = result.pop(k, None) if return_type in k else None # type: ignore
if k in result:
data[ETF_PERFORMANCE_MAP.get(k, k)] = v
# Get an additional set of data to combine with the first set.
analytics_url = (
f"https://api-v2.intrinio.com/etfs/{symbol}/analytics?api_key={api_key}"
)
if data:
analytics = await amake_request(analytics_url, **kwargs)
if "messages" in analytics and analytics["messages"] != []: # type: ignore
warn(
f"Symbol Error: {analytics['messages']}" # type: ignore
+ f"for {etf.get('ticker')}" # type: ignore
)
return
# Remove the duplicate data from the analytics response.
_ = analytics.pop("messages", None) # type: ignore
_ = analytics.pop("etf", None) # type: ignore
_ = analytics.pop("date", None) # type: ignore
data.update(analytics) # type: ignore
results.append(data)
tasks = [get_one(symbol, **kwargs) for symbol in symbols]
await asyncio.gather(*tasks)
if not results:
raise EmptyDataError("No data was returned.")
# Undo any formatting changes made to the symbols before sorting.
symbols = query.symbol.replace(":US", "").split(",")
return sorted(
results,
key=(lambda item: (symbols.index(item.get("symbol", len(symbols))))),
)
@staticmethod
def transform_data(
query: IntrinioEtfPricePerformanceQueryParams,
data: List[Dict],
**kwargs: Any,
) -> List[IntrinioEtfPricePerformanceData]:
"""Transform data."""
return [IntrinioEtfPricePerformanceData.model_validate(d) for d in data]

View File

@@ -64,6 +64,39 @@ ETF_EXCHANGES = Literal[
]
ETF_PERFORMANCE_MAP = {
"trailing_one_month_return_split_and_dividend": "one_month",
"trailing_one_month_return_split_only": "one_month",
"trailing_one_year_return_split_and_dividend": "one_year",
"trailing_one_year_return_split_only": "one_year",
"trailing_one_year_volatility_annualized": "volatility_one_year",
"trailing_three_year_annualized_return_split_and_dividend": "three_year",
"trailing_three_year_annualized_return_split_only": "three_year",
"trailing_three_year_volatility_annualized": "volatility_three_year",
"trailing_five_year_annualized_return_split_and_dividend": "five_year",
"trailing_five_year_annualized_return_split_only": "five_year",
"trailing_five_year_volatility_annualized": "volatility_five_year",
"trailing_ten_year_annualized_return_split_and_dividend": "ten_year",
"trailing_ten_year_annualized_return_split_only": "ten_year",
"inception_annualized_return_split_and_dividend": "max_annualized",
"inception_annualized_return_split_only": "max_annualized",
"calendar_year_5_return_split_and_dividend": "five_year",
"calendar_year_5_return_split_only": "five_year",
"calendar_year_4_return_split_and_dividend": "four_year",
"calendar_year_4_return_split_only": "four_year",
"calendar_year_3_return_split_and_dividend": "three_year",
"calendar_year_3_return_split_only": "three_year",
"calendar_year_2_return_split_and_dividend": "two_year",
"calendar_year_2_return_split_only": "two_year",
"calendar_year_1_return_split_and_dividend": "one_year",
"calendar_year_1_return_split_only": "one_year",
"calendar_year_to_date_return_split_and_dividend": "ytd",
"calendar_year_to_date_return_split_only": "ytd",
"net_asset_value": "nav",
"beta_vs_spy": "beta",
}
class IntrinioCompany(Data):
"""Intrinio Company Data."""

View File

@@ -0,0 +1,82 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api-v2.intrinio.com/etfs/SPY:US/stats?api_key=MOCK_API_KEY
response:
body:
string: !!binary |
H4sIAE9n82UAA5RVUW8aMQz+K9U9U2Q7cZzwNm3rc1daqdM2nW6Q0mjXo7s70Gi1/z4fXSkqlCsv
QEK+z/6cz/FjdhebppjFJht9+zHIpkUbs1FGQPYUzCmabJBVsc2LptHPZVEu9G9GHgZwKDLIfsa2
yJdN3tyvshEOYZC1dZHKVM3yeRXzu3nV3uZ1bBd1pWfKpFTVNJ+mZZrGapqNYAgUAqFhi5a9WLCu
n2Nelat3YVexqA+HNxzIk1EsiID1vQTPsY3xgYI36C2zFeR9yOW8LFrda1cauFoUZXqI67BICI6Y
nIgxwNvg9raO/+EvmMMiEMGTAHsmAGesHEe3KWewNgSL3hkkYXH7ad4WJSYEdAKA+oXebOFv0vJY
UVZNFvRaDT5d7lFsz5rQgGUQQs/KJ8btZXlTEkEIWggExOAZeDuJNlZHKiItrAvsLPgQjJdjyDaC
tPNMAGIwFmFtnVRN4n2b5tX7M9EGN+LYeW0fMGTfQ7KxiSeLol4TIW/VL4NsUpTKrNmvJXBPyyGR
tp3VuwVWo5ke/HNc8l1Q1CqKA3b8GmZ7NHujTmIiVW2EQg98U2+Hjgw4fSf0pWDvX+PM4bCaNXmP
wTlWDwu5HvxGrUD3sOk9oQS3UyM6FPW0U4uiWq0+UoBqO+kheArbAYP1qA5lFQx6R/QaiD16dTA4
UMm8tgjsBMY39FryToKw18bQe9qpUzvPu/HUM028PsbqTkQhTR52st/L8uLsHXRsb7LRY5aUvfud
P/wG8+W6G4vFXTcqx+efLk7G3xcA5M5PGODk8+XZyWW9aFo91KbJr1ivj33V5U2apXx7b3Q11u06
TZ6Ww3NdpSZVurwai7eOzhAM6G4Tp/OyG83eAiHrTvwzuS2qmQ7INfzDxcfr7O/ffwAAAP//AwCV
rhxr0gcAAA==
headers:
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Thu, 14 Mar 2024 21:08:31 GMT
Transfer-Encoding:
- chunked
Vary:
- Origin,Accept-Encoding
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api-v2.intrinio.com/etfs/SPY:US/analytics?api_key=MOCK_API_KEY
response:
body:
string: !!binary |
H4sIAE9n82UAA3RRy07DMBD8lcjnNFo7Dzu5IaDn0rRSESDLStzEah4QO31Q8e9sCqqQUOWLd2Zn
xzs+k1ZbqyptSfby5pNSOU0ywoBFMwhnNCQ+2ZqtO0l36OVB652sTVWTLKYiYAzoP7rpDyQLRRhw
5PZ9M7ZaukGVukRRTCECIQLwidrrAX1lqUxzkr+Nfadl23cODRIe85RFIohTwENvKFw96KuGJ2Ga
JhELKKeCxzck1hyvgpSylAOgiYhYzHzSqmGnnSzUOz4XcBywRIQpCybS1mrQVvajs051pekqkqU8
4YJRmkxLabcl2ZkY3HW6y88PCJ82mGGn2inXfPGw9PLXEXDqwosBvMfV3FsNo3XY5Eyx08Ol7fmS
e2XkXyxb5wgPpvgpgwVWxpoOy3XORZSwOYUQELW67JvpH0UEjMaI6GNRqw6jaC/yu+X9hnx9fQMA
AP//AwCMIK2E/wEAAA==
headers:
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Thu, 14 Mar 2024 21:08:31 GMT
Transfer-Encoding:
- chunked
Vary:
- Origin,Accept-Encoding
status:
code: 200
message: OK
version: 1

View File

@@ -14,6 +14,9 @@ from openbb_intrinio.models.equity_info import IntrinioEquityInfoFetcher
from openbb_intrinio.models.equity_quote import IntrinioEquityQuoteFetcher
from openbb_intrinio.models.equity_search import IntrinioEquitySearchFetcher
from openbb_intrinio.models.etf_info import IntrinioEtfInfoFetcher
from openbb_intrinio.models.etf_price_performance import (
IntrinioEtfPricePerformanceFetcher,
)
from openbb_intrinio.models.etf_search import IntrinioEtfSearchFetcher
from openbb_intrinio.models.financial_ratios import IntrinioFinancialRatiosFetcher
from openbb_intrinio.models.fred_series import IntrinioFredSeriesFetcher
@@ -382,3 +385,12 @@ def test_intrinio_etf_info_fetcher(credentials=test_credentials):
fetcher = IntrinioEtfInfoFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_intrinio_etf_price_performance_fetcher(credentials=test_credentials):
params = {"symbol": "SPY:US"}
fetcher = IntrinioEtfPricePerformanceFetcher()
result = fetcher.test(params, credentials)
assert result is None