[Feature] Refactor FMP (#7217)

* stash the work here

* finish the job

* also add most active

* fix tmx etf countries

* codespell

* fix reference path in codespell

* still didn't get the path right

* linter

* linter

* unused-argument

* fix polygon

* fix tiingo

* remove debug print

* fix nasdaq

* more lint

* didn't save that file

* fix bad url in error message

* some review items

---------

Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com>
This commit is contained in:
Danglewood
2025-10-06 16:34:19 -07:00
committed by GitHub
parent 13d9e69976
commit 2e3d8ef6b2
311 changed files with 67029 additions and 209421 deletions

3
.codespell.skip vendored
View File

@@ -19,4 +19,5 @@
./openbb_platform/providers/econdb/openbb_econdb/utils/helpers.py
./openbb_platform/core/openbb/package/**
./openbb_platform/providers/imf/openbb_imf/utils/constants.py
./openbb_platform/extensions/economy/openbb_economy/economy_views.py
./openbb_platform/extensions/economy/openbb_economy/economy_views.py
./openbb_platform/providers/fmp/openbb_fmp/models/calendar_ipo.py

View File

@@ -1,68 +1,16 @@
"""Root configuration for pytest."""
# flake8: noqa: S101
# pylint: disable=unused-argument
# pylint: disable=unused-argument,unused-import
import os
import shutil
from pathlib import Path
import pytest
import pytest # noqa: F401
ROOT_DIR = Path(__file__).parent
@pytest.fixture(scope="session", autouse=True)
def cleanup_generated_files():
"""Clean up generated files and restore original state before and after test session."""
reference_file = ROOT_DIR / "core" / "openbb" / "assets" / "reference.json"
reference_backup = ROOT_DIR / "core" / "openbb" / "reference.json.original"
def clean_and_restore():
# 1. Remove all files in core/openbb/package except __init__.py
package_dir = ROOT_DIR / "core" / "openbb" / "package"
if package_dir.exists():
for item in package_dir.iterdir():
if item.name != "__init__.py":
if item.is_file():
item.unlink()
elif item.is_dir():
shutil.rmtree(item)
# 2. Create backup before first cleanup if it doesn't exist
if reference_file.exists() and not reference_backup.exists():
shutil.copy2(reference_file, reference_backup)
# 3. Restore from backup if it exists
elif reference_backup.exists() and reference_file.exists():
shutil.copy2(reference_backup, reference_file)
reference_backup.unlink()
# Clean before tests
clean_and_restore()
yield
# Clean after tests
clean_and_restore()
@pytest.mark.order(1)
def test_repository_state():
"""Ensure repository is in clean state before other tests."""
# Check that core/openbb/package only contains __init__.py
package_dir = ROOT_DIR / "core" / "openbb" / "package"
if package_dir.exists():
files = [f for f in package_dir.iterdir() if f.is_file()]
assert (
len(files) <= 1
), f"Package directory should only contain __init__.py, found: {[f.name for f in files]}"
if files:
assert (
files[0].name == "__init__.py"
), f"Only __init__.py should exist, found: {files[0].name}"
def pytest_configure():
"""Set environment variables for testing."""
os.environ["OPENBB_AUTO_BUILD"] = "true"

View File

@@ -55,8 +55,8 @@ class QueryExecutor:
website = provider.website or ""
extra_msg = f" Check {website} to get it." if website else ""
raise OpenBBError(
f"Missing credential '{c}'.{extra_msg} Known more about how to set provider "
"credentials at https://docs.openbb.co/platform/getting_started/api_keys."
f"Missing credential '{c}'.{extra_msg} Refer to the documentation for setting provider "
"credentials at https://docs.openbb.co/platform/settings/user_settings/api_keys."
)
else:
filtered_credentials[c] = secret

View File

@@ -4,6 +4,9 @@ from typing import Optional
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
)
from pydantic import Field
@@ -17,7 +20,13 @@ class AvailableIndicesData(Data):
Returns the list of available indices from a provider.
"""
name: Optional[str] = Field(default=None, description="Name of the index.")
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
name: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("name", "")
)
exchange: Optional[str] = Field(
default=None, description="Stock exchange where the index is listed."
)
currency: Optional[str] = Field(
default=None, description="Currency the index is traded in."
)

View File

@@ -16,7 +16,7 @@ class BalanceSheetQueryParams(QueryParams):
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
limit: Optional[NonNegativeInt] = Field(
default=5, description=QUERY_DESCRIPTIONS.get("limit", "")
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)

View File

@@ -14,7 +14,7 @@ class BalanceSheetGrowthQueryParams(QueryParams):
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
limit: Optional[int] = Field(
default=10, description=QUERY_DESCRIPTIONS.get("limit", "")
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)

View File

@@ -27,7 +27,6 @@ class CalendarSplitsData(Data):
"""Calendar Splits Data."""
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
label: str = Field(description="Label of the stock splits.")
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
numerator: float = Field(description="Numerator of the stock splits.")
denominator: float = Field(description="Denominator of the stock splits.")
numerator: float = Field(description="Numerator of the stock split.")
denominator: float = Field(description="Denominator of the stock split.")

View File

@@ -14,7 +14,7 @@ class CashFlowStatementGrowthQueryParams(QueryParams):
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
limit: Optional[int] = Field(
default=10, description=QUERY_DESCRIPTIONS.get("limit", "")
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)

View File

@@ -4,9 +4,8 @@ from datetime import (
date as dateType,
datetime,
)
from typing import Dict, List, Optional
from typing import Any, Optional
from dateutil.relativedelta import relativedelta
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
@@ -27,10 +26,11 @@ class CompanyNewsQueryParams(QueryParams):
default=None, description=QUERY_DESCRIPTIONS.get("start_date", "")
)
end_date: Optional[dateType] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("end_date", "")
default=None,
description=QUERY_DESCRIPTIONS.get("end_date", ""),
)
limit: Optional[NonNegativeInt] = Field(
default=2500, description=QUERY_DESCRIPTIONS.get("limit", "")
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before")
@@ -39,34 +39,20 @@ class CompanyNewsQueryParams(QueryParams):
"""Validate the symbols."""
return v.upper() if v else None
@field_validator("start_date", mode="before")
@classmethod
def start_date_validate(cls, v) -> dateType: # pylint: disable=E0213
"""Populate start date if empty."""
if not v:
now = datetime.now().date()
v = now - relativedelta(weeks=16)
return v
@field_validator("end_date", mode="before")
@classmethod
def end_date_validate(cls, v) -> dateType: # pylint: disable=E0213
"""Populate end date if empty."""
if not v:
v = datetime.now().date()
return v
class CompanyNewsData(Data):
"""Company News Data."""
date: datetime = Field(
description=DATA_DESCRIPTIONS.get("date", "")
+ " Here it is the published date of the article."
description=DATA_DESCRIPTIONS.get("date", "") + " The date of publication."
)
title: str = Field(description="Title of the article.")
text: Optional[str] = Field(default=None, description="Text/body of the article.")
images: Optional[List[Dict[str, str]]] = Field(
author: Optional[str] = Field(default=None, description="Author of the article.")
excerpt: Optional[str] = Field(
default=None, description="Excerpt of the article text."
)
body: Optional[str] = Field(default=None, description="Body of the article text.")
images: Optional[Any] = Field(
default=None, description="Images associated with the article."
)
url: str = Field(description="URL to the article.")

View File

@@ -1,106 +0,0 @@
"""Company Overview Standard Model."""
from datetime import date
from typing import List, Optional, Set, Union
from openbb_core.provider.abstract.data import Data, ForceInt
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, field_validator
class CompanyOverviewQueryParams(QueryParams):
"""Company Overview Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def to_upper(cls, v: str) -> str:
"""Convert field to uppercase."""
return v.upper()
class CompanyOverviewData(Data):
"""Company Overview Data.
Returns the profile of a given company.
"""
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
price: Optional[float] = Field(default=None, description="Price of the company.")
beta: Optional[float] = Field(default=None, description="Beta of the company.")
vol_avg: Optional[ForceInt] = Field(
default=None, description="Volume average of the company."
)
mkt_cap: Optional[ForceInt] = Field(
default=None, description="Market capitalization of the company."
)
last_div: Optional[float] = Field(
default=None, description="Last dividend of the company."
)
range: Optional[str] = Field(default=None, description="Range of the company.")
changes: Optional[float] = Field(
default=None, description="Changes of the company."
)
company_name: Optional[str] = Field(
default=None, description="Company name of the company."
)
currency: Optional[str] = Field(
default=None, description="Currency of the company."
)
cik: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("cik", "")
)
isin: Optional[str] = Field(default=None, description="ISIN of the company.")
cusip: Optional[str] = Field(default=None, description="CUSIP of the company.")
exchange: Optional[str] = Field(
default=None, description="Exchange of the company."
)
exchange_short_name: Optional[str] = Field(
default=None, description="Exchange short name of the company."
)
industry: Optional[str] = Field(
default=None, description="Industry of the company."
)
website: Optional[str] = Field(default=None, description="Website of the company.")
description: Optional[str] = Field(
default=None, description="Description of the company."
)
ceo: Optional[str] = Field(default=None, description="CEO of the company.")
sector: Optional[str] = Field(default=None, description="Sector of the company.")
country: Optional[str] = Field(default=None, description="Country of the company.")
full_time_employees: Optional[str] = Field(
default=None, description="Full time employees of the company."
)
phone: Optional[str] = Field(default=None, description="Phone of the company.")
address: Optional[str] = Field(default=None, description="Address of the company.")
city: Optional[str] = Field(default=None, description="City of the company.")
state: Optional[str] = Field(default=None, description="State of the company.")
zip: Optional[str] = Field(default=None, description="Zip of the company.")
dcf_diff: Optional[float] = Field(
default=None, description="Discounted cash flow difference of the company."
)
dcf: Optional[float] = Field(
default=None, description="Discounted cash flow of the company."
)
image: Optional[str] = Field(default=None, description="Image of the company.")
ipo_date: Optional[date] = Field(
default=None, description="IPO date of the company."
)
default_image: bool = Field(description="If the image is the default image.")
is_etf: bool = Field(description="If the company is an ETF.")
is_actively_trading: bool = Field(description="If the company is actively trading.")
is_adr: bool = Field(description="If the company is an ADR.")
is_fund: bool = Field(description="If the company is a fund.")
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def to_upper(cls, v: Union[str, List[str], Set[str]]):
"""Convert field to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])

View File

@@ -4,7 +4,7 @@ from datetime import (
date as dateType,
datetime,
)
from typing import List, Optional, Set, Union
from typing import Optional, Union
from dateutil import parser
from openbb_core.provider.abstract.data import Data
@@ -19,10 +19,7 @@ from pydantic import Field, field_validator
class CryptoHistoricalQueryParams(QueryParams):
"""Crypto Historical Price Query."""
symbol: str = Field(
description=QUERY_DESCRIPTIONS.get("symbol", "")
+ " Can use CURR1-CURR2 or CURR1CURR2 format."
)
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
start_date: Optional[dateType] = Field(
default=None,
description=QUERY_DESCRIPTIONS.get("start_date", ""),
@@ -34,11 +31,9 @@ class CryptoHistoricalQueryParams(QueryParams):
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def validate_symbol(cls, v: Union[str, List[str], Set[str]]):
def _to_upper(cls, v):
"""Convert field to uppercase and remove '-'."""
if isinstance(v, str):
return v.upper().replace("-", "")
return ",".join([symbol.upper().replace("-", "") for symbol in list(v)])
return str(v).upper()
class CryptoHistoricalData(Data):
@@ -47,9 +42,15 @@ class CryptoHistoricalData(Data):
date: Union[dateType, datetime] = Field(
description=DATA_DESCRIPTIONS.get("date", "")
)
open: float = Field(description=DATA_DESCRIPTIONS.get("open", ""))
high: float = Field(description=DATA_DESCRIPTIONS.get("high", ""))
low: float = Field(description=DATA_DESCRIPTIONS.get("low", ""))
open: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("open", "")
)
high: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("high", "")
)
low: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("low", "")
)
close: float = Field(description=DATA_DESCRIPTIONS.get("close", ""))
volume: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("volume", "")

View File

@@ -6,13 +6,14 @@ from datetime import (
)
from typing import List, Optional, Set, Union
from dateutil import parser
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, PositiveFloat, field_validator
from pydantic import Field, field_validator
class CurrencyHistoricalQueryParams(QueryParams):
@@ -47,13 +48,27 @@ class CurrencyHistoricalData(Data):
date: Union[dateType, datetime] = Field(
description=DATA_DESCRIPTIONS.get("date", "")
)
open: PositiveFloat = Field(description=DATA_DESCRIPTIONS.get("open", ""))
high: PositiveFloat = Field(description=DATA_DESCRIPTIONS.get("high", ""))
low: PositiveFloat = Field(description=DATA_DESCRIPTIONS.get("low", ""))
close: PositiveFloat = Field(description=DATA_DESCRIPTIONS.get("close", ""))
open: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("open", "")
)
high: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("high", "")
)
low: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("low", "")
)
close: float = Field(description=DATA_DESCRIPTIONS.get("close", ""))
volume: Optional[float] = Field(
description=DATA_DESCRIPTIONS.get("volume", ""), default=None
)
vwap: Optional[PositiveFloat] = Field(
vwap: Optional[float] = Field(
description=DATA_DESCRIPTIONS.get("vwap", ""), default=None
)
@field_validator("date", mode="before", check_fields=False)
@classmethod
def date_validate(cls, v): # pylint: disable=E0213
"""Return formatted datetime."""
if ":" in str(v):
return parser.isoparse(str(v))
return parser.parse(str(v)).date()

View File

@@ -33,8 +33,8 @@ class DiscoveryFilingsQueryParams(QueryParams):
"for a list of supported form types."
),
)
limit: NonNegativeInt = Field(
default=100, description=QUERY_DESCRIPTIONS.get("limit", "")
limit: Optional[NonNegativeInt] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@@ -43,7 +43,9 @@ class DiscoveryFilingsData(Data):
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
cik: str = Field(description=DATA_DESCRIPTIONS.get("cik", ""))
title: str = Field(description="Title of the filing.")
date: datetime = Field(description=DATA_DESCRIPTIONS.get("date", ""))
filing_date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
accepted_date: datetime = Field(
description=DATA_DESCRIPTIONS.get("accepted_date", "")
)
form_type: str = Field(description="The form type of the filing")
link: str = Field(description="URL to the filing page on the SEC site.")

View File

@@ -1,7 +1,7 @@
"""Earnings Call Transcript Standard Model."""
from datetime import datetime
from typing import List, Set, Union
from datetime import date as dateType
from typing import Literal, Optional
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -16,7 +16,12 @@ class EarningsCallTranscriptQueryParams(QueryParams):
"""Earnings Call Transcript rating Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
year: Union[int, str] = Field(description="Year of the earnings call transcript.")
year: Optional[int] = Field(
default=None, description="Year of the earnings call transcript."
)
quarter: Optional[Literal[1, 2, 3, 4]] = Field(
default=None, description="Quarterly period of the earnings call transcript."
)
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
@@ -29,15 +34,7 @@ class EarningsCallTranscriptData(Data):
"""Earnings Call Transcript Data."""
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
quarter: int = Field(description="Quarter of the earnings call transcript.")
year: int = Field(description="Year of the earnings call transcript.")
date: datetime = Field(description=DATA_DESCRIPTIONS.get("date", ""))
quarter: str = Field(description="Quarter of the earnings call transcript.")
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
content: str = Field(description="Content of the earnings call transcript.")
@field_validator("symbol", mode="before")
@classmethod
def to_upper(cls, v: Union[str, List[str], Set[str]]):
"""Convert field to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])

View File

@@ -16,19 +16,6 @@ class EquityOwnershipQueryParams(QueryParams):
"""Equity Ownership Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
date: Optional[dateType] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("date", "")
)
page: Optional[int] = Field(
default=0, description="Page number of the data to fetch."
)
@field_validator("date", mode="before", check_fields=False)
@classmethod
def time_validate(cls, v: str):
"""Validate the date."""
if v is None:
v = dateType.today()
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
@@ -40,117 +27,12 @@ class EquityOwnershipQueryParams(QueryParams):
class EquityOwnershipData(Data):
"""Equity Ownership Data."""
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
cik: int = Field(description=DATA_DESCRIPTIONS.get("cik", ""))
filing_date: dateType = Field(description="Filing date of the stock ownership.")
investor_name: str = Field(
...,
description="Investor name of the stock ownership.",
investor_name: str = Field(description="Investing entity's name.")
cik: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("cik", "")
)
date: dateType = Field(
description=DATA_DESCRIPTIONS.get("date", "") + " For the period ending."
)
filing_date: Optional[dateType] = Field(description="Date when reported.")
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
security_name: str = Field(
...,
description="Security name of the stock ownership.",
)
type_of_security: str = Field(
...,
description="Type of security of the stock ownership.",
)
security_cusip: str = Field(
...,
description="Security cusip of the stock ownership.",
)
shares_type: str = Field(description="Shares type of the stock ownership.")
put_call_share: str = Field(
...,
description="Put call share of the stock ownership.",
)
investment_discretion: str = Field(
...,
description="Investment discretion of the stock ownership.",
)
industry_title: str = Field(
...,
description="Industry title of the stock ownership.",
)
weight: float = Field(description="Weight of the stock ownership.")
last_weight: float = Field(description="Last weight of the stock ownership.")
change_in_weight: float = Field(
...,
description="Change in weight of the stock ownership.",
)
change_in_weight_percentage: float = Field(
...,
description="Change in weight percentage of the stock ownership.",
)
market_value: int = Field(description="Market value of the stock ownership.")
last_market_value: int = Field(
...,
description="Last market value of the stock ownership.",
)
change_in_market_value: int = Field(
...,
description="Change in market value of the stock ownership.",
)
change_in_market_value_percentage: float = Field(
...,
description="Change in market value percentage of the stock ownership.",
)
shares_number: int = Field(
...,
description="Shares number of the stock ownership.",
)
last_shares_number: int = Field(
...,
description="Last shares number of the stock ownership.",
)
change_in_shares_number: float = Field(
...,
description="Change in shares number of the stock ownership.",
)
change_in_shares_number_percentage: float = Field(
...,
description="Change in shares number percentage of the stock ownership.",
)
quarter_end_price: float = Field(
...,
description="Quarter end price of the stock ownership.",
)
avg_price_paid: float = Field(
...,
description="Average price paid of the stock ownership.",
)
is_new: bool = Field(description="Is the stock ownership new.")
is_sold_out: bool = Field(description="Is the stock ownership sold out.")
ownership: float = Field(description="How much is the ownership.")
last_ownership: float = Field(description="Last ownership amount.")
change_in_ownership: float = Field(description="Change in ownership amount.")
change_in_ownership_percentage: float = Field(
...,
description="Change in ownership percentage.",
)
holding_period: int = Field(
...,
description="Holding period of the stock ownership.",
)
first_added: dateType = Field(
...,
description="First added date of the stock ownership.",
)
performance: float = Field(description="Performance of the stock ownership.")
performance_percentage: float = Field(
...,
description="Performance percentage of the stock ownership.",
)
last_performance: float = Field(
...,
description="Last performance of the stock ownership.",
)
change_in_performance: float = Field(
...,
description="Change in performance of the stock ownership.",
)
is_counted_for_performance: bool = Field(
...,
description="Is the stock ownership counted for performance.",
)

View File

@@ -1,10 +1,11 @@
"""Equity Peers Standard Model."""
from typing import List
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,
)
from pydantic import Field, field_validator
@@ -23,7 +24,4 @@ class EquityPeersQueryParams(QueryParams):
class EquityPeersData(Data):
"""Equity Peers Data."""
peers_list: List[str] = Field(
default_factory=list,
description="A list of equity peers based on sector, exchange and market cap.",
)
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))

View File

@@ -45,6 +45,7 @@ class EquityPerformanceData(Data):
description="Percent change.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
volume: Union[int, float] = Field(
volume: Optional[Union[int, float]] = Field(
default=None,
description=DATA_DESCRIPTIONS.get("volume", ""),
)

View File

@@ -141,7 +141,7 @@ class EquityQuoteData(Data):
change_percent: Optional[float] = Field(
default=None,
description="Change in price as a normalized percentage.",
json_schema_extra={"x-frontendmultiply": 100},
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
year_high: Optional[float] = Field(
default=None, description="The one year high (52W High)."

View File

@@ -1,6 +1,7 @@
"""Equity Valuation Multiples Standard Model."""
from typing import Optional
from datetime import date as dateType
from typing import Optional, Union
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -27,236 +28,22 @@ class EquityValuationMultiplesData(Data):
"""Equity Valuation Multiples Data."""
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
revenue_per_share_ttm: Optional[float] = Field(
period_ending: Optional[dateType] = Field(
default=None, description="End date of the reporting period."
)
fiscal_year: Optional[int] = Field(
default=None, description="Fiscal year for the fiscal period, if available."
)
fiscal_period: Optional[str] = Field(
default=None, description="Fiscal period for the data, if available."
)
currency: Optional[str] = Field(
default=None,
description="Revenue per share calculated as trailing twelve months.",
description="Currency in which the data is reported.",
)
net_income_per_share_ttm: Optional[float] = Field(
default=None,
description="Net income per share calculated as trailing twelve months.",
market_cap: Optional[Union[int, float]] = Field(
default=None, description=DATA_DESCRIPTIONS.get("market_cap", "")
)
operating_cash_flow_per_share_ttm: Optional[float] = Field(
default=None,
description="Operating cash flow per share calculated as trailing twelve months.",
)
free_cash_flow_per_share_ttm: Optional[float] = Field(
default=None,
description="Free cash flow per share calculated as trailing twelve months.",
)
cash_per_share_ttm: Optional[float] = Field(
default=None, description="Cash per share calculated as trailing twelve months."
)
book_value_per_share_ttm: Optional[float] = Field(
default=None,
description="Book value per share calculated as trailing twelve months.",
)
tangible_book_value_per_share_ttm: Optional[float] = Field(
default=None,
description="Tangible book value per share calculated as trailing twelve months.",
)
shareholders_equity_per_share_ttm: Optional[float] = Field(
default=None,
description="Shareholders equity per share calculated as trailing twelve months.",
)
interest_debt_per_share_ttm: Optional[float] = Field(
default=None,
description="Interest debt per share calculated as trailing twelve months.",
)
market_cap_ttm: Optional[float] = Field(
default=None,
description="Market capitalization calculated as trailing twelve months.",
)
enterprise_value_ttm: Optional[float] = Field(
default=None,
description="Enterprise value calculated as trailing twelve months.",
)
pe_ratio_ttm: Optional[float] = Field(
default=None,
description="Price-to-earnings ratio (P/E ratio) calculated as trailing twelve months.",
)
price_to_sales_ratio_ttm: Optional[float] = Field(
default=None,
description="Price-to-sales ratio calculated as trailing twelve months.",
)
pocf_ratio_ttm: Optional[float] = Field(
default=None,
description="Price-to-operating cash flow ratio calculated as trailing twelve months.",
)
pfcf_ratio_ttm: Optional[float] = Field(
default=None,
description="Price-to-free cash flow ratio calculated as trailing twelve months.",
)
pb_ratio_ttm: Optional[float] = Field(
default=None,
description="Price-to-book ratio calculated as trailing twelve months.",
)
ptb_ratio_ttm: Optional[float] = Field(
default=None,
description="Price-to-tangible book ratio calculated as trailing twelve months.",
)
ev_to_sales_ttm: Optional[float] = Field(
default=None,
description="Enterprise value-to-sales ratio calculated as trailing twelve months.",
)
enterprise_value_over_ebitda_ttm: Optional[float] = Field(
default=None,
description="Enterprise value-to-EBITDA ratio calculated as trailing twelve months.",
)
ev_to_operating_cash_flow_ttm: Optional[float] = Field(
default=None,
description="Enterprise value-to-operating cash flow ratio calculated as trailing twelve months.",
)
ev_to_free_cash_flow_ttm: Optional[float] = Field(
default=None,
description="Enterprise value-to-free cash flow ratio calculated as trailing twelve months.",
)
earnings_yield_ttm: Optional[float] = Field(
default=None, description="Earnings yield calculated as trailing twelve months."
)
free_cash_flow_yield_ttm: Optional[float] = Field(
default=None,
description="Free cash flow yield calculated as trailing twelve months.",
)
debt_to_equity_ttm: Optional[float] = Field(
default=None,
description="Debt-to-equity ratio calculated as trailing twelve months.",
)
debt_to_assets_ttm: Optional[float] = Field(
default=None,
description="Debt-to-assets ratio calculated as trailing twelve months.",
)
net_debt_to_ebitda_ttm: Optional[float] = Field(
default=None,
description="Net debt-to-EBITDA ratio calculated as trailing twelve months.",
)
current_ratio_ttm: Optional[float] = Field(
default=None, description="Current ratio calculated as trailing twelve months."
)
interest_coverage_ttm: Optional[float] = Field(
default=None,
description="Interest coverage calculated as trailing twelve months.",
)
income_quality_ttm: Optional[float] = Field(
default=None, description="Income quality calculated as trailing twelve months."
)
dividend_yield_ttm: Optional[float] = Field(
default=None, description="Dividend yield calculated as trailing twelve months."
)
dividend_yield_percentage_ttm: Optional[float] = Field(
default=None,
description="Dividend yield percentage calculated as trailing twelve months.",
)
dividend_to_market_cap_ttm: Optional[float] = Field(
default=None,
description="Dividend to market capitalization ratio calculated as trailing twelve months.",
)
dividend_per_share_ttm: Optional[float] = Field(
default=None,
description="Dividend per share calculated as trailing twelve months.",
)
payout_ratio_ttm: Optional[float] = Field(
default=None, description="Payout ratio calculated as trailing twelve months."
)
sales_general_and_administrative_to_revenue_ttm: Optional[float] = Field(
default=None,
description="Sales general and administrative expenses-to-revenue ratio calculated as trailing twelve months.",
)
research_and_development_to_revenue_ttm: Optional[float] = Field(
default=None,
description="Research and development expenses-to-revenue ratio calculated as trailing twelve months.",
)
intangibles_to_total_assets_ttm: Optional[float] = Field(
default=None,
description="Intangibles-to-total assets ratio calculated as trailing twelve months.",
)
capex_to_operating_cash_flow_ttm: Optional[float] = Field(
default=None,
description="Capital expenditures-to-operating cash flow ratio calculated as trailing twelve months.",
)
capex_to_revenue_ttm: Optional[float] = Field(
default=None,
description="Capital expenditures-to-revenue ratio calculated as trailing twelve months.",
)
capex_to_depreciation_ttm: Optional[float] = Field(
default=None,
description="Capital expenditures-to-depreciation ratio calculated as trailing twelve months.",
)
stock_based_compensation_to_revenue_ttm: Optional[float] = Field(
default=None,
description="Stock-based compensation-to-revenue ratio calculated as trailing twelve months.",
)
graham_number_ttm: Optional[float] = Field(
default=None, description="Graham number calculated as trailing twelve months."
)
roic_ttm: Optional[float] = Field(
default=None,
description="Return on invested capital calculated as trailing twelve months.",
)
return_on_tangible_assets_ttm: Optional[float] = Field(
default=None,
description="Return on tangible assets calculated as trailing twelve months.",
)
graham_net_net_ttm: Optional[float] = Field(
default=None,
description="Graham net-net working capital calculated as trailing twelve months.",
)
working_capital_ttm: Optional[float] = Field(
default=None,
description="Working capital calculated as trailing twelve months.",
)
tangible_asset_value_ttm: Optional[float] = Field(
default=None,
description="Tangible asset value calculated as trailing twelve months.",
)
net_current_asset_value_ttm: Optional[float] = Field(
default=None,
description="Net current asset value calculated as trailing twelve months.",
)
invested_capital_ttm: Optional[float] = Field(
default=None,
description="Invested capital calculated as trailing twelve months.",
)
average_receivables_ttm: Optional[float] = Field(
default=None,
description="Average receivables calculated as trailing twelve months.",
)
average_payables_ttm: Optional[float] = Field(
default=None,
description="Average payables calculated as trailing twelve months.",
)
average_inventory_ttm: Optional[float] = Field(
default=None,
description="Average inventory calculated as trailing twelve months.",
)
days_sales_outstanding_ttm: Optional[float] = Field(
default=None,
description="Days sales outstanding calculated as trailing twelve months.",
)
days_payables_outstanding_ttm: Optional[float] = Field(
default=None,
description="Days payables outstanding calculated as trailing twelve months.",
)
days_of_inventory_on_hand_ttm: Optional[float] = Field(
default=None,
description="Days of inventory on hand calculated as trailing twelve months.",
)
receivables_turnover_ttm: Optional[float] = Field(
default=None,
description="Receivables turnover calculated as trailing twelve months.",
)
payables_turnover_ttm: Optional[float] = Field(
default=None,
description="Payables turnover calculated as trailing twelve months.",
)
inventory_turnover_ttm: Optional[float] = Field(
default=None,
description="Inventory turnover calculated as trailing twelve months.",
)
roe_ttm: Optional[float] = Field(
default=None,
description="Return on equity calculated as trailing twelve months.",
)
capex_per_share_ttm: Optional[float] = Field(
default=None,
description="Capital expenditures per share calculated as trailing twelve months.",
enterprise_value: Optional[Union[int, float]] = Field(
default=None, description=DATA_DESCRIPTIONS.get("enterprise_value", "")
)

View File

@@ -4,7 +4,7 @@ from datetime import (
date as dateType,
datetime,
)
from typing import List, Set, Union
from typing import Optional, Union
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -15,7 +15,7 @@ from openbb_core.provider.utils.descriptions import (
from pydantic import Field, field_validator
class ESGScoreQueryParams(QueryParams):
class EsgScoreQueryParams(QueryParams):
"""ESG Score Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
@@ -27,27 +27,29 @@ class ESGScoreQueryParams(QueryParams):
return v.upper()
class ESGScoreData(Data):
class EsgScoreData(Data):
"""ESG Score Data."""
period_ending: dateType = Field(description="Period ending date of the report.")
disclosure_date: Optional[Union[dateType, datetime]] = Field(
description="Date when the report was submitted."
)
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
cik: str = Field(description=DATA_DESCRIPTIONS.get("cik", ""))
company_name: str = Field(description="Company name of the company.")
form_type: str = Field(description="Form type of the company.")
accepted_date: datetime = Field(description="Accepted date of the company.")
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
cik: Optional[str] = Field(
default=None,
description=DATA_DESCRIPTIONS.get("cik", ""),
coerce_numbers_to_str=True,
)
company_name: Optional[str] = Field(
default=None, description="Company name of the company."
)
form_type: Optional[str] = Field(
default=None, description="Form type where the disclosure was made."
)
environmental_score: float = Field(
description="Environmental score of the company."
)
social_score: float = Field(description="Social score of the company.")
governance_score: float = Field(description="Governance score of the company.")
esg_score: float = Field(description="ESG score of the company.")
url: str = Field(description="URL of the company.")
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def to_upper(cls, v: Union[str, List[str], Set[str]]):
"""Convert field to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])
url: Optional[str] = Field(default=None, description="URL to the report or filing.")

View File

@@ -1,15 +1,20 @@
"""ETF Countries Standard Model."""
from typing import Optional
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,
)
from pydantic import Field, field_validator
class EtfCountriesQueryParams(QueryParams):
"""ETF Countries Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", "") + " (ETF)")
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
@field_validator("symbol")
@classmethod
@@ -21,6 +26,13 @@ class EtfCountriesQueryParams(QueryParams):
class EtfCountriesData(Data):
"""ETF Countries Data."""
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
country: str = Field(
description="The country of the exposure. Corresponding values are normalized percentage points."
)
weight: float = Field(
description="The net exposure of the ETF to the country as a percentage of the total ETF assets.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)

View File

@@ -11,7 +11,9 @@ from pydantic import Field, field_validator
class EtfEquityExposureQueryParams(QueryParams):
"""ETF Equity Exposure Query Params."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", "") + " (Stock)")
symbol: str = Field(
description=QUERY_DESCRIPTIONS.get("symbol", "") + " (underlying equity)"
)
@field_validator("symbol")
@classmethod
@@ -27,10 +29,6 @@ class EtfEquityExposureData(Data):
etf_symbol: str = Field(
description="The symbol of the ETF with exposure to the requested equity."
)
shares: Optional[float] = Field(
default=None,
description="The number of shares held in the ETF.",
)
weight: Optional[float] = Field(
default=None,
description="The weight of the equity in the ETF, as a normalized percent.",
@@ -40,3 +38,7 @@ class EtfEquityExposureData(Data):
default=None,
description="The market value of the equity position in the ETF.",
)
shares: Optional[Union[int, float]] = Field(
default=None,
description="Number of reported shares controlled by the ETF.",
)

View File

@@ -27,9 +27,9 @@ class EtfHoldingsData(Data):
"""ETF Holdings Data."""
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "") + " (ETF)"
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
name: Optional[str] = Field(
default=None,
description="Name of the ETF holding.",
description="Name of the asset.",
)

View File

@@ -1,23 +0,0 @@
"""ETF Holdings Date Standard Model."""
from datetime import date as dateType
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field
class EtfHoldingsDateQueryParams(QueryParams):
"""ETF Holdings Date Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", "") + " (ETF)")
class EtfHoldingsDateData(Data):
"""ETF Holdings Date Data."""
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date"))

View File

@@ -1,5 +1,6 @@
"""ETF Info Standard Model."""
from datetime import date as dateType
from typing import Optional
from openbb_core.provider.abstract.data import Data
@@ -28,7 +29,12 @@ class EtfInfoData(Data):
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", "") + " (ETF)")
name: Optional[str] = Field(description="Name of the ETF.")
issuer: Optional[str] = Field(default=None, description="Issuer of the ETF.")
domicile: Optional[str] = Field(default=None, description="Domicile of the ETF.")
website: Optional[str] = Field(default=None, description="Website of the ETF.")
description: Optional[str] = Field(
default=None, description="Description of the fund."
)
inception_date: Optional[str] = Field(description="Inception date of the ETF.")
inception_date: Optional[dateType] = Field(
default=None, description="Inception date of the ETF."
)

View File

@@ -4,7 +4,10 @@ from typing import Optional
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,
)
from pydantic import Field, field_validator
@@ -23,7 +26,11 @@ class EtfSectorsQueryParams(QueryParams):
class EtfSectorsData(Data):
"""ETF Sectors Data."""
sector: str = Field(description="Sector of exposure.")
weight: Optional[float] = Field(
description="Exposure of the ETF to the sector in normalized percentage points."
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
sector: str = Field(description="Sector of exposure.")
weight: float = Field(
description="Sector exposure for the ETF as a percent of total assets.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)

View File

@@ -1,6 +1,7 @@
"""Executive Compensation Standard Model."""
from typing import Optional
from datetime import date as dateType
from typing import Optional, Union
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -8,7 +9,7 @@ from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, NonNegativeFloat, field_validator
from pydantic import Field, field_validator
class ExecutiveCompensationQueryParams(QueryParams):
@@ -30,29 +31,32 @@ class ExecutiveCompensationData(Data):
cik: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("cik", "")
)
report_date: Optional[dateType] = Field(
default=None, description="Date of reported compensation."
)
company_name: Optional[str] = Field(
default=None, description="The name of the company."
)
industry: Optional[str] = Field(
default=None, description="The industry of the company."
)
executive: Optional[str] = Field(default=None, description="Name and position.")
year: Optional[int] = Field(default=None, description="Year of the compensation.")
name_and_position: Optional[str] = Field(
default=None, description="Name and position."
salary: Optional[Union[int, float]] = Field(
default=None, description="Base salary."
)
salary: Optional[NonNegativeFloat] = Field(default=None, description="Salary.")
bonus: Optional[NonNegativeFloat] = Field(
bonus: Optional[Union[int, float]] = Field(
default=None, description="Bonus payments."
)
stock_award: Optional[NonNegativeFloat] = Field(
stock_award: Optional[Union[int, float]] = Field(
default=None, description="Stock awards."
)
incentive_plan_compensation: Optional[NonNegativeFloat] = Field(
option_award: Optional[Union[int, float]] = Field(
default=None, description="Option awards."
)
incentive_plan_compensation: Optional[Union[int, float]] = Field(
default=None, description="Incentive plan compensation."
)
all_other_compensation: Optional[NonNegativeFloat] = Field(
all_other_compensation: Optional[Union[int, float]] = Field(
default=None, description="All other compensation."
)
total: Optional[NonNegativeFloat] = Field(
total: Optional[Union[int, float]] = Field(
default=None, description="Total compensation."
)

View File

@@ -1,5 +1,6 @@
"""Financial Ratios Standard Model."""
from datetime import date as dateType
from typing import Optional
from openbb_core.provider.abstract.data import Data
@@ -8,15 +9,15 @@ from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, NonNegativeInt, field_validator
from pydantic import Field, field_validator
class FinancialRatiosQueryParams(QueryParams):
"""Financial Ratios Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
limit: NonNegativeInt = Field(
default=12, description=QUERY_DESCRIPTIONS.get("limit", "")
limit: Optional[int] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)
@@ -29,6 +30,13 @@ class FinancialRatiosQueryParams(QueryParams):
class FinancialRatiosData(Data):
"""Financial Ratios Standard Model."""
period_ending: str = Field(description=DATA_DESCRIPTIONS.get("date", ""))
fiscal_period: str = Field(description="Period of the financial ratios.")
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
period_ending: Optional[dateType] = Field(
default=None, description=DATA_DESCRIPTIONS.get("date", "")
)
fiscal_period: Optional[str] = Field(
default=None, description="Period of the financial ratios."
)
fiscal_year: Optional[int] = Field(default=None, description="Fiscal year.")

View File

@@ -9,7 +9,7 @@ from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, NonNegativeInt, field_validator
from pydantic import Field, field_validator
class GovernmentTradesQueryParams(QueryParams):
@@ -21,8 +21,8 @@ class GovernmentTradesQueryParams(QueryParams):
chamber: Literal["house", "senate", "all"] = Field(
default="all", description="Government Chamber."
)
limit: Optional[NonNegativeInt] = Field(
default=100, description=QUERY_DESCRIPTIONS.get("limit", "")
limit: Optional[int] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)

View File

@@ -6,6 +6,7 @@ from typing import Optional
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, field_validator
@@ -32,6 +33,9 @@ class HistoricalDividendsQueryParams(QueryParams):
class HistoricalDividendsData(Data):
"""Historical Dividends Data."""
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
ex_dividend_date: dateType = Field(
description="The ex-dividend date - the date on which the stock begins trading without rights to the dividend."
)

View File

@@ -1,7 +1,7 @@
"""Historical Employees Standard Model."""
from datetime import date, datetime
from typing import List, Set, Union
from datetime import date as dateType
from typing import Optional
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -16,6 +16,12 @@ class HistoricalEmployeesQueryParams(QueryParams):
"""Historical Employees Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
start_date: Optional[dateType] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("start_date", "")
)
end_date: Optional[dateType] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("end_date", "")
)
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
@@ -27,46 +33,8 @@ class HistoricalEmployeesQueryParams(QueryParams):
class HistoricalEmployeesData(Data):
"""Historical Employees Data."""
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
cik: int = Field(description=DATA_DESCRIPTIONS.get("cik", ""))
acceptance_time: datetime = Field(
description="Time of acceptance of the company employee."
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
period_of_report: date = Field(
description="Date of reporting of the company employee."
)
company_name: str = Field(
description="Registered name of the company to retrieve the historical employees of."
)
form_type: str = Field(description="Form type of the company employee.")
filing_date: date = Field(description="Filing date of the company employee")
employee_count: int = Field(description="Count of employees of the company.")
source: str = Field(
description="Source URL which retrieves this data for the company."
)
@field_validator("acceptance_time", mode="before", check_fields=False)
@classmethod
def acceptance_time_validate(cls, v): # pylint: disable=E0213
"""Validate acceptance time."""
return datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
@field_validator("period_of_report", mode="before", check_fields=False)
@classmethod
def period_of_report_validate(cls, v): # pylint: disable=E0213
"""Validate period of report."""
return datetime.strptime(v, "%Y-%m-%d")
@field_validator("filing_date", mode="before", check_fields=False)
@classmethod
def filing_date_validate(cls, v): # pylint: disable=E0213
"""Validate filing date."""
return datetime.strptime(v, "%Y-%m-%d")
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def to_upper(cls, v: Union[str, List[str], Set[str]]):
"""Convert field to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])
employees: int = Field(description="Reported number of employees.")

View File

@@ -1,9 +1,8 @@
"""Historical EPS Standard Model."""
from datetime import date as dateType
from typing import Optional
from typing import Optional, Union
from dateutil import parser
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
@@ -28,19 +27,11 @@ class HistoricalEpsQueryParams(QueryParams):
class HistoricalEpsData(Data):
"""Historical EPS Data."""
date: dateType = Field(default=None, description=DATA_DESCRIPTIONS.get("date", ""))
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
announce_time: Optional[str] = Field(
default=None, description="Timing of the earnings announcement."
)
eps_actual: Optional[float] = Field(
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
eps_actual: Optional[Union[int, float]] = Field(
default=None, description="Actual EPS from the earnings date."
)
eps_estimated: Optional[float] = Field(
eps_estimated: Optional[Union[int, float]] = Field(
default=None, description="Estimated EPS for the earnings date."
)
@field_validator("date", mode="before", check_fields=False)
def date_validate(cls, v): # pylint: disable=E0213
"""Return formatted datetime."""
return parser.isoparse(str(v))

View File

@@ -16,7 +16,7 @@ class IncomeStatementQueryParams(QueryParams):
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
limit: Optional[NonNegativeInt] = Field(
default=5, description=QUERY_DESCRIPTIONS.get("limit", "")
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)

View File

@@ -14,7 +14,7 @@ class IncomeStatementGrowthQueryParams(QueryParams):
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
limit: Optional[int] = Field(
default=10, description=QUERY_DESCRIPTIONS.get("limit", "")
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)

View File

@@ -7,13 +7,13 @@ from datetime import (
from typing import Optional, Union
from dateutil import parser
from openbb_core.provider.abstract.data import Data, ForceInt
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, StrictFloat, field_validator
from pydantic import Field, field_validator
class IndexHistoricalQueryParams(QueryParams):
@@ -37,22 +37,25 @@ class IndexHistoricalQueryParams(QueryParams):
class IndexHistoricalData(Data):
"""Index Historical Data."""
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
date: Union[dateType, datetime] = Field(
description=DATA_DESCRIPTIONS.get("date", "")
)
open: Optional[StrictFloat] = Field(
open: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("open", "")
)
high: Optional[StrictFloat] = Field(
high: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("high", "")
)
low: Optional[StrictFloat] = Field(
low: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("low", "")
)
close: Optional[StrictFloat] = Field(
close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("close", "")
)
volume: Optional[ForceInt] = Field(
volume: Optional[Union[int, float]] = Field(
default=None, description=DATA_DESCRIPTIONS.get("volume", "")
)

View File

@@ -14,15 +14,15 @@ from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, StrictInt, field_validator
from pydantic import Field, field_validator
class InsiderTradingQueryParams(QueryParams):
"""Insider Trading Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
limit: StrictInt = Field(
default=500,
limit: Optional[int] = Field(
default=None,
description=QUERY_DESCRIPTIONS.get("limit", ""),
)
@@ -39,8 +39,10 @@ class InsiderTradingData(Data):
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
company_cik: Optional[Union[int, str]] = Field(
default=None, description="CIK number of the company."
company_cik: Optional[str] = Field(
default=None,
description="CIK number of the company.",
coerce_numbers_to_str=True,
)
filing_date: Optional[Union[dateType, datetime]] = Field(
default=None, description="Filing date of the trade."
@@ -57,6 +59,9 @@ class InsiderTradingData(Data):
owner_title: Optional[str] = Field(
default=None, description="The title held by the reporting individual."
)
ownership_type: Optional[str] = Field(
default=None, description="Type of ownership, e.g., direct or indirect."
)
transaction_type: Optional[str] = Field(
default=None, description="Type of transaction being reported."
)

View File

@@ -1,7 +1,7 @@
"""Institutional Ownership Standard Model."""
from datetime import date as dateType
from typing import List, Optional, Set, Union
from typing import Optional
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -33,11 +33,3 @@ class InstitutionalOwnershipData(Data):
description=DATA_DESCRIPTIONS.get("cik", ""),
)
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def to_upper(cls, v: Union[str, List[str], Set[str]]):
"""Convert field to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])

View File

@@ -2,7 +2,7 @@
from typing import Optional
from openbb_core.provider.abstract.data import Data, ForceInt
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 pydantic import Field, field_validator
@@ -25,18 +25,13 @@ class KeyExecutivesData(Data):
title: str = Field(description="Designation of the key executive.")
name: str = Field(description="Name of the key executive.")
pay: Optional[ForceInt] = Field(
default=None, description="Pay of the key executive."
)
pay: Optional[int] = Field(default=None, description="Pay of the key executive.")
currency_pay: Optional[str] = Field(
default=None, description="Currency of the pay."
)
gender: Optional[str] = Field(
default=None, description="Gender of the key executive."
)
year_born: Optional[ForceInt] = Field(
year_born: Optional[int] = Field(
default=None, description="Birth year of the key executive."
)
title_since: Optional[ForceInt] = Field(
default=None, description="Date the tile was held since."
)

View File

@@ -1,6 +1,7 @@
"""Key Metrics Standard Model."""
from typing import Optional
from datetime import date as dateType
from typing import Optional, Union
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -15,9 +16,6 @@ class KeyMetricsQueryParams(QueryParams):
"""Key Metrics Query."""
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
limit: Optional[int] = Field(
default=100, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
@@ -29,12 +27,20 @@ class KeyMetricsQueryParams(QueryParams):
class KeyMetricsData(Data):
"""Key Metrics Data."""
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
period_ending: Optional[dateType] = Field(
default=None, description="End date of the reporting period."
)
market_cap: Optional[float] = Field(
default=None, description="Market capitalization"
fiscal_year: Optional[int] = Field(
default=None, description="Fiscal year for the fiscal period, if available."
)
pe_ratio: Optional[float] = Field(
default=None, description="Price-to-earnings ratio (P/E ratio)"
fiscal_period: Optional[str] = Field(
default=None, description="Fiscal period for the data, if available."
)
currency: Optional[str] = Field(
default=None,
description="Currency in which the data is reported.",
)
market_cap: Optional[Union[int, float]] = Field(
default=None, description=DATA_DESCRIPTIONS.get("market_cap", "")
)

View File

@@ -15,7 +15,13 @@ class MarketSnapshotsQueryParams(QueryParams):
class MarketSnapshotsData(Data):
"""Market Snapshots Data."""
exchange: Optional[str] = Field(
description="Exchange the security is listed on.", default=None
)
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
name: Optional[str] = Field(
description="Name of the company, fund, or security.", default=None
)
open: Optional[float] = Field(
description=DATA_DESCRIPTIONS.get("open", ""),
default=None,

View File

@@ -0,0 +1,130 @@
"""N-PORT Discolsure Standard Model."""
from typing import Optional, Union
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, field_validator
class NportDisclosureQueryParams(QueryParams):
"""N-PORT Disclosure Query."""
symbol: str = Field(
description=QUERY_DESCRIPTIONS.get("symbol", "") + " (Fund ticker or CIK)"
)
year: Optional[int] = Field(
default=None,
description="Reporting year of the filing. Default is the year for the most recent, reported, quarter.",
)
quarter: Optional[int] = Field(
default=None,
description="Reporting quarter of the filing. Default is the most recent, reported, quarter.",
)
@field_validator("symbol")
@classmethod
def to_upper(cls, v: str) -> str:
"""Convert field to uppercase."""
return v.upper()
class NportDisclosureData(Data):
"""N-PORT Disclosure Data."""
symbol: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
)
name: Optional[str] = Field(
default=None,
description="Name of the asset.",
)
title: Optional[str] = Field(
default=None,
description="Title of the asset.",
)
cusip: Optional[str] = Field(
default=None,
description="CUSIP of the holding.",
coerce_numbers_to_str=True,
)
lei: Optional[str] = Field(
default=None,
description="The LEI of the holding.",
coerce_numbers_to_str=True,
)
isin: Optional[str] = Field(
default=None,
description="The ISIN of the holding.",
coerce_numbers_to_str=True,
)
other_id: Optional[str] = Field(
description="Internal identifier for the holding.", default=None
)
is_restricted: Optional[str] = Field(
description="Whether the holding is restricted.",
default=None,
)
fair_value_level: Optional[int] = Field(
description="The fair value level of the holding.",
default=None,
)
is_cash_collateral: Optional[str] = Field(
description="Whether the holding is cash collateral.",
default=None,
)
is_non_cash_collateral: Optional[str] = Field(
description="Whether the holding is non-cash collateral.",
default=None,
)
is_loan_by_fund: Optional[str] = Field(
description="Whether the holding is loan by fund.",
default=None,
)
loan_value: Optional[float] = Field(
description="The loan value of the holding.",
default=None,
)
issuer_conditional: Optional[str] = Field(
description="The issuer conditions of the holding.", default=None
)
asset_conditional: Optional[str] = Field(
description="The asset conditions of the holding.", default=None
)
payoff_profile: Optional[str] = Field(
description="The payoff profile of the holding.",
default=None,
)
asset_category: Optional[str] = Field(
description="The asset category of the holding.", default=None
)
issuer_category: Optional[str] = Field(
description="The issuer category of the holding.",
default=None,
)
country: Optional[str] = Field(
description="The country of the holding.", default=None
)
balance: Optional[Union[int, float]] = Field(
description="The balance of the holding, in shares or units.", default=None
)
units: Optional[Union[int, float, str]] = Field(
description="The type of units.", default=None
)
currency: Optional[str] = Field(
description="The currency of the holding.", default=None
)
value: Optional[Union[int, float]] = Field(
description="The value of the holding, in dollars.",
default=None,
json_schema_extra={"x-unit_measurement": "currency"},
)
weight: Optional[float] = Field(
description="The weight of the holding, as a normalized percent.",
default=None,
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)

View File

@@ -13,7 +13,7 @@ from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field, NonNegativeInt, field_validator
from pydantic import Field, field_validator
class PriceTargetQueryParams(QueryParams):
@@ -22,8 +22,8 @@ class PriceTargetQueryParams(QueryParams):
symbol: Optional[str] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("symbol", "")
)
limit: NonNegativeInt = Field(
default=200, description=QUERY_DESCRIPTIONS.get("limit", "")
limit: Optional[int] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
@field_validator("symbol", mode="before", check_fields=False)

View File

@@ -1,6 +1,6 @@
"""Price Target Consensus Standard Model."""
from typing import List, Optional, Set, Union
from typing import Optional
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -42,11 +42,3 @@ class PriceTargetConsensusData(Data):
target_median: Optional[float] = Field(
default=None, description="Median target of the price target consensus."
)
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def to_upper(cls, v: Union[str, List[str], Set[str]]):
"""Convert field to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])

View File

@@ -1,7 +1,10 @@
"""Share Statistics Standard Model."""
from datetime import date as dateType
from typing import List, Optional, Set, Union
from datetime import (
date as dateType,
datetime,
)
from typing import Optional, Union
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -28,28 +31,18 @@ class ShareStatisticsData(Data):
"""Share Statistics Data."""
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
date: Optional[dateType] = Field(
date: Optional[Union[dateType, datetime]] = Field(
default=None, description=DATA_DESCRIPTIONS.get("date", "")
)
free_float: Optional[float] = Field(
default=None,
description="Percentage of unrestricted shares of a publicly-traded company.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
float_shares: Optional[float] = Field(
float_shares: Optional[Union[int, float]] = Field(
default=None,
description="Number of shares available for trading by the general public.",
)
outstanding_shares: Optional[float] = Field(
outstanding_shares: Optional[Union[int, float]] = Field(
default=None, description="Total number of shares of a publicly-traded company."
)
source: Optional[str] = Field(
default=None, description="Source of the received data."
)
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
def to_upper(cls, v: Union[str, List[str], Set[str]]):
"""Convert field to uppercase."""
if isinstance(v, str):
return v.upper()
return ",".join([symbol.upper() for symbol in list(v)])

View File

@@ -4,7 +4,7 @@ from datetime import (
date as dateType,
datetime,
)
from typing import Dict, List, Optional
from typing import Any, Optional
from dateutil.relativedelta import relativedelta
from openbb_core.provider.abstract.data import Data
@@ -19,16 +19,19 @@ from pydantic import Field, NonNegativeInt, field_validator
class WorldNewsQueryParams(QueryParams):
"""World News Query."""
limit: NonNegativeInt = Field(
default=2500,
description=QUERY_DESCRIPTIONS.get("limit", "")
+ " The number of articles to return.",
)
start_date: Optional[dateType] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("start_date", "")
default=None,
description=QUERY_DESCRIPTIONS.get("start_date", "")
+ " The default is 2 weeks ago.",
)
end_date: Optional[dateType] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("end_date", "")
default=None,
description=QUERY_DESCRIPTIONS.get("end_date", "") + " The default is today.",
)
limit: Optional[NonNegativeInt] = Field(
default=None,
description=QUERY_DESCRIPTIONS.get("limit", "")
+ " The number of articles to return.",
)
@field_validator("start_date", mode="before")
@@ -53,12 +56,15 @@ class WorldNewsData(Data):
"""World News Data."""
date: datetime = Field(
description=DATA_DESCRIPTIONS.get("date", "")
+ " The published date of the article."
description=DATA_DESCRIPTIONS.get("date", "") + " The date of publication."
)
title: str = Field(description="Title of the article.")
images: Optional[List[Dict[str, str]]] = Field(
author: Optional[str] = Field(default=None, description="Author of the article.")
excerpt: Optional[str] = Field(
default=None, description="Excerpt of the article text."
)
body: Optional[str] = Field(default=None, description="Body of the article text.")
images: Optional[Any] = Field(
default=None, description="Images associated with the article."
)
text: Optional[str] = Field(default=None, description="Text/body of the article.")
url: Optional[str] = Field(default=None, description="URL to the article.")

View File

@@ -10,7 +10,6 @@ from typing import (
TYPE_CHECKING,
Awaitable,
Callable,
List,
Literal,
Optional,
TypeVar,
@@ -25,6 +24,7 @@ from openbb_core.provider.utils.client import (
ClientSession,
get_user_agent,
)
from openbb_core.provider.utils.errors import UnauthorizedError
from typing_extensions import ParamSpec
if TYPE_CHECKING:
@@ -35,14 +35,14 @@ P = ParamSpec("P")
D = TypeVar("D", bound="Data")
def check_item(item: str, allowed: List[str], threshold: float = 0.75) -> None:
def check_item(item: str, allowed: list[str], threshold: float = 0.75) -> None:
"""Check if an item is in a list of allowed items and raise an error if not.
Parameters
----------
item : str
The item to check.
allowed : List[str]
allowed : list[str]
The list of allowed items.
threshold : float, optional
The similarity threshold for the error message, by default 0.75
@@ -62,7 +62,7 @@ def check_item(item: str, allowed: List[str], threshold: float = 0.75) -> None:
raise ValueError(f"'{item}' is not available.")
def get_querystring(items: dict, exclude: List[str]) -> str:
def get_querystring(items: dict, exclude: list[str]) -> str:
"""Turn a dictionary into a querystring, excluding the keys in the exclude list.
Parameters
@@ -70,7 +70,7 @@ def get_querystring(items: dict, exclude: List[str]) -> str:
items: dict
The dictionary to be turned into a querystring.
exclude: List[str]
exclude: list[str]
The keys to be excluded from the querystring.
Returns
@@ -202,13 +202,13 @@ def get_requests_session(**kwargs) -> "Session":
if cookies := python_settings.get("cookies"):
_session.cookies = (
cookies
if isinstance(cookies, requests.cookies.RequestsCookieJar)
else requests.cookies.cookiejar_from_dict(cookies)
if isinstance(cookies, requests.cookies.RequestsCookieJar) # type: ignore
else requests.cookies.cookiejar_from_dict(cookies) # type: ignore
)
if auth := python_settings.get("auth"):
_session.auth = (
auth if isinstance(auth, (tuple, requests.auth.AuthBase)) else tuple(auth)
auth if isinstance(auth, (tuple, requests.auth.AuthBase)) else tuple(auth) # type: ignore
)
if kwargs:
@@ -349,10 +349,10 @@ async def amake_request(
method: Literal["GET", "POST"] = "GET",
timeout: int = 10,
response_callback: Optional[
Callable[[ClientResponse, ClientSession], Awaitable[Union[dict, List[dict]]]]
Callable[[ClientResponse, ClientSession], Awaitable[Union[dict, list[dict]]]]
] = None,
**kwargs,
) -> Union[dict, List[dict]]:
) -> Union[dict, list[dict]]:
"""
Abstract helper to make requests from a url with potential headers and params.
@@ -364,7 +364,7 @@ async def amake_request(
HTTP method to use. Can be "GET" or "POST", by default "GET"
timeout : int, optional
Timeout in seconds, by default 10. Can be overwritten by user setting, request_timeout
response_callback : Callable[[ClientResponse, ClientSession], Awaitable[Union[dict, List[dict]]]], optional
response_callback : Callable[[ClientResponse, ClientSession], Awaitable[Union[dict, list[dict]]]], optional
Async callback with response and session as arguments that returns the json, by default None
session : ClientSession, optional
Custom session to use for requests, by default None
@@ -372,7 +372,7 @@ async def amake_request(
Returns
-------
Union[dict, List[dict]]
Union[dict, list[dict]]
Response json
"""
if method.upper() not in ["GET", "POST"]:
@@ -396,9 +396,9 @@ async def amake_request(
async def amake_requests(
urls: Union[str, List[str]],
urls: Union[str, list[str]],
response_callback: Optional[
Callable[[ClientResponse, ClientSession], Awaitable[Union[dict, List[dict]]]]
Callable[[ClientResponse, ClientSession], Awaitable[Union[dict, list[dict]]]]
] = None,
**kwargs,
):
@@ -406,28 +406,30 @@ async def amake_requests(
Parameters
----------
urls : Union[str, List[str]]
List of urls to make requests to
urls : Union[str, list[str]]
list of urls to make requests to
method : Literal["GET", "POST"], optional
HTTP method to use. Can be "GET" or "POST", by default "GET"
timeout : int, optional
Timeout in seconds, by default 10. Can be overwritten by user setting, request_timeout
response_callback : Callable[[ClientResponse, ClientSession], Awaitable[Union[dict, List[dict]]]], optional
response_callback : Callable[[ClientResponse, ClientSession], Awaitable[Union[dict, list[dict]]]], optional
Async callback with response and session as arguments that returns the json, by default None
session : ClientSession, optional
Custom session to use for requests, by default None
Returns
-------
Union[dict, List[dict]]
Union[dict, list[dict]]
Response json
"""
session = kwargs.pop("session", await get_async_requests_session(**kwargs))
ret_exceptions = kwargs.pop("return_exceptions", False)
kwargs["response_callback"] = response_callback
urls = urls if isinstance(urls, list) else [urls]
try:
results: list = []
exceptions: list = []
for result in await asyncio.gather(
*[amake_request(url, session=session, **kwargs) for url in urls],
@@ -435,15 +437,30 @@ async def amake_requests(
):
is_exception = isinstance(result, Exception)
if is_exception and kwargs.get("raise_for_status", False):
if is_exception and (
isinstance(result, UnauthorizedError)
or kwargs.get("raise_for_status", False)
):
raise result # type: ignore[misc]
if is_exception or not result:
if is_exception and ret_exceptions:
results.append(result) # type: ignore[arg-type]
continue
results.extend(
result if isinstance(result, list) else [result] # type: ignore[list-item]
)
if is_exception:
exceptions.append(result) # type: ignore[arg-type]
continue
if not result:
continue
if not isinstance(result, Exception):
results.extend(
result if isinstance(result, list) else [result] # type: ignore[list-item]
)
if exceptions and not results and not ret_exceptions:
raise exceptions[0] # type: ignore
return results
@@ -593,8 +610,8 @@ def run_async(
def filter_by_dates(
data: List[D], start_date: Optional[date] = None, end_date: Optional[date] = None
) -> List[D]:
data: list[D], start_date: Optional[date] = None, end_date: Optional[date] = None
) -> list[D]:
"""Filter data by dates."""
if start_date is None and end_date is None:
return data

View File

@@ -317,6 +317,7 @@ def test_equity_fundamental_historical_splits(params, headers):
"start_date": "2021-01-01",
"end_date": "2023-06-06",
"provider": "fmp",
"limit": None,
}
),
(
@@ -330,6 +331,7 @@ def test_equity_fundamental_historical_splits(params, headers):
{
"symbol": "AAPL",
"provider": "fmp",
"limit": None,
}
),
(
@@ -362,7 +364,17 @@ def test_equity_fundamental_dividends(params, headers):
@pytest.mark.parametrize(
"params",
[{"symbol": "AAPL", "provider": "fmp"}],
[
(
{
"symbol": "AAPL",
"provider": "fmp",
"start_date": None,
"end_date": None,
"limit": None,
}
),
],
)
@pytest.mark.integration
def test_equity_fundamental_employee_count(params, headers):
@@ -565,6 +577,7 @@ def test_equity_fundamental_income_growth(params, headers):
"symbol": "AAPL",
"limit": 10,
"transaction_type": None,
"statistics": False,
}
),
(
@@ -608,8 +621,8 @@ def test_equity_ownership_insider_trading(params, headers):
(
{
"symbol": "AAPL",
"include_current_quarter": True,
"date": "2021-09-30",
"year": 2024,
"quarter": 4,
"provider": "fmp",
}
),
@@ -656,6 +669,13 @@ def test_equity_ownership_institutional(params, headers):
"is_spo": False,
}
),
(
{
"start_date": "2023-01-01",
"end_date": "2023-11-01",
"provider": "fmp",
}
),
],
)
@pytest.mark.integration
@@ -678,11 +698,11 @@ def test_equity_calendar_ipo(params, headers):
"provider": "fmp",
"symbol": "AAPL",
"period": "annual",
"limit": 100,
"with_ttm": False,
"limit": 2,
"ttm": "include",
}
),
({"provider": "intrinio", "symbol": "AAPL", "limit": 100}),
({"provider": "intrinio", "symbol": "AAPL"}),
({"provider": "yfinance", "symbol": "AAPL"}),
({"provider": "finviz", "symbol": "AAPL,GOOG"}),
],
@@ -720,7 +740,18 @@ def test_equity_fundamental_management(params, headers):
@pytest.mark.parametrize(
"params",
[{"symbol": "AAPL", "date": "2023-01-01", "page": 1, "provider": "fmp"}],
[
(
{
"symbol": "AAPL",
"year": 2024,
"quarter": 1,
"page": None,
"limit": None,
"provider": "fmp",
}
),
],
)
@pytest.mark.integration
def test_equity_ownership_major_holders(params, headers):
@@ -737,7 +768,7 @@ def test_equity_ownership_major_holders(params, headers):
@pytest.mark.parametrize(
"params",
[
({"symbol": "AAPL", "limit": 10, "provider": "fmp", "with_grade": True}),
({"symbol": "AAPL", "limit": 10, "provider": "fmp"}),
({"symbol": "AAPL", "provider": "finviz"}),
(
{
@@ -844,7 +875,15 @@ def test_equity_estimates_consensus(params, headers):
@pytest.mark.parametrize(
"params",
[
({"symbol": "AAPL", "period": "annual", "limit": 12, "provider": "fmp"}),
(
{
"symbol": "AAPL",
"period": "annual",
"limit": 2,
"ttm": "include",
"provider": "fmp",
}
),
(
{
"symbol": "AAPL",
@@ -1000,7 +1039,7 @@ def test_equity_ownership_share_statistics(params, headers):
@pytest.mark.parametrize(
"params",
[{"symbol": "AAPL", "year": 2023}],
[{"symbol": "AAPL", "year": 2023, "quarter": 2, "provider": "fmp"}],
)
@pytest.mark.integration
def test_equity_fundamental_transcript(params, headers):
@@ -1087,6 +1126,7 @@ def test_equity_compare_groups(params, headers):
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": "1d",
"adjustment": "splits_only",
}
),
(
@@ -1096,6 +1136,7 @@ def test_equity_compare_groups(params, headers):
"symbol": "AAPL,MSFT",
"start_date": None,
"end_date": None,
"adjustment": None,
}
),
(
@@ -1237,22 +1278,6 @@ def test_equity_price_historical(params, headers):
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[{"symbol": "AAPL", "provider": "fmp"}],
)
@pytest.mark.integration
def test_equity_fundamental_multiples(params, headers):
"""Test the equity fundamental multiples endpoint."""
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/fundamental/multiples?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
@@ -1579,6 +1604,7 @@ def test_equity_profile(params, headers):
[
({"sort": "desc", "provider": "yfinance", "limit": 10}),
({"provider": "tmx", "category": "52w_high"}),
({"provider": "fmp"}),
],
)
@pytest.mark.integration
@@ -1595,7 +1621,10 @@ def test_equity_discovery_gainers(params, headers):
@pytest.mark.parametrize(
"params",
[{"sort": "desc", "provider": "yfinance", "limit": 10}],
[
({"sort": "desc", "provider": "yfinance", "limit": 10}),
({"provider": "fmp"}),
],
)
@pytest.mark.integration
def test_equity_discovery_losers(params, headers):
@@ -1611,7 +1640,10 @@ def test_equity_discovery_losers(params, headers):
@pytest.mark.parametrize(
"params",
[{"sort": "desc", "provider": "yfinance", "limit": 10}],
[
({"sort": "desc", "provider": "yfinance", "limit": 10}),
({"provider": "fmp"}),
],
)
@pytest.mark.integration
def test_equity_discovery_active(params, headers):
@@ -1735,7 +1767,6 @@ def test_equity_discovery_top_retail(params, headers):
"end_date": None,
"limit": 10,
"form_type": None,
"is_done": None,
"provider": "fmp",
}
),
@@ -1745,7 +1776,6 @@ def test_equity_discovery_top_retail(params, headers):
"end_date": "2023-11-07",
"limit": 50,
"form_type": "10-Q",
"is_done": "true",
"provider": "fmp",
}
),
@@ -2255,3 +2285,26 @@ def test_equity_calendar_events(params, headers):
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"symbol": "AAPL",
"provider": "fmp",
}
),
],
)
@pytest.mark.integration
def test_equity_fundamental_esg_score(params, headers):
"""Test the equity fundamental esg score endpoint."""
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/fundamental/esg_score?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200

View File

@@ -294,6 +294,7 @@ def test_equity_fundamental_historical_splits(params, obb):
"start_date": "2021-01-01",
"end_date": "2023-06-06",
"provider": "fmp",
"limit": None,
}
),
(
@@ -307,6 +308,7 @@ def test_equity_fundamental_historical_splits(params, obb):
{
"symbol": "AAPL",
"provider": "fmp",
"limit": None,
}
),
(
@@ -337,7 +339,15 @@ def test_equity_fundamental_dividends(params, obb):
@pytest.mark.parametrize(
"params",
[
({"symbol": "AAPL", "provider": "fmp"}),
(
{
"symbol": "AAPL",
"provider": "fmp",
"start_date": None,
"end_date": None,
"limit": None,
}
),
],
)
@pytest.mark.integration
@@ -454,6 +464,7 @@ def test_equity_fundamental_income_growth(params, obb):
"symbol": "AAPL",
"limit": 10,
"transaction_type": None,
"statistics": False,
}
),
(
@@ -494,8 +505,8 @@ def test_equity_ownership_insider_trading(params, obb):
(
{
"symbol": "AAPL",
"include_current_quarter": True,
"date": "2021-09-30",
"year": 2024,
"quarter": 4,
"provider": "fmp",
}
),
@@ -539,6 +550,13 @@ def test_equity_ownership_institutional(params, obb):
"is_spo": False,
}
),
(
{
"start_date": "2023-01-01",
"end_date": "2023-11-01",
"provider": "fmp",
}
),
],
)
@pytest.mark.integration
@@ -553,17 +571,17 @@ def test_equity_calendar_ipo(params, obb):
@pytest.mark.parametrize(
"params",
[
({"symbol": "AAPL", "period": "annual", "limit": 100}),
({"symbol": "AAPL", "period": "annual"}),
(
{
"provider": "fmp",
"symbol": "AAPL",
"period": "annual",
"limit": 100,
"with_ttm": False,
"limit": 2,
"ttm": "include",
}
),
({"provider": "intrinio", "symbol": "AAPL", "limit": 100}),
({"provider": "intrinio", "symbol": "AAPL"}),
({"provider": "yfinance", "symbol": "AAPL"}),
({"provider": "finviz", "symbol": "AAPL,GOOG"}),
],
@@ -599,7 +617,16 @@ def test_equity_fundamental_management(params, obb):
@pytest.mark.parametrize(
"params",
[
({"symbol": "AAPL", "date": "2023-01-01", "page": 1, "provider": "fmp"}),
(
{
"symbol": "AAPL",
"year": 2024,
"quarter": 1,
"page": None,
"limit": None,
"provider": "fmp",
}
),
],
)
@pytest.mark.integration
@@ -614,7 +641,7 @@ def test_equity_ownership_major_holders(params, obb):
@pytest.mark.parametrize(
"params",
[
({"symbol": "AAPL", "limit": 10, "provider": "fmp", "with_grade": True}),
({"symbol": "AAPL", "limit": 10, "provider": "fmp"}),
(
{
"symbol": "AAPL",
@@ -799,7 +826,15 @@ def test_equity_estimates_forward_ebitda(params, obb):
@pytest.mark.parametrize(
"params",
[
({"symbol": "AAPL", "period": "annual", "limit": 12, "provider": "fmp"}),
(
{
"symbol": "AAPL",
"period": "annual",
"limit": 2,
"ttm": "include",
"provider": "fmp",
}
),
(
{
"symbol": "AAPL",
@@ -955,7 +990,7 @@ def test_equity_ownership_share_statistics(params, obb):
@pytest.mark.parametrize(
"params",
[
({"symbol": "AAPL", "year": 2023}),
({"symbol": "AAPL", "year": 2023, "quarter": 2, "provider": "fmp"}),
],
)
@pytest.mark.integration
@@ -1036,6 +1071,7 @@ def test_equity_compare_groups(params, obb):
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": "1d",
"adjustment": "splits_only",
}
),
(
@@ -1045,6 +1081,7 @@ def test_equity_compare_groups(params, obb):
"symbol": "AAPL,MSFT",
"start_date": None,
"end_date": None,
"adjustment": None,
}
),
(
@@ -1182,21 +1219,6 @@ def test_equity_price_historical(params, obb):
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
({"symbol": "AAPL", "provider": "fmp"}),
],
)
@pytest.mark.integration
def test_equity_fundamental_multiples(params, obb):
"""Test the equity fundamental multiples endpoint."""
result = obb.equity.fundamental.multiples(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
@@ -1509,6 +1531,7 @@ def test_equity_profile(params, obb):
[
({"sort": "desc", "provider": "yfinance", "limit": 10}),
({"provider": "tmx", "category": "52w_high"}),
({"provider": "fmp"}),
],
)
@pytest.mark.integration
@@ -1524,7 +1547,10 @@ def test_equity_discovery_gainers(params, obb):
@pytest.mark.parametrize(
"params",
[{"sort": "desc", "provider": "yfinance", "limit": 10}],
[
({"sort": "desc", "provider": "yfinance", "limit": 10}),
({"provider": "fmp"}),
],
)
@pytest.mark.integration
def test_equity_discovery_losers(params, obb):
@@ -1539,7 +1565,10 @@ def test_equity_discovery_losers(params, obb):
@pytest.mark.parametrize(
"params",
[{"sort": "desc", "provider": "yfinance", "limit": 10}],
[
({"sort": "desc", "provider": "yfinance", "limit": 10}),
({"provider": "fmp"}),
],
)
@pytest.mark.integration
def test_equity_discovery_active(params, obb):
@@ -1654,7 +1683,6 @@ def test_equity_discovery_top_retail(params, obb):
"end_date": None,
"limit": 10,
"form_type": "1-A",
"is_done": None,
"provider": "fmp",
}
),
@@ -1664,7 +1692,6 @@ def test_equity_discovery_top_retail(params, obb):
"end_date": "2023-11-07",
"limit": 50,
"form_type": "10-Q",
"is_done": "true",
"provider": "fmp",
}
),
@@ -2111,3 +2138,23 @@ def test_equity_calendar_events(params, obb):
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"symbol": "AAPL",
"provider": "fmp",
}
),
],
)
@pytest.mark.integration
def test_equity_fundamental_esg_score(params, obb):
"""Test the equity fundamental esg score endpoint."""
result = obb.equity.fundamental.esg_score(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0

View File

@@ -444,7 +444,11 @@ async def historical_splits(
@router.command(
model="EarningsCallTranscript",
examples=[APIEx(parameters={"symbol": "AAPL", "year": 2020, "provider": "fmp"})],
examples=[
APIEx(
parameters={"symbol": "AAPL", "year": 2020, "quarter": 1, "provider": "fmp"}
)
],
)
async def transcript(
cc: CommandContext,
@@ -526,3 +530,20 @@ async def management_discussion_analysis(
) -> OBBject:
"""Get the Management Discussion & Analysis section from the financial statements for a given company."""
return await OBBject.from_query(Query(**locals()))
@router.command(
model="EsgScore",
examples=[
APIEx(parameters={"symbol": "AAPL", "provider": "fmp"}),
APIEx(parameters={"symbol": "TSLA,F", "provider": "fmp"}),
],
)
async def esg_score(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Get ESG (Environmental, Social, and Governance) scores from company disclosures."""
return await OBBject.from_query(Query(**locals()))

View File

@@ -35,7 +35,12 @@ async def major_holders(
@router.command(
model="InstitutionalOwnership",
examples=[APIEx(parameters={"symbol": "AAPL", "provider": "fmp"})],
examples=[
APIEx(parameters={"symbol": "AAPL", "provider": "fmp"}),
APIEx(
parameters={"symbol": "AAPL", "year": 2024, "quarter": 2, "provider": "fmp"}
),
],
)
async def institutional(
cc: CommandContext,
@@ -43,7 +48,7 @@ async def institutional(
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Get data about institutional ownership for a given company over time."""
"""Net statistics on institutional ownership for a given company, reported on 13-F filings."""
return await OBBject.from_query(Query(**locals()))

View File

@@ -96,6 +96,7 @@ def test_etf_search(params, headers):
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": "1d",
"adjustment": "splits_only",
}
),
(
@@ -279,16 +280,25 @@ def test_etf_sectors(params, headers):
@pytest.mark.parametrize(
"params",
[
({"symbol": "QQQ", "cik": None, "provider": "fmp"}),
({"symbol": "DIA", "year": 2025, "quarter": 1, "provider": "fmp"}),
(
{
"symbol": "DIA",
"year": 2025,
"quarter": 1,
"provider": "sec",
"use_cache": True,
}
),
],
)
@pytest.mark.integration
def test_etf_holdings_date(params, headers):
"""Test the ETF holdings date endpoint."""
def test_etf_nport_disclosure(params, headers):
"""Test the ETF nport disclosure endpoint."""
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/etf/holdings_date?{query_str}"
url = f"http://0.0.0.0:8000/api/v1/etf/nport_disclosure?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@@ -300,35 +310,9 @@ def test_etf_holdings_date(params, headers):
(
{
"symbol": "IOO",
"date": "2023-03-31",
"cik": None,
"provider": "fmp",
}
),
(
{
"symbol": "VOO",
"date": "2023-03-31",
"cik": None,
"provider": "fmp",
}
),
(
{
"symbol": "TQQQ",
"date": None,
"provider": "sec",
"use_cache": False,
}
),
(
{
"symbol": "QQQ",
"date": "2021-06-30",
"provider": "sec",
"use_cache": False,
}
),
(
{
"symbol": "XIU",

View File

@@ -91,6 +91,7 @@ def test_etf_search(params, obb):
"start_date": "2023-01-01",
"end_date": "2023-06-06",
"interval": "1d",
"adjustment": "splits_only",
}
),
(
@@ -269,15 +270,24 @@ def test_etf_sectors(params, obb):
@pytest.mark.parametrize(
"params",
[
({"symbol": "QQQ", "cik": None, "provider": "fmp"}),
({"symbol": "QQQ", "year": 2025, "quarter": 1, "provider": "fmp"}),
(
{
"symbol": "DIA",
"year": 2025,
"quarter": 1,
"provider": "sec",
"use_cache": True,
}
),
],
)
@pytest.mark.integration
def test_etf_holdings_date(params, obb):
"""Test the ETF holdings date endpoint."""
def test_etf_nport_disclosure(params, obb):
"""Test the ETF nport disclosure endpoint."""
params = {p: v for p, v in params.items() if v}
result = obb.etf.holdings_date(**params)
result = obb.etf.nport_disclosure(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@@ -286,19 +296,9 @@ def test_etf_holdings_date(params, obb):
@pytest.mark.parametrize(
"params",
[
(
{
"symbol": "IOO",
"date": "2023-03-31",
"cik": None,
"provider": "fmp",
}
),
(
{
"symbol": "SILJ",
"date": "2019-12-31",
"cik": None,
"provider": "fmp",
}
),

View File

@@ -1,7 +1,7 @@
"""ETF Router."""
from openbb_core.app.model.command_context import CommandContext
from openbb_core.app.model.example import APIEx
from openbb_core.app.model.example import APIEx, PythonEx
from openbb_core.app.model.obbject import OBBject
from openbb_core.app.provider_interface import (
ExtraParams,
@@ -136,10 +136,6 @@ async def price_performance(
model="EtfHoldings",
examples=[
APIEx(parameters={"symbol": "XLK", "provider": "fmp"}),
APIEx(
description="Including a date (FMP, SEC) will return the holdings as per NPORT-P filings.",
parameters={"symbol": "XLK", "date": "2022-03-31", "provider": "fmp"},
),
APIEx(
description="The same data can be returned from the SEC directly.",
parameters={"symbol": "XLK", "date": "2022-03-31", "provider": "sec"},
@@ -157,16 +153,32 @@ async def holdings(
@router.command(
model="EtfHoldingsDate",
examples=[APIEx(parameters={"symbol": "XLK", "provider": "fmp"})],
model="NportDisclosure",
examples=[
APIEx(
parameters={"symbol": "XLK", "provider": "fmp", "year": 2025, "quarter": 1}
),
APIEx(
description="The same data can be returned from the SEC directly.",
parameters={"symbol": "XLK", "provider": "sec", "year": 2025, "quarter": 1},
),
PythonEx(
description="Additional disclosures, such as flow and returns are included in the SEC's response"
+ " under the `extra['results_metadata']` field.",
code=[
"response = obb.etf.nport_disclosure(symbol='XLK', provider='sec', year=2025, quarter=1)",
"print(response.extra['results_metadata'])",
],
),
],
)
async def holdings_date(
async def nport_disclosure(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Use this function to get the holdings dates, if available."""
"""Get SEC NPORT-P disclosure filings for a given ETF or mutual fund (US only)."""
return await OBBject.from_query(Query(**locals()))

View File

@@ -171,10 +171,10 @@ class FixedIncomeViews:
figure.update_layout(
title=dict(text=title, x=0.5, font=dict(size=20)),
plot_bgcolor=(
"rgba(0,0,0,0)" if text_color == "white" else "rgba(255,255,255,0)"
"rgba(21,21,21,0)" if text_color == "white" else "rgba(230,230,230,0)"
),
paper_bgcolor=(
"rgba(0,0,0,0)" if text_color == "white" else "rgba(255,255,255,0)"
"rgba(21,21,21,1)" if text_color == "white" else "rgba(230,230,230,1)"
),
xaxis=dict(
title="Maturity",

View File

@@ -50,6 +50,8 @@ def headers():
"limit": 30,
"start_date": None,
"end_date": None,
"topic": "general",
"page": 1,
}
),
(
@@ -154,6 +156,7 @@ def test_news_world(params, headers):
"page": 1,
"start_date": None,
"end_date": None,
"press_release": False,
}
),
(

View File

@@ -46,6 +46,8 @@ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
"limit": 20,
"start_date": None,
"end_date": None,
"page": 0,
"topic": "general",
}
),
(
@@ -136,6 +138,7 @@ def test_news_world(params, obb):
"page": 1,
"start_date": None,
"end_date": None,
"press_release": False,
}
),
(

View File

@@ -163,10 +163,10 @@ def modify_query_schema(query_schema: list[dict], provider_value: str):
]
)
if "x-widget_config" in _item:
if xwidget := _item.pop("x-widget_config", {}):
provider_value_widget_config[
provider_value if provider_value else "custom"
] = _item.pop("x-widget_config", {})
] = xwidget.get(provider_value if provider_value else "custom", {})
if (
provider_value_widget_config
@@ -176,11 +176,12 @@ def modify_query_schema(query_schema: list[dict], provider_value: str):
if provider_value_widget_config[provider_value].get("exclude"):
continue
_item = deep_merge_configs(
_item,
provider_value_widget_config[provider_value],
["paramName", "value"],
)
if provider_value_widget_config[provider_value]:
_item = deep_merge_configs(
_item,
provider_value_widget_config[provider_value],
["paramName", "value"],
)
if not _item.get("label") and _item["paramName"] in [
"url",
@@ -200,24 +201,6 @@ def modify_query_schema(query_schema: list[dict], provider_value: str):
]
)
if "x-widget_config" in _item:
provider_value_widget_config = _item.pop("x-widget_config")
_item.update(provider_value_widget_config)
if (
provider_value_widget_config
and provider_value in provider_value_widget_config
):
if provider_value_widget_config[provider_value].get("exclude"):
continue
_item = deep_merge_configs(
_item,
provider_value_widget_config[provider_value],
["paramName", "value"],
)
if (
_item.get("multiSelect") is True
and _item.get("type") == "text"

View File

@@ -12,7 +12,7 @@ from dataclasses import dataclass
from importlib.metadata import EntryPoint, entry_points
from inspect import getmembers, isfunction
from sys import version_info
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from typing import Any, Optional, Set, Tuple, Union
from importlib_metadata import EntryPoints
from openbb_core.app.provider_interface import ProviderInterface
@@ -22,9 +22,9 @@ pi = ProviderInterface()
logging.basicConfig(level=logging.INFO)
def get_packages_info() -> Dict[str, str]:
def get_packages_info() -> dict[str, str]:
"""Get the paths and names of all the static packages."""
paths_and_names: Dict[str, str] = {}
paths_and_names: dict[str, str] = {}
package_paths = glob.glob("openbb_platform/core/openbb/package/*.py")
for path in package_paths:
name = os.path.basename(path).split(".")[0]
@@ -36,7 +36,7 @@ def get_packages_info() -> Dict[str, str]:
return paths_and_names
def execute_docstring_examples(module_name: str, path: str) -> List[str]:
def execute_docstring_examples(module_name: str, path: str) -> list[str]:
"""Execute the docstring examples of a module."""
errors = []
module = importlib.import_module(f"openbb.package.{module_name}")
@@ -57,7 +57,7 @@ def execute_docstring_examples(module_name: str, path: str) -> List[str]:
return errors
def check_docstring_examples() -> List[str]:
def check_docstring_examples() -> list[str]:
"""Test that the docstring examples execute without errors."""
errors = []
paths_and_names = get_packages_info()
@@ -77,7 +77,7 @@ def filter_eps(eps: Union[EntryPoints, dict], group: str) -> Tuple[EntryPoint, .
def list_openbb_extensions() -> Tuple[Set[str], Set[str], Set[str]]:
"""List installed openbb extensions and providers.
"""list installed openbb extensions and providers.
Returns
-------
@@ -103,9 +103,9 @@ def list_openbb_extensions() -> Tuple[Set[str], Set[str], Set[str]]:
group="openbb_obbject_extension"
)
else:
core_entry_points = entry_points_dict.get("openbb_core_extension", [])
provider_entry_points = entry_points_dict.get("openbb_provider_extension", [])
obbject_entry_points = entry_points_dict.get("openbb_obbject_extension", [])
core_entry_points = entry_points_dict.get("openbb_core_extension", []) # type: ignore pylint: disable=E1101
provider_entry_points = entry_points_dict.get("openbb_provider_extension", []) # type: ignore pylint: disable=E1101
obbject_entry_points = entry_points_dict.get("openbb_obbject_extension", []) # type: ignore pylint: disable=E1101
for entry_point in core_entry_points:
core_extensions.add(f"{entry_point.name}")
@@ -119,7 +119,7 @@ def list_openbb_extensions() -> Tuple[Set[str], Set[str], Set[str]]:
return core_extensions, provider_extensions, obbject_extensions
def collect_routers(target_dir: str) -> List[str]:
def collect_routers(target_dir: str) -> list[str]:
"""Collect all routers in the target directory."""
current_dir = os.path.dirname(__file__)
base_path = os.path.abspath(os.path.join(current_dir, "../../../"))
@@ -139,9 +139,9 @@ def collect_routers(target_dir: str) -> List[str]:
return routers
def import_routers(routers: List) -> List:
def import_routers(routers: list) -> list:
"""Import all routers."""
loaded_routers: List = []
loaded_routers: list = []
for router in routers:
module = importlib.import_module(router)
loaded_routers.append(module)
@@ -149,7 +149,7 @@ def import_routers(routers: List) -> List:
return loaded_routers
def collect_router_functions(loaded_routers: List) -> Dict:
def collect_router_functions(loaded_routers: list) -> dict:
"""Collect all router functions."""
router_functions = {}
for router in loaded_routers:
@@ -235,10 +235,10 @@ def get_decorator_details(function) -> Optional[Decorator]:
def find_missing_router_function_models(
router_functions: Dict, pi_map: Dict
) -> List[str]:
router_functions: dict, pi_map: dict
) -> list[str]:
"""Find the missing models in the router functions."""
missing_models: List[str] = []
missing_models: list[str] = []
for router_name, functions in router_functions.items():
for function in functions:
decorator = find_decorator(
@@ -251,7 +251,12 @@ def find_missing_router_function_models(
and "POST" not in decorator
and "GET" not in decorator
):
model = decorator.split("model=")[1].split(",")[0].strip('"')
if (
returns := str(function.__annotations__.get("return"))
) and returns.rsplit(".", maxsplit=1)[-1].startswith("OBBject"):
model = returns.rsplit("_", maxsplit=1)[-1].replace("'>", "")
else:
model = decorator.split("model=")[1].split(",")[0].strip('"')
if (
model not in pi_map
and "POST" not in decorator
@@ -264,7 +269,7 @@ def find_missing_router_function_models(
return missing_models
def parse_example_string(example_string: str) -> Dict[str, Any]:
def parse_example_string(example_string: str) -> dict[str, Any]:
"""Parse a string of examples into nested dictionaries.
This is capturing all instances of PythonEx and APIEx, including their "parameters", "code", and "description".
@@ -296,15 +301,15 @@ def parse_example_string(example_string: str) -> Dict[str, Any]:
return result
def get_required_fields(model: str) -> List[str]:
def get_required_fields(model: str) -> list[str]:
"""Get the required fields of a model."""
fields = pi.map[model]["openbb"]["QueryParams"]["fields"]
return [field for field, info in fields.items() if info.is_required()]
def get_all_fields(model: str) -> List[str]:
def get_all_fields(model: str) -> list[str]:
"""Get all the fields of a model."""
all_fields: List[str] = []
all_fields: list[str] = []
info = pi.map[model]
# for every key, grab the fields
for _, provider_info in info.items():

View File

@@ -57,6 +57,10 @@ class AlphaVantageHistoricalEpsData(HistoricalEpsData):
default=None,
description="Date of the earnings report.",
)
report_time: Optional[str] = Field(
default=None,
description="Time of day when the earnings report was released, e.g., 'post-market'.",
)
@field_validator(
"eps_estimated",

View File

@@ -80,24 +80,14 @@ class BenzingaCompanyNewsData(CompanyNewsData):
__alias_dict__ = {
"symbols": "stocks",
"date": "created",
"text": "body",
"excerpt": "teaser",
"images": "image",
}
id: str = Field(description="Article ID.")
author: Optional[str] = Field(default=None, description="Author of the article.")
teaser: Optional[str] = Field(description="Teaser of the news.", default=None)
images: Optional[List[Dict[str, str]]] = Field(
default=None, description="URL to the images of the news."
)
channels: Optional[str] = Field(
default=None,
description="Channels associated with the news.",
)
stocks: Optional[str] = Field(
description="Stocks associated with the news.",
default=None,
)
tags: Optional[str] = Field(
description="Tags associated with the news.",
default=None,
@@ -105,29 +95,25 @@ class BenzingaCompanyNewsData(CompanyNewsData):
updated: Optional[datetime] = Field(
default=None, description="Updated date of the news."
)
@field_validator("symbols", mode="before", check_fields=False)
@classmethod
def symbols_string(cls, v):
"""Symbols string validator."""
return ",".join([item["name"] for item in v])
id: str = Field(description="Article ID.")
original_id: Optional[str] = Field(
default=None, description="Original ID of the news article."
)
@field_validator("date", "updated", mode="before", check_fields=False)
def date_validate(cls, v): # pylint: disable=E0213
"""Return the date as a datetime object."""
return datetime.strptime(v, "%a, %d %b %Y %H:%M:%S %z")
@field_validator("stocks", "channels", "tags", mode="before", check_fields=False)
@field_validator("symbols", "channels", "tags", mode="before", check_fields=False)
def list_validate(cls, v): # pylint: disable=E0213
"""Return the list as a string."""
return ",".join(
[item.get("name", None) for item in v if item.get("name", None)]
)
return ",".join([item.get("name") for item in v if item.get("name")])
@field_validator("id", mode="before", check_fields=False)
@field_validator("id", "original_id", mode="before", check_fields=False)
def id_validate(cls, v): # pylint: disable=E0213
"""Return the id as a string."""
return str(v)
return str(v) if v else None
class BenzingaCompanyNewsFetcher(
@@ -157,29 +143,28 @@ class BenzingaCompanyNewsFetcher(
from openbb_benzinga.utils.helpers import response_callback
token = credentials.get("benzinga_api_key") if credentials else ""
base_url = "https://api.benzinga.com/api/v2/news"
query.limit = query.limit if query.limit else 2500
model = query.model_dump(by_alias=True)
model["sort"] = (
f"{query.sort}:{query.order}" if query.sort and query.order else ""
)
querystring = get_querystring(model, ["order", "pageSize"])
pages = math.ceil(query.limit / 100) if query.limit else 1
page_size = 100 if query.limit and query.limit > 100 else query.limit
pages = math.ceil(query.limit / page_size) if query.limit else 1
urls = [
f"{base_url}?{querystring}&page={page}&pageSize={page_size}&token={token}"
for page in range(pages)
]
results: list = []
async def get_one(url):
"""Get data for one url."""
try:
response = await amake_request(
url, response_callback=response_callback, **kwargs
url,
response_callback=response_callback,
**kwargs,
)
if response:
results.extend(response)

View File

@@ -283,8 +283,8 @@ class BenzingaPriceTargetFetcher(
from openbb_core.provider.utils.helpers import amake_request, get_querystring
token = credentials.get("benzinga_api_key") if credentials else ""
base_url = "https://api.benzinga.com/api/v2.1/calendar/ratings"
query.limit = query.limit or 200
querystring = get_querystring(query.model_dump(by_alias=True), [])
url = f"{base_url}?{querystring}&token={token}"

View File

@@ -6,7 +6,7 @@ from datetime import (
date as dateType,
datetime,
)
from typing import Any, Dict, List, Literal, Optional
from typing import Any, Literal, Optional
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.fetcher import Fetcher
@@ -77,11 +77,8 @@ class BenzingaWorldNewsQueryParams(WorldNewsQueryParams):
class BenzingaWorldNewsData(WorldNewsData):
"""Benzinga World News Data."""
__alias_dict__ = {"date": "created", "text": "body", "images": "image"}
__alias_dict__ = {"date": "created", "excerpt": "teaser", "images": "image"}
id: str = Field(description="Article ID.")
author: Optional[str] = Field(default=None, description="Author of the news.")
teaser: Optional[str] = Field(description="Teaser of the news.", default=None)
channels: Optional[str] = Field(
default=None,
description="Channels associated with the news.",
@@ -97,6 +94,10 @@ class BenzingaWorldNewsData(WorldNewsData):
updated: Optional[datetime] = Field(
default=None, description="Updated date of the news."
)
id: str = Field(description="Article ID.")
updated_id: Optional[str] = Field(
default=None, description="Updated article ID if the article was updated."
)
@field_validator("date", "updated", mode="before", check_fields=False)
@classmethod
@@ -111,9 +112,7 @@ class BenzingaWorldNewsData(WorldNewsData):
v = ",".join([item.get("name", None) for item in v if item.get("name", None)])
return v if v != "" else None
@field_validator(
"id", "text", "teaser", "title", "author", mode="before", check_fields=False
)
@field_validator("id", "updated_id", mode="before", check_fields=False)
@classmethod
def id_validate(cls, v):
"""Return the a string if the field is not empty."""
@@ -129,22 +128,22 @@ class BenzingaWorldNewsData(WorldNewsData):
class BenzingaWorldNewsFetcher(
Fetcher[
BenzingaWorldNewsQueryParams,
List[BenzingaWorldNewsData],
list[BenzingaWorldNewsData],
]
):
"""Transform the query, extract and transform the data from the Benzinga endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> BenzingaWorldNewsQueryParams:
def transform_query(params: dict[str, Any]) -> BenzingaWorldNewsQueryParams:
"""Transform the query parameters."""
return BenzingaWorldNewsQueryParams(**params)
@staticmethod
async def aextract_data(
query: BenzingaWorldNewsQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Extract the data."""
# pylint: disable=import-outside-toplevel
import asyncio # noqa
@@ -154,17 +153,13 @@ class BenzingaWorldNewsFetcher(
token = credentials.get("benzinga_api_key") if credentials else ""
base_url = "https://api.benzinga.com/api/v2/news"
query = query.model_copy(update={"sort": f"{query.sort}:{query.order}"})
querystring = get_querystring(query.model_dump(by_alias=True), ["order"])
pages = math.ceil(query.limit / 100)
pages = math.ceil((query.limit if query.limit else 2500) / 100)
urls = [
f"{base_url}?{querystring}&page={page}&token={token}"
for page in range(pages)
]
results: list = []
async def get_one(url):
@@ -190,8 +185,8 @@ class BenzingaWorldNewsFetcher(
@staticmethod
def transform_data(
query: BenzingaWorldNewsQueryParams,
data: List[Dict],
data: list[dict],
**kwargs: Any,
) -> List[BenzingaWorldNewsData]:
) -> list[BenzingaWorldNewsData]:
"""Transform the data."""
return [BenzingaWorldNewsData.model_validate(item) for item in data]

View File

@@ -1,7 +1,7 @@
"""Benzinga Helpers."""
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
from openbb_core.provider.utils.errors import UnauthorizedError
async def response_callback(response, _):
@@ -18,7 +18,4 @@ async def response_callback(response, _):
raise UnauthorizedError(f"Unauthorized Benzinga request -> {results[0]}")
raise OpenBBError(results[0])
if isinstance(results, list) and not results:
raise EmptyDataError("The request was returned empty.")
return results

View File

@@ -2,944 +2,74 @@ interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
accept:
- application/json
method: GET
uri: https://api.benzinga.com/api/v2/news?displayOutput=full&page=0&pageSize=20&sort=created%3Adesc&token=MOCK_TOKEN
uri: https://api.benzinga.com/api/v2/news?displayOutput=full&page=0&pageSize=2&sort=created%3Adesc&token=MOCK_TOKEN
response:
body:
string: "[{\"id\":34923089,\"author\":\"Henry Khederian\",\"created\":\"Tue,
26 Sep 2023 15:04:20 -0400\",\"updated\":\"Tue, 26 Sep 2023 15:04:20 -0400\",\"title\":\"What's
Going On With PDD Holdings Stock?\",\"teaser\":\"PDD Holdings Inc - ADR (NASDAQ:
PDD) shares are trading lower by 2.6% to $94.16 Tuesday afternoon.\",\"body\":\"<p><strong>PDD
Holdings Inc - ADR</strong> (NASDAQ:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/PDD#NASDAQ\\\">PDD</a>)
shares are trading lower by 2.6% to $94.16 Tuesday afternoon. Shares of several
Chinese stocks are trading lower this week amid concerns over the country&#39;s
property sector following reports suggesting <a href=\\\"https://www.benzinga.com/markets/penny-stocks/23/09/34869281/china-property-market-woes-spark-fears-of-2008-style-global-recession-led-by-china-slump\\\">China
Evergrande</a> said it&#39;s unable to issue new debt due to an ongoing investigation
into its Hengda Real Estate subsidiary.</p>\\r\\n\\r\\n<p><strong>Why It Matters</strong></p>\\r\\n\\r\\n<p>If
there are concerns about a potential economic slowdown in China due to issues
in the property sector, consumer spending may decrease. PDD Holdings relies
on consumer spending for its e-commerce platform, and a reduction in consumer
confidence and spending can negatively impact its revenue and growth prospects.</p>\\r\\n\\r\\n<p>A
downturn in the property sector could also lead to disruptions in supply chains
and logistics, which may affect Pinduoduo&#39;s ability to source and deliver
products efficiently. Supply chain disruptions can increase costs and lead
to operational challenges for the company.</p>\\r\\n\\r\\n<p><strong>What&#39;s
Going On?</strong></p>\\r\\n\\r\\n<p>Evergrande, the beleaguered Chinese property
developer, said Friday that it was cancelling a creditor meeting scheduled
early this week.&nbsp;It said that&nbsp;&ldquo;sales had not been as expected&rdquo;.</p>\\r\\n\\r\\n<p>The
announcement is the latest in a series of scandals to hit the Chinese financial
behemoth. Mid-September, the domestic regulator&nbsp;<a href=\\\"https://asia.nikkei.com/Business/Markets/China-debt-crunch/Evergrande-s-life-insurance-arm-taken-over-by-new-state-owned-vehicle\\\"
target=\\\"_blank\\\">approved</a>&nbsp;a 50% sale of its insurance business
China Evergrande Life into a special purpose vehicle owned by the Chinese
state to free up cash.</p>\\r\\n\\r\\n<p>Then last week,&nbsp;staff in the
company&rsquo;s wealth management division&nbsp;were detained by Shenzhen
police...<em><a href=\\\"https://www.benzinga.com/markets/penny-stocks/23/09/34869281/china-property-market-woes-spark-fears-of-2008-style-global-recession-led-by-china-slump\\\">Read
More</a></em></p>\\r\\n\\r\\n<p>According to data from&nbsp;<a href=\\\"https://benzinga.grsm.io/register174\\\">Benzinga
Pro</a>, PDD Holdings has a 52-week high of $106.38 and a 52-week low of $38.80.</p>\\r\\n\",\"url\":\"https://www.benzinga.com/news/23/09/34923089/whats-going-on-with-pdd-holdings-stock\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/analysis-4937349.jpg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/analysis-4937349.jpg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/analysis-4937349.jpg\"}],\"channels\":[{\"name\":\"News\"}],\"stocks\":[{\"name\":\"PDD\"}],\"tags\":[{\"name\":\"why
it's moving\"}]},{\"id\":34923603,\"author\":\"Benzinga Insights\",\"created\":\"Tue,
26 Sep 2023 15:01:36 -0400\",\"updated\":\"Tue, 26 Sep 2023 15:01:37 -0400\",\"title\":\"Mastercard
Unusual Options Activity\",\"teaser\":\" \",\"body\":\"<p>A whale with a lot
of money to spend has taken a noticeably bearish stance on <strong>Mastercard</strong>.</p>\\n<p>Looking
at options history for Mastercard (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/MA#NYSE\\\">MA</a>)
we detected 40 strange trades.</p>\\n<p>If we consider the specifics of each
trade, it is accurate to state that 45% of the investors opened trades with
bullish expectations and 55% with bearish.</p>\\n<p>From the overall spotted
trades, 30 are puts, for a total amount of $1,521,493 and 10, calls, for a
total amount of $1,215,296.</p>\\n<h3>What's The Price Target?</h3>\\n<p>Taking
into account the Volume and Open Interest on these contracts, it appears that
whales have been targeting a price range from $370.0 to $480.0 for Mastercard
over the last 3 months. </p>\\n<h3>Volume &amp; Open Interest Development</h3>\\n<p>In
terms of liquidity and interest, the mean open interest for Mastercard options
trades today is 556.93 with a total volume of 3,529.00.</p>\\n<p>In the following
chart, we are able to follow the development of volume and open interest of
call and put options for Mastercard's big money trades within a strike price
range of $370.0 to $480.0 over the last 30 days.</p>\\n<h3>Mastercard Option
Volume And Open Interest Over Last 30 Days</h3>\\n<p><img src=\\\"https://www.benzinga.com/files/images/story/2023/1695754893_0.png\\\"
alt=\\\"Options Call Chart\\\" /></p>\\n\\n<h3>Biggest Options Spotted:</h3>\\n<p><table>\\n<thead>\\n<tr>\\n<th><strong>Symbol</strong></th>\\n<th><strong>PUT/CALL</strong></th>\\n<th><strong>Trade
Type</strong></th>\\n<th><strong>Sentiment</strong></th>\\n<th><strong>Exp.
Date</strong></th>\\n<th><strong>Strike Price</strong></th>\\n<th><strong>Total
Trade Price</strong></th>\\n<th><strong>Open Interest</strong></th>\\n<th
align=\\\"right\\\"><strong>Volume</strong></th>\\n</tr>\\n</thead>\\n<tbody>\\n<tr>\\n<td>MA</td>\\n<td>CALL</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>01/17/25</td>\\n<td>$400.00</td>\\n<td>$547.5K</td>\\n<td>422</td>\\n<td
align=\\\"right\\\">102</td>\\n</tr>\\n<tr>\\n<td>MA</td>\\n<td>CALL</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>01/19/24</td>\\n<td>$410.00</td>\\n<td>$289.0K</td>\\n<td>3.8K</td>\\n<td
align=\\\"right\\\">226</td>\\n</tr>\\n<tr>\\n<td>MA</td>\\n<td>PUT</td>\\n<td>SWEEP</td>\\n<td>BULLISH</td>\\n<td>01/17/25</td>\\n<td>$480.00</td>\\n<td>$120.4K</td>\\n<td>11</td>\\n<td
align=\\\"right\\\">49</td>\\n</tr>\\n<tr>\\n<td>MA</td>\\n<td>PUT</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>01/17/25</td>\\n<td>$480.00</td>\\n<td>$103.4K</td>\\n<td>11</td>\\n<td
align=\\\"right\\\">85</td>\\n</tr>\\n<tr>\\n<td>MA</td>\\n<td>PUT</td>\\n<td>TRADE</td>\\n<td>BULLISH</td>\\n<td>01/17/25</td>\\n<td>$480.00</td>\\n<td>$95.0K</td>\\n<td>11</td>\\n<td
align=\\\"right\\\">24</td>\\n</tr>\\n</tbody>\\n</table></p>\\n<h3>Where
Is Mastercard Standing Right Now?</h3>\\n<ul>\\n<li>With a volume of 1,501,965,
the price of MA is down -1.67% at $395.76. </li>\\n<li>RSI indicators hint
that the underlying stock may be oversold.</li>\\n<li>Next earnings are expected
to be released in 30 days.</li>\\n</ul>\\n<h3>What The Experts Say On Mastercard:</h3>\\n<ul>\\n<li>Jefferies
has decided to maintain their Buy rating on Mastercard, which currently sits
at a price target of $475. </li>\\n<li>Truist Securities has decided to maintain
their Buy rating on Mastercard, which currently sits at a price target of
$475. </li>\\n<li>Mizuho downgraded its action to Buy with a price target
of $435</li>\\n</ul>\\n<p>Options are a riskier asset compared to just trading
the stock, but they have higher profit potential. Serious options traders
manage this risk by educating themselves daily, scaling in and out of trades,
following more than one indicator, and following the markets closely.</p>\\n<p>If
you want to stay updated on the latest options trades for Mastercard, <a href=\\\"https://benzinga.grsm.io/register174\\\">Benzinga
Pro</a> gives you real-time options trades alerts.</p>\",\"url\":\"https://www.benzinga.com/markets/options/23/09/34923603/mastercard-unusual-options-activity\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/movers_image_1.jpeg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/movers_image_1.jpeg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/movers_image_1.jpeg\"}],\"channels\":[{\"name\":\"Options\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"MA\"}],\"tags\":[{\"name\":\"BZI-UOA\"}]},{\"id\":34917839,\"author\":\"Adam
Eckert\",\"created\":\"Tue, 26 Sep 2023 15:01:06 -0400\",\"updated\":\"Tue,
26 Sep 2023 15:01:06 -0400\",\"title\":\"People's Net Worth Is Up Since COVID-19:
Here's Who Made The Most\",\"teaser\":\"A lot of people&#39;s net worth has
continued to climb since the COVID-19 pandemic,\_largely driven by stock market
gains and an increase in property values.\",\"body\":\"<p>A lot of people&#39;s
net worth has continued to climb since the COVID-19 pandemic,&nbsp;largely
driven by stock market gains and an increase in property values. <strong>That
may be why the rich <a href=\\\"https://www.benzinga.com/personal-finance/financial-advisors/23/07/33099045/the-richest-people-in-the-world-are-getting-richer-by-doing-this-you-can-do-i\\\">grew
their net worth the most</a>.</strong></p>\\r\\n\\r\\n<p><strong>What To Know:</strong>&nbsp;On
Tuesday, KPMG chief economist <strong>Diane Swonk</strong> shared a <a href=\\\"https://twitter.com/DianeSwonk/status/1706663509331718197\\\">chart</a>&nbsp;on
X&nbsp;showing&nbsp;household net worth grew across all incomes from the fourth
quarter of 2019 to the second quarter of this year.&nbsp;</p>\\r\\n\\r\\n<p>After
adjusting for inflation, the top 1% saw the highest&nbsp;net worth increase
of 22%. The net worth of those in the&nbsp;80th to 99th percentile jumped
by 12% to 13%, the 60th percentile increased by 9%, the 40th percentile increased
by 5% and earners in the 20th percentile saw their net worth climb by 9%.&nbsp;</p>\\r\\n\\r\\n<p><a
href=\\\"https://www.federalreserve.gov/releases/z1/\\\">Data</a> from the
Federal Reserve&nbsp;released earlier this month showed household wealth hit
record highs of $154.28 trillion last quarter, fueled by&nbsp;a stock market
rally and rebounding property values. That may help explain why those in the
top income percentiles are seeing the biggest increase in net worth.&nbsp;</p>\\r\\n\\r\\n<p>Households
have now fully regained the losses driven by declining real estate values
and&nbsp;a bear market in 2022 brought on by <a href=\\\"https://www.benzinga.com/economics/macro-economic-events/23/06/32861662/powell-hits-the-brakes-after-10-straight-interest-rate-increases-but-fed-signals-\\\">rising
interest rates</a> in response to high inflation.&nbsp;</p>\\r\\n\\r\\n<p><em>Related
Link:&nbsp;<a href=\\\"https://www.benzinga.com/federal-reserve/23/09/34761643/fed-meeting-preview-will-rates-hold-steady-despite-inflation-concerns\\\">Fed
Decision Looms: Will Steady Rates Prevail Despite Inflation Pressures?</a></em></p>\\r\\n\\r\\n<p>After
falling more than 18% in 2022, the<strong>&nbsp;SPDR S&amp;P 500 ETF Trust</strong>
(NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/SPY#NYSE\\\">SPY</a>)
was&nbsp;up more than 12% since the start of the year. But markets have trended
lower in recent months. The SPY is down more than 3.5% over the last month
and was down 1.18% at last check Tuesday, according to&nbsp;<a href=\\\"https://benzinga.grsm.io/gxjhpowx7zks\\\">Benzinga
Pro</a>.</p>\\r\\n\\r\\n<p>In 2020, markets tumbled, but quickly recovered
and finished the year up about 18%. In 2021, the SPY surged more than 28%.&nbsp;</p>\\r\\n\\r\\n<p><em>Photo:
Shutterstock</em></p>\\r\\n\",\"url\":\"https://www.benzinga.com/news/23/09/34917839/peoples-net-worth-is-up-since-covid-19-heres-who-made-the-most\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/money.man_.shutterstock_283343006.jpg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/money.man_.shutterstock_283343006.jpg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/money.man_.shutterstock_283343006.jpg\"}],\"channels\":[{\"name\":\"News\"},{\"name\":\"Economics\"},{\"name\":\"Personal
Finance\"},{\"name\":\"General\"}],\"stocks\":[{\"name\":\"SPY\"}],\"tags\":[{\"name\":\"Diane
Swonk\"},{\"name\":\"KPMG\"},{\"name\":\"net worth\"}]},{\"id\":34923584,\"author\":\"Benzinga
Insights\",\"created\":\"Tue, 26 Sep 2023 15:00:28 -0400\",\"updated\":\"Tue,
26 Sep 2023 15:00:29 -0400\",\"title\":\"Analyst Expectations for AVITA Medical's
Future\",\"teaser\":\" \",\"body\":\"<p>AVITA Medical (NASDAQ:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/RCEL#NASDAQ\\\">RCEL</a>) has observed
the following analyst ratings within the last quarter: </p>\\n<table>\\n<thead>\\n<tr>\\n<th></th>\\n<th
align=\\\"right\\\"><strong>Bullish</strong></th>\\n<th align=\\\"right\\\"><strong>Somewhat
Bullish</strong></th>\\n<th align=\\\"right\\\"><strong>Indifferent</strong></th>\\n<th
align=\\\"right\\\"><strong>Somewhat Bearish</strong></th>\\n<th align=\\\"right\\\"><strong>Bearish</strong></th>\\n</tr>\\n</thead>\\n<tbody>\\n<tr>\\n<td><strong>Total
Ratings</strong></td>\\n<td align=\\\"right\\\">2</td>\\n<td align=\\\"right\\\">3</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>Last
30D</strong></td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">1</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>1M
Ago</strong></td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>2M
Ago</strong></td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">2</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>3M
Ago</strong></td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n</tbody>\\n</table>\\n\\n<p>According
to 5 analyst offering 12-month price targets in the last 3 months, AVITA Medical
has an average price target of $28.4 with a high of $40.00 and a low of $23.00.
</p>\\n<p>Below is a summary of how these 5 analysts rated AVITA Medical over
the past 3 months. The greater the number of bullish ratings, the more positive
analysts are on the stock and the greater the number of bearish ratings, the
more negative analysts are on the stock</p>\\n<p><img src=\\\"https://www.benzinga.com/files/images/story/2023/1695754825_0.png\\\"
alt=\\\"price target chart\\\" /></p>\\n\\n<p>This average price target has
increased by 28.1% over the past month.</p>\\n<p><a href=\\\"https://www.benzinga.com/quote/RCEL/analyst-ratings?utm_source=Benzinga
Insights\\\"><strong>Stay up to date on AVITA Medical analyst ratings.</strong></a></p>\\n<p>If
you are interested in following small-cap stock news and performance <a href=\\\"https://www.benzinga.com/news/small-cap\\\">you
can start by tracking it here</a>.</p>\\n<h1>How Are Analyst Ratings Determined?</h1>\\n<p>Benzinga
tracks 150 analyst firms and reports on their stock expectations. Analysts
typically arrive at their conclusions by predicting how much money a company
will make in the future, usually the upcoming five years, and how risky or
predictable that company's revenue streams are.</p>\\n<p>Analysts attend company
conference calls and meetings, research company financial statements, and
communicate with insiders to publish their ratings on stocks. Analysts typically
rate each stock once per quarter or whenever the company has a major update.</p>\\n<p>Some
analysts will also offer forecasts for metrics like growth estimates, earnings,
and revenue to provide further guidance on stocks. Investors who use analyst
ratings should note that this specialized advice comes from humans and may
be subject to error.</p>\\n<p>This article was generated by Benzinga's automated
content engine and reviewed by an editor.</p>\",\"url\":\"https://www.benzinga.com/analyst-ratings/23/09/34923584/analyst-expectations-for-avita-medicals-future\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/analyst_ratings_image_3.png\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/analyst_ratings_image_3.png\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/analyst_ratings_image_3.png\"}],\"channels\":[{\"name\":\"Analyst
Ratings\"}],\"stocks\":[{\"name\":\"RCEL\"}],\"tags\":[{\"name\":\"BZI-AAR\"}]},{\"id\":34923583,\"author\":\"Benzinga
Insights\",\"created\":\"Tue, 26 Sep 2023 15:00:20 -0400\",\"updated\":\"Tue,
26 Sep 2023 15:00:21 -0400\",\"title\":\"Expert Ratings for O-I Glass\",\"teaser\":\"
\",\"body\":\"<p>Analysts have provided the following ratings for O-I Glass
(NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/OI#NYSE\\\">OI</a>)
within the last quarter: </p>\\n<table>\\n<thead>\\n<tr>\\n<th></th>\\n<th
align=\\\"right\\\"><strong>Bullish</strong></th>\\n<th align=\\\"right\\\"><strong>Somewhat
Bullish</strong></th>\\n<th align=\\\"right\\\"><strong>Indifferent</strong></th>\\n<th
align=\\\"right\\\"><strong>Somewhat Bearish</strong></th>\\n<th align=\\\"right\\\"><strong>Bearish</strong></th>\\n</tr>\\n</thead>\\n<tbody>\\n<tr>\\n<td><strong>Total
Ratings</strong></td>\\n<td align=\\\"right\\\">2</td>\\n<td align=\\\"right\\\">1</td>\\n<td
align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>Last
30D</strong></td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>1M
Ago</strong></td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>2M
Ago</strong></td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">1</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>3M
Ago</strong></td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n</tbody>\\n</table>\\n\\n<p>These
4 analysts have an average price target of $28.0 versus the current price
of O-I Glass at $17.025, implying upside.</p>\\n<p>Below is a summary of
how these 4 analysts rated O-I Glass over the past 3 months. The greater the
number of bullish ratings, the more positive analysts are on the stock and
the greater the number of bearish ratings, the more negative analysts are
on the stock</p>\\n<p><img src=\\\"https://www.benzinga.com/files/images/story/2023/1695754817_0.png\\\"
alt=\\\"price target chart\\\" /></p>\\n\\n<p>This current average has decreased
by 2.34% from the previous average price target of $28.67. </p>\\n<p><a href=\\\"https://www.benzinga.com/quote/OI/analyst-ratings?utm_source=Benzinga
Insights\\\"><strong>Stay up to date on O-I Glass analyst ratings.</strong></a></p>\\n<h1>What
Are Analyst Ratings?</h1>\\n<p>Analysts work in banking and financial systems
and typically specialize in reporting for stocks or defined sectors. Analysts
may attend company conference calls and meetings, research company financial
statements, and communicate with insiders to publish \\\"analyst ratings\\\"
for stocks. Analysts typically rate each stock once per quarter.</p>\\n<p>Some
analysts publish their predictions for metrics such as growth estimates, earnings,
and revenue to provide additional guidance with their ratings. When using
analyst ratings, it is important to keep in mind that stock and sector analysts
are also human and are only offering their opinions to investors.</p>\\n<p>If
you want to keep track of which analysts are outperforming others, you can
view updated analyst ratings along withanalyst success scores in <a href=\\\"https://benzinga.grsm.io/register174\\\">Benzinga
Pro</a>.</p>\\n<p>This article was generated by Benzinga's automated content
engine and reviewed by an editor.</p>\",\"url\":\"https://www.benzinga.com/analyst-ratings/23/09/34923583/expert-ratings-for-o-i-glass\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/analyst_ratings_image_0.jpeg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/analyst_ratings_image_0.jpeg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/analyst_ratings_image_0.jpeg\"}],\"channels\":[{\"name\":\"Analyst
Ratings\"}],\"stocks\":[{\"name\":\"OI\"}],\"tags\":[{\"name\":\"BZI-AAR\"}]},{\"id\":34916272,\"author\":\"Murtuza
Merchant\",\"created\":\"Tue, 26 Sep 2023 14:49:19 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:49:19 -0400\",\"title\":\"Chase's Crypto-Related Transactions
Ban In UK: A Sign Of The Times \u2014 Or A Sign Of Weakness?\",\"teaser\":\"JP
Morgan Chase &amp; Co (NYSE: JPM), the prominent banking institution, decided
to halt cryptocurrency-related transactions for its U.K. clientele starting\_Oct.
16, according to\_a recent communication to its users.\",\"body\":\"<p><strong>JP
Morgan Chase &amp; Co</strong> (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/JPM#NYSE\\\">JPM</a>),
the prominent banking institution, decided to halt cryptocurrency-related
transactions for its U.K. clientele starting&nbsp;Oct. 16, according to&nbsp;a
recent communication to its users.</p>\\r\\n\\r\\n<p>The bank stated, &quot;If
we think you&#39;re making a payment related to crypto assets, we&#39;ll decline
it,&quot; suggesting&nbsp;patrons keen on crypto investments might need to
explore other banking alternatives, Coindesk <a href=\\\"https://www.coindesk.com/policy/2023/09/26/chase-uk-to-block-crypto-payments-citing-fraud-scams/?utm_medium=social&amp;utm_source=onecryptofeed&amp;utm_campaign=headlines\\\">reported</a>.</p>\\r\\n\\r\\n<p>This
move by Chase comes ahead of the much-anticipated Benzinga&#39;s <a href=\\\"https://www.benzinga.com/events/digital-assets/?UTM_source=article\\\">Future
of Digital Assets</a> conference on Nov. 14, where such industry shifts are
likely to be discussed.</p>\\r\\n\\r\\n<p>Many U.K. banks have already been
traditionally wary of cryptocurrency transactions, often restricting or outright
blocking access for their customers.</p>\\r\\n\\r\\n<p>This cautious stance
has been so prevalent that the Financial Conduct Authority (FCA), the U.K.&#39;s
financial regulatory body,&nbsp;had to mediate dialogues between banks and
cryptocurrency entities due to the banking sector&#39;s hesitancy in serving
the crypto industry.</p>\\r\\n\\r\\n<p><em>Also Read:&nbsp;<a href=\\\"https://www.benzinga.com/markets/cryptocurrency/23/09/34905989/fhenix-secures-7m-funding-aims-to-bolster-privacy-on-public-blockchains\\\">Fhenix
Secures $7M Funding, Aims To Bolster Privacy On Public Blockchains</a></em></p>\\r\\n\\r\\n<p>Chase&#39;s
decision, as detailed in the communication reviewed by CoinDesk, stemmed&nbsp;from
concerns over rising fraudulent activities linked to cryptocurrencies.</p>\\r\\n\\r\\n<p>The
bank expressed that malefactors are increasingly exploiting digital assets
to defraud individuals of substantial amounts.</p>\\r\\n\\r\\n<p>Echoing the
sentiments of other U.K. banks that have implemented similar restrictions,
a Chase U.K. representative mentioned, &ldquo;We&rsquo;re committed to helping
keep our customers&rsquo; money safe and secure. We&rsquo;ve seen an increase
in the number of crypto scams targeting U.K. consumers, so we have taken the
decision to prevent the purchase of crypto assets on a Chase debit card or
by transferring money to a crypto site from a Chase account.&rdquo;</p>\\r\\n\\r\\n<p><em>Read
Next:&nbsp;<a href=\\\"https://www.benzinga.com/markets/cryptocurrency/23/09/34900667/taiwan-regulators-unveil-guiding-principles-to-regulate-digital-assets-crypto\\\">Taiwan
Regulators Unveil &#39;Guiding Principles&#39; To Regulate Digital Assets,
Crypto</a></em></p>\\r\\n\\r\\n<p><em>Meet and engage with transformative&nbsp;</em><em><a
href=\\\"https://www.benzinga.com/go/crypto-conference-article-link\\\" rel=\\\"noopener
noreferrer\\\" target=\\\"_blank\\\">Digital Asset and Crypto</a></em><em>&nbsp;business
leaders and investors at Benzinga&#39;s exclusive event:&nbsp;</em><em><a
href=\\\"https://www.benzinga.com/go/crypto-conference-article-link\\\" rel=\\\"noopener
noreferrer\\\" target=\\\"_blank\\\">Future of Digital Assets</a></em><em>.
Tickets are flying:&nbsp;</em><em><a href=\\\"https://www.benzinga.com/go/crypto-conference-article-link\\\"
rel=\\\"noopener noreferrer\\\" target=\\\"_blank\\\">Get yours</a></em><em>!&nbsp;</em></p>\\r\\n\\r\\n<p><em>Photo:
Shutterstock</em></p>\\r\\n\",\"url\":\"https://www.benzinga.com/markets/cryptocurrency/23/09/34916272/chases-crypto-related-transactions-ban-in-uk-a-sign-of-the-times-or-a-sign-of-weakness\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/banned.untitled_desigshutterstock..png\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/banned.untitled_desigshutterstock..png\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/banned.untitled_desigshutterstock..png\"}],\"channels\":[{\"name\":\"Cryptocurrency\"},{\"name\":\"News\"},{\"name\":\"Global\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"JPM\"}],\"tags\":[{\"name\":\"crypto
assets\"},{\"name\":\"Crypto industry\"},{\"name\":\"Crypto Scams\"},{\"name\":\"cryptocurrency
transactions\"},{\"name\":\"debit card\"},{\"name\":\"Financial Conduct Authority\"},{\"name\":\"U.K.\"},{\"name\":\"UK\"}]},{\"id\":34923076,\"author\":\"Benzinga
Insights\",\"created\":\"Tue, 26 Sep 2023 14:46:15 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:46:16 -0400\",\"title\":\"This Is What Whales Are Betting On
Home Depot\",\"teaser\":\" \",\"body\":\"<p>A whale with a lot of money to
spend has taken a noticeably bearish stance on <strong>Home Depot</strong>.</p>\\n<p>Looking
at options history for Home Depot (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/HD#NYSE\\\">HD</a>)
we detected 22 strange trades.</p>\\n<p>If we consider the specifics of each
trade, it is accurate to state that 36% of the investors opened trades with
bullish expectations and 63% with bearish.</p>\\n<p>From the overall spotted
trades, 11 are puts, for a total amount of $518,319 and 11, calls, for a total
amount of $545,972.</p>\\n<h3>What's The Price Target?</h3>\\n<p>Taking into
account the Volume and Open Interest on these contracts, it appears that whales
have been targeting a price range from $290.0 to $375.0 for Home Depot over
the last 3 months. </p>\\n<h3>Volume &amp; Open Interest Development</h3>\\n<p>In
terms of liquidity and interest, the mean open interest for Home Depot options
trades today is 959.57 with a total volume of 2,099.00.</p>\\n<p>In the following
chart, we are able to follow the development of volume and open interest of
call and put options for Home Depot's big money trades within a strike price
range of $290.0 to $375.0 over the last 30 days.</p>\\n<h3>Home Depot Option
Volume And Open Interest Over Last 30 Days</h3>\\n<p><img src=\\\"https://www.benzinga.com/files/images/story/2023/1695753971_0.png\\\"
alt=\\\"Options Call Chart\\\" /></p>\\n\\n<h3>Biggest Options Spotted:</h3>\\n<p><table>\\n<thead>\\n<tr>\\n<th><strong>Symbol</strong></th>\\n<th><strong>PUT/CALL</strong></th>\\n<th><strong>Trade
Type</strong></th>\\n<th><strong>Sentiment</strong></th>\\n<th><strong>Exp.
Date</strong></th>\\n<th><strong>Strike Price</strong></th>\\n<th><strong>Total
Trade Price</strong></th>\\n<th><strong>Open Interest</strong></th>\\n<th
align=\\\"right\\\"><strong>Volume</strong></th>\\n</tr>\\n</thead>\\n<tbody>\\n<tr>\\n<td>HD</td>\\n<td>CALL</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>10/20/23</td>\\n<td>$315.00</td>\\n<td>$110.5K</td>\\n<td>1.4K</td>\\n<td
align=\\\"right\\\">480</td>\\n</tr>\\n<tr>\\n<td>HD</td>\\n<td>PUT</td>\\n<td>SWEEP</td>\\n<td>BEARISH</td>\\n<td>01/19/24</td>\\n<td>$315.00</td>\\n<td>$105.0K</td>\\n<td>915</td>\\n<td
align=\\\"right\\\">53</td>\\n</tr>\\n<tr>\\n<td>HD</td>\\n<td>CALL</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>03/15/24</td>\\n<td>$320.00</td>\\n<td>$63.4K</td>\\n<td>245</td>\\n<td
align=\\\"right\\\">105</td>\\n</tr>\\n<tr>\\n<td>HD</td>\\n<td>CALL</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>03/15/24</td>\\n<td>$320.00</td>\\n<td>$63.3K</td>\\n<td>245</td>\\n<td
align=\\\"right\\\">155</td>\\n</tr>\\n<tr>\\n<td>HD</td>\\n<td>CALL</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>03/15/24</td>\\n<td>$320.00</td>\\n<td>$63.3K</td>\\n<td>245</td>\\n<td
align=\\\"right\\\">156</td>\\n</tr>\\n</tbody>\\n</table></p>\\n<h3>Where
Is Home Depot Standing Right Now?</h3>\\n<ul>\\n<li>With a volume of 1,456,296,
the price of HD is down -1.5% at $301.89. </li>\\n<li>RSI indicators hint
that the underlying stock may be oversold.</li>\\n<li>Next earnings are expected
to be released in 49 days.</li>\\n</ul>\\n<h3>What The Experts Say On Home
Depot:</h3>\\n<ul>\\n<li>Evercore ISI Group has decided to maintain their
Outperform rating on Home Depot, which currently sits at a price target of
$370. </li>\\n<li>Stifel downgraded its action to Hold with a price target
of $350</li>\\n<li>HSBC downgraded its action to Hold with a price target
of $365</li>\\n</ul>\\n<p>Options are a riskier asset compared to just trading
the stock, but they have higher profit potential. Serious options traders
manage this risk by educating themselves daily, scaling in and out of trades,
following more than one indicator, and following the markets closely.</p>\\n<p>If
you want to stay updated on the latest options trades for Home Depot, <a href=\\\"https://benzinga.grsm.io/register174\\\">Benzinga
Pro</a> gives you real-time options trades alerts.</p>\",\"url\":\"https://www.benzinga.com/markets/options/23/09/34923076/this-is-what-whales-are-betting-on-home-depot\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/movers_image_18.jpeg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/movers_image_18.jpeg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/movers_image_18.jpeg\"}],\"channels\":[{\"name\":\"Options\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"HD\"}],\"tags\":[{\"name\":\"BZI-UOA\"}]},{\"id\":34919396,\"author\":\"Shivani
Kumaresan\",\"created\":\"Tue, 26 Sep 2023 14:38:48 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:38:48 -0400\",\"title\":\"Holiday Hiring In A Pinch? Dick's
Sporting Goods Aims To Hire Less Seasonal Help Than Last Year\",\"teaser\":\"Dick&#39;s
Sporting Goods Inc\_(NYSE: DKS) disclosed its intentions to onboard 8,600
seasonal teammates across the nation in\_\",\"body\":\"<p><strong>Dick&#39;s
Sporting Goods Inc</strong>&nbsp;(NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/DKS#NYSE\\\">DKS</a>)
disclosed its intentions to onboard 8,600 seasonal teammates across the nation
in&nbsp;<a class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/pressreleases/23/09/n34904539/dicks-sporting-goods-plans-to-hire-8-600-seasonal-teammates-for-holiday-shopping-season\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\">anticipation of the upcoming holiday season.</a></p>\\r\\n\\r\\n<p>Last
year, the athletic-wear chain had marked a target of hiring&nbsp;<a class=\\\"editor-rtfLink\\\"
href=\\\"https://investors.dicks.com/news/news-details/2022/DICKS-Sporting-Goods-to-Begin-Holiday-Hiring-with-National-Signing-Day-on-September-28/default.aspx\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\">9,000</a>&nbsp;interim staff for the festive rush.</p>\\r\\n\\r\\n<p>The
reveal from Dick&#39;s follows similar conservative seasonal employment goals
recently unveiled by giants like&nbsp;<strong>Target Corporation</strong>&nbsp;(NYSE:<a
class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/TGT#NYSE\\\">TGT</a>)
and&nbsp;<strong>Macy&#39;s Inc</strong>&nbsp;(NYSE:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/M#NYSE\\\">M</a>), according to a
report from Reuters.</p>\\r\\n\\r\\n<p>The report quoted a study conducted
by Challenger, Gray &amp; Christmas that indicated a trend, noting retail
outlets in the U.S. are expected to hire the lowest number of seasonal workers
since 2008.</p>\\r\\n\\r\\n<p>Rising labor costs seem to be the primary reason
for this trend.</p>\\r\\n\\r\\n<p>The report also cited a recent analysis
by Deloitte, which highlights that the U.S. might witness the most sluggish
growth in holiday sales in half a decade due to escalating inflation rates,
leading households to tighten their budgetary belts.</p>\\r\\n\\r\\n<p>However,&nbsp;<strong>Amazon.Com,
Inc.</strong>&nbsp;(NASDAQ:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/AMZN#NASDAQ\\\">AMZN</a>)
is on a different tangent altogether, with plans to amplify its hires by a
whopping 67% compared to its figures from the last two years.</p>\\r\\n\\r\\n<p><strong><em>Also
Read</em></strong><em>:&nbsp;</em><a class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/news/23/09/34767824/amazons-holiday-hiring-spree-signals-strong-consumer-demand-expects-strong-q4-growth-bofa-analyst\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\"><em>Amazon&#39;s Holiday Hiring Spree Signals Strong
Consumer Demand, Expects Strong Q4 Growth: BofA Analyst</em></a></p>\\r\\n\\r\\n<p>The
report further noted that brands like Dick&#39;s Sporting Goods,&nbsp;<strong>Nike,
Inc.</strong>(NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/NKE#NYSE\\\">NKE</a>),
and&nbsp;<strong>Foot Locker, Inc.</strong>&nbsp;(NYSE:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/FL#NYSE\\\">FL</a>) have found themselves
grappling with diminished wholesale demand.</p>\\r\\n\\r\\n<p><strong>Price
Action</strong>: DKS shares are trading lower by 0.62% at $106.75 on the last
check Tuesday.</p>\\r\\n\\r\\n<p><em>This content was partially produced with
the help of AI tools and was reviewed and published by Benzinga editors.</em></p>\\r\\n
\",\"url\":\"https://www.benzinga.com/markets/equities/23/09/34919396/holiday-hiring-in-a-pinch-dicks-sporting-goods-aims-to-hire-less-seasonal-help-than-last-year\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/dks_1.png\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/dks_1.png\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/dks_1.png\"}],\"channels\":[{\"name\":\"Equities\"},{\"name\":\"News\"},{\"name\":\"Small
Cap\"},{\"name\":\"Markets\"},{\"name\":\"Media\"},{\"name\":\"General\"}],\"stocks\":[{\"name\":\"AMZN\"},{\"name\":\"DKS\"},{\"name\":\"FL\"},{\"name\":\"M\"},{\"name\":\"NKE\"},{\"name\":\"TGT\"}],\"tags\":[{\"name\":\"AI
Generated\"},{\"name\":\"Briefs\"},{\"name\":\"Consumer Discretionary\"},{\"name\":\"Department
Stores\"}]},{\"id\":34912218,\"author\":\"Lekha Gupta\",\"created\":\"Tue,
26 Sep 2023 14:34:53 -0400\",\"updated\":\"Tue, 26 Sep 2023 14:35:39 -0400\",\"title\":\"Blink
Charging And Parkopedia Collaborate, Adding 4,000 Locations To Platform\",\"teaser\":\"Blink
Charging Co\_(NASDAQ: BLNK) disclosed an agreement with Parkopedia to offer
increased accessibility\_\",\"body\":\"<p><strong>Blink Charging Co</strong>&nbsp;(NASDAQ:<a
class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/BLNK#NASDAQ\\\">BLNK</a>)
disclosed an agreement with Parkopedia to offer increased accessibility&nbsp;<a
class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/pressreleases/23/09/g34903735/blink-charging-teams-up-with-parkopedia-to-provide-ev-drivers-greater-accessibility-to-electric-ve\\\"
style=\\\"background-image:none; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\">to electric vehicle charging to drivers</a>.</p>\\r\\n\\r\\n<p>As
per the deal, BLNK will integrate more than 4,000 EV charging locations in
the Parkopedia platform in North America.&nbsp;</p>\\r\\n\\r\\n<p>This strategic
integration will grant the platform access to around 12,000 public EV chargers,
which include 129 DC fast chargers.</p>\\r\\n\\r\\n<p>&quot;Our collaboration
with Parkopedia creates a streamlined experience for EV drivers to conveniently
locate Blink EV charging stations across North America. The combination of
Parkopedia&#39;s excellence in serving drivers through convenience in parking
accessibility and Blink&#39;s extensive range of public EV chargers gives
EV drivers confidence in finding a reliable charging experience wherever they
are. This initiative builds upon our strategy of making it easy for EV drivers
to locate Blink chargers through a variety of platforms.&quot; said <strong>Jim
Nemec</strong>, chief revenue officer for Blink Charging.&nbsp;</p>\\r\\n\\r\\n<p>Last
month, BLNK reported a Q2 EPS loss of $0.44, which beat the analyst consensus
estimate of an EPS loss of $0.49, a decline of 7.32% Y/Y, and sales of $32.84
million, which&nbsp;<a class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/news/23/08/33645117/blink-charging-q2-earnings-strongest-quarter-in-history-raises-guidance\\\"
style=\\\"background-image:none; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\">beat the analyst of $23.76 million</a>.</p>\\r\\n\\r\\n<p>The
company raised its FY23 revenue target to $110 and $120 million, targeting
a positive adjusted <a href=\\\"https://www.benzinga.com/calendars/guidance\\\">EBITDA
run rate by December 2024</a>.</p>\\r\\n\\r\\n<p><em>Also Read:&nbsp;</em><a
class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/markets/equities/23/08/34027538/electrifying-partnership-blink-charging-becomes-the-ev-charging-supplier-for-tennessee-valley-au\\\"
style=\\\"background-image:none; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\"><em>Electrifying Partnership - Blink Charging Becomes
The EV Charging Supplier For Tennessee Valley Authority</em></a></p>\\r\\n\\r\\n<p><strong>Price
Action</strong>: BLNK shares were trading 0.85% higher at $2.98&nbsp;on the
last check Tuesday.&nbsp;</p>\\r\\n\\r\\n<p><em>Photo: Shutterstock</em></p>\\r\\n\",\"url\":\"https://www.benzinga.com/markets/equities/23/09/34912218/blink-charging-and-parkopedia-collaborate-adding-4-000-locations-to-platform\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/blink_shutter.jpg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/blink_shutter.jpg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/blink_shutter.jpg\"}],\"channels\":[{\"name\":\"Equities\"},{\"name\":\"News\"},{\"name\":\"Penny
Stocks\"},{\"name\":\"Contracts\"},{\"name\":\"Small Cap\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"BLNK\"}],\"tags\":[{\"name\":\"Briefs\"}]},{\"id\":34919603,\"author\":\"Lara
Goldstein\",\"created\":\"Tue, 26 Sep 2023 14:31:38 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:31:38 -0400\",\"title\":\"Depression Relief: Possible Therapy
Boost With DMT & SSRIs, Per Small Pharma Clinical Results\",\"teaser\":\"New
findings on safety, tolerability and efficacy from a clinical study assessing
the interaction of psychedelics and traditional pharma for the treatment of
major depressive disorder (MDD) are out.\",\"body\":\"<p>New findings on safety,
tolerability and efficacy from a clinical study assessing the interaction
of psychedelics and traditional pharma for the treatment of major depressive
disorder (MDD) are out.</p>\\r\\n\\r\\n<p>They correspond to U.K.&rsquo;s
short-acting psychedelic therapies developer <strong>Small Pharma</strong>&rsquo;s
(OTCQB:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/DMTTF#OTCQB\\\">DMTTF</a>)
open-label Phase 1b study assessing the joint work of its &ldquo;native&rdquo;
DMT compound, SPL026&nbsp;and selective serotonin reuptake inhibitors (SSRIs.)</p>\\r\\n\\r\\n<p>The
trial studied the safety, tolerability, pharmacokinetics and pharmacodynamics
(PK/PD) and exploratory efficacy of one 27.5 mg IV infusion of SPL026, both
alone and in combination with SSRIs in 17 patients paired with support therapy,
as follows:</p>\\r\\n\\r\\n<ul>\\r\\n\\t<li>\\r\\n\\t<p>The test cohort had
12 patients on a stable -and insufficient- SSRIs treatment course, while the
control cohort had 5 patients not using any pharmacological treatment for
their depression symptoms.&nbsp;Patients were recruited with moderate to severe
MDD.</p>\\r\\n\\t</li>\\r\\n\\t<li>\\r\\n\\t<p>SPL026 was well-tolerated by
all 17 patients, with no apparent differences between both cohorts and&nbsp;no
drug-related serious adverse events&nbsp;reported.</p>\\r\\n\\t</li>\\r\\n</ul>\\r\\n\\r\\n<ul>\\r\\n\\t<li>\\r\\n\\t<p>A
total of 11 mild or moderate drug-related adverse events (AEs) were reported
in both groups (8 in the SSRI cohort, 3 in the&nbsp;non-SSRI group) the&nbsp;majority
of them resolved during the dosing visit.</p>\\r\\n\\t</li>\\r\\n\\t<li>\\r\\n\\t<p>Results
reaffirmed SPL026&rsquo;s efficacy (shown in a prior <a href=\\\"https://www.benzinga.com/markets/cannabis/23/01/30573674/small-pharma-incs-psychedelics-for-major-depression-trial-results-and-q1-earnings\\\">Phase
2a study</a>) in the non-SSRI cohort, yet the treatment showed &ldquo;marked,&rdquo;
greater antidepressant effects in the SSRI cohort, suggesting a &ldquo;potentially
enhanced efficacy effect&rdquo; when administered jointly with SSRIs.</p>\\r\\n\\t</li>\\r\\n</ul>\\r\\n\\r\\n<p>While
the prior Phase 1/2a trial assessed the safety and efficacy profile of SPL026
paired with support therapy alone, the&nbsp;fact of not requesting MDD patients
to drop their SSRIs would expectedly enable broader patient recruitment for
future, large-scale SPL026 studies, accelerate its clinical development, and
facilitate patient access&nbsp;earlier within their therapeutic journey, if
approved.</p>\\r\\n\\r\\n<p>CMO<strong> Dr. Carol Routledge</strong> said
that while the team was &ldquo;very pleased&rdquo; to find patients may not
need to&nbsp;withdraw&nbsp;from their SSRIs, they were not expecting to see
&ldquo;such a marked difference in efficacy&rdquo; when administering SPL026
in combination with SSRIs compared to SPL026 alone.&nbsp;</p>\\r\\n\\r\\n<p>&ldquo;The
potentially enhanced efficacy effect of a DMT-based treatment when administered
with SSRIs could lead to greater therapeutic benefit for patients, and a compelling
argument for positioning it earlier in the treatment pathway. This was a small
study, but the findings are both interesting and encouraging and warrant further
exploration,&rdquo; Routledge explained.</p>\\r\\n\\r\\n<p><strong>CEO George
Tziras</strong> added that these safety and tolerability results &ldquo;further
support&rdquo; Small Pharma&rsquo;s DMT programs&rsquo; patient access strategy,
and further stated the team looks forward to exploring these findings &ldquo;as
part of the <a href=\\\"https://www.benzinga.com/markets/cannabis/23/08/34074153/cybins-acquisition-of-small-pharma-will-elevate-dmt-therapeutics-for-depression-and-anxiety\\\">integrated
DMT program with Cybin</a>&nbsp;(NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/CYBN#NYSE\\\">CYBN</a>),
subject to completion of the proposed arrangement transaction.&rdquo;</p>\\r\\n\\r\\n<p><em>Into
cannabis markets? Our biannual <a href=\\\"https://www.benzinga.com/go/cannabis-conference-article-link\\\">Cannabis
Capital Conference</a> is up this week! Whether to close your deals or just
get acquainted with the sector&rsquo;s best, you&rsquo;re still on time to
come join us this Sept. 27-28 in Chicago for our 17th edition! Tickets <a
href=\\\"https://www.benzinga.com/go/cannabis-conference-article-link\\\">HERE</a>.</em></p>\\r\\n\\r\\n<p><em>Photo:
Benzinga edit with photo by&nbsp;Bacsica, aiyoshi597, Gisele Yashar and Gorodenkoff
on Shutterstock.</em></p>\\r\\n\",\"url\":\"https://www.benzinga.com/markets/cannabis/23/09/34919603/depression-relief-possible-therapy-boost-with-dmt-ssris-per-small-pharma-clinical-results\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/advanced_technology_clinic_dmt_psilocybin_mdma_bacsica_aiyoshi597_gisele_yashar_and_gorodenkoff_on_shutterstock.jpg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/advanced_technology_clinic_dmt_psilocybin_mdma_bacsica_aiyoshi597_gisele_yashar_and_gorodenkoff_on_shutterstock.jpg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/advanced_technology_clinic_dmt_psilocybin_mdma_bacsica_aiyoshi597_gisele_yashar_and_gorodenkoff_on_shutterstock.jpg\"}],\"channels\":[{\"name\":\"Cannabis\"},{\"name\":\"News\"},{\"name\":\"Penny
Stocks\"},{\"name\":\"Psychedelics\"},{\"name\":\"Management\"},{\"name\":\"Global\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"CYBN\"},{\"name\":\"DMTTF\"}],\"tags\":[{\"name\":\"DMT
Clinical Trial\"},{\"name\":\"Drug Interactions\"},{\"name\":\"major depressive
disorder\"},{\"name\":\"Psychedelic-Assisted Therapies\"}]},{\"id\":34922721,\"author\":\"Benzinga
Insights\",\"created\":\"Tue, 26 Sep 2023 14:31:06 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:31:06 -0400\",\"title\":\"Looking At Visa's Recent Unusual
Options Activity\",\"teaser\":\" \",\"body\":\"<p>Someone with a lot of money
to spend has taken a bearish stance on <strong>Visa</strong> (NYSE:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/V#NYSE\\\">V</a>).</p>\\n<p>And retail
traders should know.</p>\\n<p>We noticed this today when the big position
showed up on publicly available options history that we track here at Benzinga.</p>\\n<p>Whether
this is an institution or just a wealthy individual, we don't know. But when
something this big happens with V, it often means somebody knows something
is about to happen.</p>\\n<p>So how do we know what this whale just did? </p>\\n<p>Today,
<a href=\\\"https://pro.benzinga.com/?utm_source=insights\\\">Benzinga</a>'s
options scanner spotted 11 uncommon options trades for Visa.</p>\\n<p>This
isn't normal.</p>\\n<p>The overall sentiment of these big-money traders is
split between 27% bullish and 72%, bearish.</p>\\n<p>Out of all of the special
options we uncovered, 4 are puts, for a total amount of $304,918, and 7 are
calls, for a total amount of $318,312.</p>\\n<h3>What's The Price Target?</h3>\\n<p>Taking
into account the Volume and Open Interest on these contracts, it appears that
whales have been targeting a price range from $130.0 to $260.0 for Visa over
the last 3 months. </p>\\n<h3>Volume &amp; Open Interest Development</h3>\\n<p>Looking
at the volume and open interest is an insightful way to conduct due diligence
on a stock.</p>\\n<p>This data can help you track the liquidity and interest
for Visa's options for a given strike price.</p>\\n<p>Below, we can observe
the evolution of the volume and open interest of calls and puts, respectively,
for all of Visa's whale activity within a strike price range from $130.0 to
$260.0 in the last 30 days. </p>\\n<h3>Visa Option Volume And Open Interest
Over Last 30 Days</h3>\\n<p><img src=\\\"https://www.benzinga.com/files/images/story/2023/1695753062_0.png\\\"
alt=\\\"Options Call Chart\\\" /></p>\\n\\n<h3>Biggest Options Spotted:</h3>\\n<p><table>\\n<thead>\\n<tr>\\n<th><strong>Symbol</strong></th>\\n<th><strong>PUT/CALL</strong></th>\\n<th><strong>Trade
Type</strong></th>\\n<th><strong>Sentiment</strong></th>\\n<th><strong>Exp.
Date</strong></th>\\n<th><strong>Strike Price</strong></th>\\n<th><strong>Total
Trade Price</strong></th>\\n<th><strong>Open Interest</strong></th>\\n<th
align=\\\"right\\\"><strong>Volume</strong></th>\\n</tr>\\n</thead>\\n<tbody>\\n<tr>\\n<td>V</td>\\n<td>PUT</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>10/13/23</td>\\n<td>$245.00</td>\\n<td>$101.5K</td>\\n<td>124</td>\\n<td
align=\\\"right\\\">72</td>\\n</tr>\\n<tr>\\n<td>V</td>\\n<td>PUT</td>\\n<td>TRADE</td>\\n<td>BULLISH</td>\\n<td>10/20/23</td>\\n<td>$225.00</td>\\n<td>$88.8K</td>\\n<td>1.3K</td>\\n<td
align=\\\"right\\\">478</td>\\n</tr>\\n<tr>\\n<td>V</td>\\n<td>PUT</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>11/17/23</td>\\n<td>$250.00</td>\\n<td>$73.7K</td>\\n<td>292</td>\\n<td
align=\\\"right\\\">36</td>\\n</tr>\\n<tr>\\n<td>V</td>\\n<td>CALL</td>\\n<td>SWEEP</td>\\n<td>BULLISH</td>\\n<td>10/20/23</td>\\n<td>$235.00</td>\\n<td>$70.0K</td>\\n<td>944</td>\\n<td
align=\\\"right\\\">358</td>\\n</tr>\\n<tr>\\n<td>V</td>\\n<td>CALL</td>\\n<td>SWEEP</td>\\n<td>BEARISH</td>\\n<td>06/21/24</td>\\n<td>$250.00</td>\\n<td>$56.6K</td>\\n<td>2.7K</td>\\n<td
align=\\\"right\\\">58</td>\\n</tr>\\n</tbody>\\n</table></p>\\n<h3>Where
Is Visa Standing Right Now?</h3>\\n<ul>\\n<li>With a volume of 3,317,808,
the price of V is down -1.44% at $229.99. </li>\\n<li>RSI indicators hint
that the underlying stock may be oversold.</li>\\n<li>Next earnings are expected
to be released in 28 days.</li>\\n</ul>\\n<h3>What The Experts Say On Visa:</h3>\\n<ul>\\n<li>Wedbush
downgraded its action to Outperform with a price target of $270</li>\\n<li>Mizuho
downgraded its action to Neutral with a price target of $255</li>\\n<li>Baird
downgraded its action to Outperform with a price target of $284</li>\\n</ul>\\n<p>Options
are a riskier asset compared to just trading the stock, but they have higher
profit potential. Serious options traders manage this risk by educating themselves
daily, scaling in and out of trades, following more than one indicator, and
following the markets closely.</p>\\n<p>If you want to stay updated on the
latest options trades for Visa, <a href=\\\"https://benzinga.grsm.io/register174\\\">Benzinga
Pro</a> gives you real-time options trades alerts.</p>\",\"url\":\"https://www.benzinga.com/markets/options/23/09/34922721/looking-at-visas-recent-unusual-options-activity\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/movers_image_18.jpeg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/movers_image_18.jpeg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/movers_image_18.jpeg\"}],\"channels\":[{\"name\":\"Options\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"V\"}],\"tags\":[{\"name\":\"BZI-UOA\"}]},{\"id\":34922718,\"author\":\"Benzinga
Insights\",\"created\":\"Tue, 26 Sep 2023 14:30:59 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:31:00 -0400\",\"title\":\"Check Out What Whales Are Doing With
Boeing\",\"teaser\":\" \",\"body\":\"<p>Someone with a lot of money to spend
has taken a bearish stance on <strong>Boeing</strong> (NYSE:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/BA#NYSE\\\">BA</a>).</p>\\n<p>And
retail traders should know.</p>\\n<p>We noticed this today when the big position
showed up on publicly available options history that we track here at Benzinga.</p>\\n<p>Whether
this is an institution or just a wealthy individual, we don't know. But when
something this big happens with BA, it often means somebody knows something
is about to happen.</p>\\n<p>So how do we know what this whale just did? </p>\\n<p>Today,
<a href=\\\"https://pro.benzinga.com/?utm_source=insights\\\">Benzinga</a>'s
options scanner spotted 22 uncommon options trades for Boeing.</p>\\n<p>This
isn't normal.</p>\\n<p>The overall sentiment of these big-money traders is
split between 31% bullish and 68%, bearish.</p>\\n<p>Out of all of the special
options we uncovered, 10 are puts, for a total amount of $970,061, and 12
are calls, for a total amount of $1,060,786.</p>\\n<h3>What's The Price Target?</h3>\\n<p>Taking
into account the Volume and Open Interest on these contracts, it appears that
whales have been targeting a price range from $150.0 to $280.0 for Boeing
over the last 3 months. </p>\\n<h3>Volume &amp; Open Interest Development</h3>\\n<p>Looking
at the volume and open interest is a powerful move while trading options.
This data can help you track the liquidity and interest for Boeing's options
for a given strike price. Below, we can observe the evolution of the volume
and open interest of calls and puts, respectively, for all of Boeing's whale
trades within a strike price range from $150.0 to $280.0 in the last 30 days.
</p>\\n<h3>Boeing Option Volume And Open Interest Over Last 30 Days</h3>\\n<p><img
src=\\\"https://www.benzinga.com/files/images/story/2023/1695753055_0.png\\\"
alt=\\\"Options Call Chart\\\" /></p>\\n\\n<h3>Biggest Options Spotted:</h3>\\n<p><table>\\n<thead>\\n<tr>\\n<th><strong>Symbol</strong></th>\\n<th><strong>PUT/CALL</strong></th>\\n<th><strong>Trade
Type</strong></th>\\n<th><strong>Sentiment</strong></th>\\n<th><strong>Exp.
Date</strong></th>\\n<th><strong>Strike Price</strong></th>\\n<th><strong>Total
Trade Price</strong></th>\\n<th><strong>Open Interest</strong></th>\\n<th
align=\\\"right\\\"><strong>Volume</strong></th>\\n</tr>\\n</thead>\\n<tbody>\\n<tr>\\n<td>BA</td>\\n<td>CALL</td>\\n<td>TRADE</td>\\n<td>BEARISH</td>\\n<td>11/17/23</td>\\n<td>$200.00</td>\\n<td>$292.3K</td>\\n<td>1.3K</td>\\n<td
align=\\\"right\\\">335</td>\\n</tr>\\n<tr>\\n<td>BA</td>\\n<td>PUT</td>\\n<td>SWEEP</td>\\n<td>BEARISH</td>\\n<td>06/21/24</td>\\n<td>$170.00</td>\\n<td>$227.5K</td>\\n<td>701</td>\\n<td
align=\\\"right\\\">250</td>\\n</tr>\\n<tr>\\n<td>BA</td>\\n<td>PUT</td>\\n<td>SWEEP</td>\\n<td>BEARISH</td>\\n<td>06/21/24</td>\\n<td>$170.00</td>\\n<td>$225.0K</td>\\n<td>701</td>\\n<td
align=\\\"right\\\">250</td>\\n</tr>\\n<tr>\\n<td>BA</td>\\n<td>CALL</td>\\n<td>TRADE</td>\\n<td>BULLISH</td>\\n<td>01/17/25</td>\\n<td>$170.00</td>\\n<td>$209.6K</td>\\n<td>321</td>\\n<td
align=\\\"right\\\">40</td>\\n</tr>\\n<tr>\\n<td>BA</td>\\n<td>CALL</td>\\n<td>SWEEP</td>\\n<td>BEARISH</td>\\n<td>11/17/23</td>\\n<td>$200.00</td>\\n<td>$147.0K</td>\\n<td>1.3K</td>\\n<td
align=\\\"right\\\">494</td>\\n</tr>\\n</tbody>\\n</table></p>\\n<h3>Where
Is Boeing Standing Right Now?</h3>\\n<ul>\\n<li>With a volume of 1,974,811,
the price of BA is down -1.43% at $195.94. </li>\\n<li>RSI indicators hint
that the underlying stock may be oversold.</li>\\n<li>Next earnings are expected
to be released in 29 days.</li>\\n</ul>\\n<h3>What The Experts Say On Boeing:</h3>\\n<ul>\\n<li>Citigroup
has decided to maintain their Buy rating on Boeing, which currently sits at
a price target of $285. </li>\\n<li>Susquehanna downgraded its action to Positive
with a price target of $270</li>\\n<li>Susquehanna downgraded its action to
Positive with a price target of $270</li>\\n<li>TD Cowen has decided to maintain
their Outperform rating on Boeing, which currently sits at a price target
of $260. </li>\\n</ul>\\n<p>Options are a riskier asset compared to just trading
the stock, but they have higher profit potential. Serious options traders
manage this risk by educating themselves daily, scaling in and out of trades,
following more than one indicator, and following the markets closely.</p>\\n<p>If
you want to stay updated on the latest options trades for Boeing, <a href=\\\"https://benzinga.grsm.io/register174\\\">Benzinga
Pro</a> gives you real-time options trades alerts.</p>\",\"url\":\"https://www.benzinga.com/markets/options/23/09/34922718/check-out-what-whales-are-doing-with-boeing\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/movers_image_1.jpeg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/movers_image_1.jpeg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/movers_image_1.jpeg\"}],\"channels\":[{\"name\":\"Options\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"BA\"}],\"tags\":[{\"name\":\"BZI-UOA\"}]},{\"id\":34910361,\"author\":\"Nabaparna
Bhattacharya\",\"created\":\"Tue, 26 Sep 2023 14:23:33 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:24:23 -0400\",\"title\":\"Qantas Airways Chairman Under Fire
Over Morale, Union Demands Resignation\",\"teaser\":\"Qantas Airways Ltd.&#39;s\_(OTC:
QABSY) Chairman Richard Goyder\_is facing calls for his resignation from a
pilots union over\_downbeat company morale.\",\"body\":\"<p><strong>Qantas
Airways Ltd.&#39;s&nbsp;</strong>(OTC:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/QABSY#OTC\\\">QABSY</a>)
Chairman <strong>Richard Goyder</strong>&nbsp;is facing calls for his resignation
from a pilots union over&nbsp;downbeat company morale.</p>\\r\\n\\r\\n<p>The
Australian and International Pilots Association (AIPA), which represents most
of Qantas&#39;&nbsp;roughly 3,000 pilots, wrote to the&nbsp;<a class=\\\"editor-rtfLink\\\"
href=\\\"https://www.benzinga.com/news/23/05/32150992/qantas-airways-names-its-first-female-ceo\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\">airline&#39;s new CEO, Vanessa Hudson</a>, demanding
that Goyder resign, according to a Reuters report.</p>\\r\\n\\r\\n<p>&quot;We
have totally lost confidence in Goyder and his board,&quot; AIPA President
Captain <strong>Tony Lucas</strong> said in a statement to Reuters.</p>\\r\\n\\r\\n<p>The
company was recently in the headlines after reports emerged that former CEO
<strong>Alan Joyce</strong> saw a significant increase in his pay, rising
by 872% in FY23, as he cashed in long-term incentives upon retiring. In the
previous year, his total pay was A$2.3 million.</p>\\r\\n\\r\\n<p><strong><em>Related:</em></strong><em>&nbsp;</em><a
class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/markets/equities/23/09/34757006/qantas-ex-ceo-takes-900-salary-hike-but-faces-bonus-reduction-due-to-scandal-report\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\"><em>Qantas&#39; Ex-CEO Takes 900% Salary Hike But Faces
Bonus Reduction Due To Scandal: Report</em></a></p>\\r\\n\\r\\n<p>Goyder,
who has served as the airline&#39;s chairman since 2018, will not stand for
re-election at the upcoming annual meeting scheduled for November, the report
added.</p>\\r\\n\\r\\n<p>On Monday, the company reportedly said it wants to
allocate additional funds for addressing &quot;customer pain points&quot;
beyond its initial plans, according to Reuters. However, it cautioned that
the escalating fuel costs might necessitate fare increases, even as prices
are already high, the report added.</p>\\r\\n\\r\\n<p><strong>Price Action:</strong>&nbsp;Shares
of QABSY were down 1.22% to $16.40 on Tuesday.&nbsp;</p>\\r\\n\\r\\n<p><em>Also
Read:&nbsp;</em><a class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/markets/equities/23/06/32711095/qantas-sees-airfares-moderating-as-capacity-returns-to-pre-covid-levels-report\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\"><em>Qantas Sees Airfares Moderating As Capacity Returns
To Pre-COVID Levels: Report</em></a></p>\\r\\n\\r\\n<p><em>Photo: Shutterstock</em></p>\\r\\n\",\"url\":\"https://www.benzinga.com/markets/equities/23/09/34910361/qantas-airways-chairman-under-fire-over-morale-union-demands-resignation\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/qantas_shutter.jpg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/qantas_shutter.jpg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/qantas_shutter.jpg\"}],\"channels\":[{\"name\":\"Equities\"},{\"name\":\"News\"},{\"name\":\"Markets\"},{\"name\":\"General\"}],\"stocks\":[{\"name\":\"QABSY\"}],\"tags\":[{\"name\":\"Briefs\"}]},{\"id\":34919573,\"author\":\"Nicol\xE1s
Jose Rodriguez\",\"created\":\"Tue, 26 Sep 2023 14:18:47 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:18:48 -0400\",\"title\":\"Legal Battles In Cannabis: UFCW Wins
In Massachusetts, Alleged Misconduct In Arkansas, SEC Suit\",\"teaser\":\"Labor
Judge Orders Massachusetts Dispensary To Negotiate With Union Despite Election
Loss\\n\\nA federal judge ruled in favor of the United Food and Commercial
Workers Union (UFCW) in Massachusetts, ordering cannabis retailer I.N.S.A.
Inc. to negotiate with the union despite the UFCW losing an election last
year.\",\"body\":\"<h3>Labor Judge Orders Massachusetts Dispensary To Negotiate
With Union Despite Election Loss</h3>\\r\\n\\r\\n<p>A federal judge ruled
in favor of the United Food and Commercial Workers Union (UFCW) in Massachusetts,
ordering cannabis retailer I.N.S.A. Inc. to negotiate with the union despite
the <a href=\\\"https://www.benzinga.com/topic/ufcw\\\">UFCW</a> losing an
election last year.</p>\\r\\n\\r\\n<p>The judge cites the illegal firing of
pro-union workers as the reason for the decision, applying a new pro-union
standard from the National Labor Relations Board (NLRB).</p>\\r\\n\\r\\n<p>This
precedent allows unions to represent workers despite election losses in cases
of employer unfair labor practices. The ruling may face appeals, potentially
reaching the U.S. Supreme Court, <a href=\\\"https://www.greenmarketreport.com/labor-judge-rules-dispensary-must-bargain-with-union-despite-election-loss/\\\">reported</a>
John Schroyer for Green Market Report.</p>\\r\\n\\r\\n<p><em>Join us at the
17th <a href=\\\"http://\\\">Benzinga Cannabis Capital Conference</a> in Chicago
on September 27-28, where you can explore the latest in cannabis regulations,
compliance, and investment opportunities &ndash; don&#39;t miss your chance,
<a href=\\\"http://\\\">secure your spot today</a>!</em></p>\\r\\n\\r\\n<h3>Lawsuit
Accuses Arkansas Law Firm of Medical Marijuana Misconduct</h3>\\r\\n\\r\\n<p>The
lawsuit alleges misconduct by <a href=\\\"https://www.benzinga.com/topic/arkansas-cannabis\\\">Arkansas</a>
law firm Steel, Wright, Gray in securing medical marijuana dispensary licenses
in 2019. Local owners claim they had little control over their dispensaries,
with out-of-state companies reaping profits, <a href=\\\"http://https://arkansasadvocate.com/2023/09/21/lawsuit-accuses-arkansas-law-firm-of-misconduct-in-early-days-of-medical-marijuana/\\\">reported</a>
local media.</p>\\r\\n\\r\\n<h3>SEC Sues Former Tree of Knowledge CEO Over
Fraudulent PPE Pivot</h3>\\r\\n\\r\\n<p>The SEC filed a lawsuit against Michael
Caridi, former CEO of Tree of Knowledge (OTC:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/TOKI#OTC\\\">TOKI</a>)
(OTCEM: TOKIF), over fraudulent activities related to the personal protective
equipment (PPE) supply chain during the COVID-19 pandemic.</p>\\r\\n\\r\\n<p>Caridi&#39;s
company reached a settlement after defrauding a Canadian hospital of $13 million
for N95 masks never delivered, Bloomberg Law <a href=\\\"https://news.bloomberglaw.com/litigation/cbd-sellers-pivot-to-n-95-masks-draws-sec-suit-against-ex-ceo\\\">reported</a>.</p>\\r\\n\\r\\n<p>According
to the lawsuit, Caridi diverted funds to himself and associates, leading to
massive liability for TOK Entities&nbsp;and issued false press releases that
caused stock price spikes.</p>\\r\\n\\r\\n<p>The SEC seeks a ban from penny
stock trading, fines, and the repayment of ill-gotten gains from the hospital
deal.</p>\\r\\n\\r\\n<p><em>The&nbsp;<a href=\\\"\\\">Benzinga Cannabis Capital
Conference</a>&nbsp;is happening this week in&nbsp;Chicago September 27-28.&nbsp;<a
href=\\\"\\\">Get your tickets today</a>&nbsp;before prices increase and secure
a spot at the epicenter of cannabis investment and branding.</em></p>\\r\\n\\r\\n<p><em>Photo
by&nbsp;<a href=\\\"https://unsplash.com/@jaimespaniol?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\\\">Jaime
Spaniol</a>&nbsp;on&nbsp;<a href=\\\"https://unsplash.com/photos/-L0N74GWsq8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\\\">Unsplash</a>.&nbsp;</em></p>\\r\\n\",\"url\":\"https://www.benzinga.com/markets/cannabis/23/09/34919573/legal-battles-in-cannabis-ufcw-wins-in-massachusetts-alleged-misconduct-in-arkansas-sec-suit\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/jaime-spaniol--l0n74gwsq8-unsplash_1.jpg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/jaime-spaniol--l0n74gwsq8-unsplash_1.jpg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/jaime-spaniol--l0n74gwsq8-unsplash_1.jpg\"}],\"channels\":[{\"name\":\"Cannabis\"},{\"name\":\"Government\"},{\"name\":\"News\"},{\"name\":\"Penny
Stocks\"},{\"name\":\"Regulations\"},{\"name\":\"Legal\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"TOKI\"}],\"tags\":[{\"name\":\"Arkansas
cannabis\"},{\"name\":\"Cannabis Unions\"},{\"name\":\"I.N.S.A. Inc.\"},{\"name\":\"Massachussetts
Cannabis\"},{\"name\":\"National Labor Relations Board\"},{\"name\":\"S.E.C.
securities exchange commission\"},{\"name\":\"Steel Wright And Gray\"},{\"name\":\"Tree
of Knowledge\"},{\"name\":\"U.S. Securities and Exchange Commission\"},{\"name\":\"UFCW\"}]},{\"id\":34917805,\"author\":\"Anusuya
Lahiri\",\"created\":\"Tue, 26 Sep 2023 14:14:00 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:14:53 -0400\",\"title\":\"FTC And States Take On Amazon In
Antitrust Battle: What's Going On? (UPDATE)\",\"teaser\":\"Editor&#39;s note:
This story has been updated to reflect the nature of the lawsuit against Amazon
and its intent.\_\",\"body\":\"<p><em><strong>Editor&#39;s note:</strong>
This story has been updated to reflect the nature of the lawsuit against Amazon
and its intent.&nbsp;</em></p>\\r\\n\\r\\n<p><strong>Amazon.com, Inc</strong>&nbsp;(NYSE:<a
class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/AMZN#NYSE\\\">AMZN</a>)
stock is trading lower Tuesday as the Federal Trade Commission and 17 states
filed suit against the company,&nbsp;<a class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/government/23/09/34265296/whats-going-on-with-amazon-com-stock-today\\\"
style=\\\"color:#4a6ee0; background:transparent; margin-top:0pt; margin-bottom:0pt\\\"
target=\\\"_blank\\\">accusing it of exploiting</a>&nbsp;monopolistic&nbsp;power
against rivals.&nbsp;</p>\\r\\n\\r\\n<p>Amazon&#39;s tactics prevent rivals
from growing and emerging by stifling competition on price, product selection
and quality, according to the complaint filed in Seattle federal court.&nbsp;</p>\\r\\n\\r\\n<p>The
complaint alleges that Amazon&#39;s schemes affect billions of dollars in
retail sales, impacting businesses and&nbsp;<a class=\\\"editor-rtfLink\\\"
href=\\\"https://www.ftc.gov/news-events/news/press-releases/2023/09/ftc-sues-amazon-illegally-maintaining-monopoly-power\\\"
style=\\\"color:#4a6ee0; background:transparent; margin-top:0pt; margin-bottom:0pt\\\"
target=\\\"_blank\\\">consumers</a>.</p>\\r\\n\\r\\n<p>FTC Chair <strong>Lina
M. Khan</strong> states that Amazon exploits its monopoly power, enriching
itself at the expense of American families and businesses.&nbsp;</p>\\r\\n\\r\\n<p>The
lawsuit aims to hold Amazon accountable and restore fair competition, according
to the FTC.&nbsp;</p>\\r\\n\\r\\n<p><strong>John Newman</strong>, deputy director
of the FTC&#39;s Bureau of Competition, said Amazon&#39;s monopolistic behavior
results in higher consumer prices and high fees for online sellers.&nbsp;</p>\\r\\n\\r\\n<p>The
complaint focuses on what the FTC said is Amazon&#39;s anticompetitive conduct
in the online superstore and marketplace services markets.</p>\\r\\n\\r\\n<p>The
suit claims that Amazon employs tactics like anti-discounting measures and
conditioning sellers&#39; &quot;Prime&quot; eligibility on costly fulfillment
services, limiting competitors&#39; ability to compete effectively.</p>\\r\\n\\r\\n<p>Amazon&#39;s
exclusionary practices, including replacing organic search results with paid
ads and biasing search results toward its products, make it impossible for
competitors to gain traction, according to the lawsuit.&nbsp;</p>\\r\\n\\r\\n<p>The
company charges substantial fees, forcing sellers to pay close to 50% of their
revenues to Amazon, leading to increased consumer prices, the lawsuit said.&nbsp;</p>\\r\\n\\r\\n<p>The
FTC and state partners seek a permanent injunction to stop what they say is
Amazon&#39;s unlawful conduct and restore competition in the market.</p>\\r\\n\\r\\n<p>Between
being paid for its logistics program, advertising, and other services, &quot;Amazon
now takes one of every $2 that a seller makes,&quot; Khan said, <a href=\\\"http://www.wsj.com/us-news/law/ftc-sues-amazon-alleging-illegal-online-marketplace-monopoly-6bd9af23\\\">according</a>
to the Wall Street Journal.&nbsp;</p>\\r\\n\\r\\n<p><strong>David Zapolsky</strong>,
Amazon&#39;s Senior Vice President of Global Public Policy and General Counsel,
said the FTC is straying from its consumer and competition protection mission.&nbsp;</p>\\r\\n\\r\\n<p>He
argued that the practices challenged by the FTC have encouraged competition
and innovation in the retail industry.&nbsp;</p>\\r\\n\\r\\n<p>Zapolsky warned
that if the FTC prevailed, it could result in fewer product choices, higher
prices, slower deliveries&nbsp;and limited options for small&nbsp;<a class=\\\"editor-rtfLink\\\"
href=\\\"https://www.aboutamazon.com/news/company-news/amazon-response-to-ftc-antitrust-lawsuit\\\"
style=\\\"color:#4a6ee0; background:transparent; margin-top:0pt; margin-bottom:0pt\\\"
target=\\\"_blank\\\">businesses</a>.</p>\\r\\n\\r\\n<p>According to recent
media reports, the case will focus on several aspects of the Amazon marketplace
and may suggest structural remedies that could result in a company breakup.&nbsp;</p>\\r\\n\\r\\n<p>However,&nbsp;<strong>Wedbush&nbsp;</strong>found
the possibility of a&nbsp;<a class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/news/23/09/34620099/will-ftc-lawsuit-end-up-breaking-amazon-analyst-says-unlikely\\\"
style=\\\"color:#4a6ee0; background:transparent; margin-top:0pt; margin-bottom:0pt\\\"
target=\\\"_blank\\\">split as unlikely</a>. The FTC faces a challenging road
ahead, given the benefits Amazon brings to consumer welfare through competitive
pricing, selection, and industry-leading delivery services, according to the
analyst firm.&nbsp;</p>\\r\\n\\r\\n<p><strong>Price Action:</strong>&nbsp;AMZN
shares traded lower by 2.99% at $127.35 on the last check Tuesday.</p>\\r\\n\\r\\n<p><strong><em>Disclaimer:</em></strong><em>&nbsp;This
content was partially produced with the help of AI tools and was reviewed
and published by Benzinga editors.</em></p>\\r\\n\",\"url\":\"https://www.benzinga.com/news/23/09/34917805/ftc-and-states-take-on-amazon-in-antitrust-battle-1\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/amazon_prime_rewards_card_32861518627.jpg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/amazon_prime_rewards_card_32861518627.jpg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/amazon_prime_rewards_card_32861518627.jpg\"}],\"channels\":[{\"name\":\"News\"},{\"name\":\"Tech\"},{\"name\":\"Media\"}],\"stocks\":[{\"name\":\"AMZN\"}],\"tags\":[{\"name\":\"AI
Generated\"},{\"name\":\"Briefs\"}]},{\"id\":34920989,\"author\":\"Joana Scopel\",\"created\":\"Tue,
26 Sep 2023 14:11:12 -0400\",\"updated\":\"Tue, 26 Sep 2023 14:11:12 -0400\",\"title\":\"Urban-Gro
Secures $11M Hospitality & Recreation Contract In Southeast U.S.\",\"teaser\":\"Urban-Gro,
Inc. (NASDAQ: UGRO), a comprehensive professional services consulting firm\_announced
its latest contract win within the hospitality and recreation sector in the
Southeastern U.S.\",\"body\":\"<p><strong>Urban-Gro, Inc.</strong> (NASDAQ:<a
class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/UGRO#NASDAQ\\\">UGRO</a>),
a comprehensive professional services consulting firm&nbsp;announced its latest
contract win within the hospitality and recreation sector in the Southeastern
U.S.</p>\\r\\n\\r\\n<p>The contract, valued at over <strong>$11 million</strong>,
encompasses a range of services, including architecture, design, engineering&nbsp;and
construction management.</p>\\r\\n\\r\\n<p>&ldquo;Our diversification playbook
continues to drive strong growth,&rdquo; said chairman and CEO of Urban-Gro
<strong>Bradley Nattrass</strong>, who will be participating at the <a href=\\\"https://www.benzinga.com/go/cannabis-conference-article-link\\\"
target=\\\"_blank\\\"><strong>Benzinga Cannabis Capital Conference</strong></a>
in <strong>Chicago</strong> on a panel on the topic of &#39;Beyond Buds: How
and Why Ancillary Companies are Diversifying&#39;. Mark your calendar for
<strong>September 27-28</strong></p>\\r\\n\\r\\n<p><em>See Also:</em> <em><a
href=\\\"https://www.benzinga.com/markets/cannabis/23/09/34537387/ancillary-cannabis-business-ramps-up-market-presence-with-3m-in-marijuana-contracts\\\">Ancillary
Cannabis Business Ramps Up Market Presence With $3M In Marijuana Contracts</a></em></p>\\r\\n\\r\\n<p>Moreover,
the project &ldquo;will be predominantly realized over the next six quarters,&rdquo;
<a href=\\\"https://www.globenewswire.com/news-release/2023/09/26/2749481/0/en/urban-gro-Inc-Awarded-Contract-for-More-Than-11-Million-of-Design-Build-Services-with-Existing-Client-in-the-Hospitality-Recreation-Sector.html\\\">per</a>
a press release.</p>\\r\\n\\r\\n<p>&quot;Having successfully executed and
delivered on two smaller contracts with the client earlier this year, to now
earning the responsibility of delivering complete turnkey design-build services,
further demonstrates that our strategy is working as intended,&rdquo; concluded
Nattrass who will also <a href=\\\"https://www.benzinga.com/pressreleases/23/09/g34861776/urban-gro-inc-announces-upcoming-fall-2023-conference-participation\\\">be
available</a> for one-on-one investor meetings at the <a href=\\\"http://www.benzinga.com/go/cannabis-conference-article-link\\\">Benzinga
Conference</a>.</p>\\r\\n\\r\\n<p><img alt=\\\"cccchicago_0.jpg\\\" src=\\\"https://www.benzinga.com/files/u1084344/cccchicago_0.jpg\\\"
style=\\\"height:359px; width:683px\\\" /></p>\\r\\n\\r\\n<p><a href=\\\"https://www.benzinga.com/events/cannabis-conference/company/urban-gro/\\\">UGRO</a>
is a fully integrated architectural, engineering&nbsp;and cultivation systems
company for commercial <a href=\\\"https://www.benzinga.com/cannabis\\\">cannabis</a>
and food-focused indoor Controlled Environment Agriculture (CEA) facilities.</p>\\r\\n\\r\\n<h3>Price
Action</h3>\\r\\n\\r\\n<p>UGRO shares <a href=\\\"https://www.benzinga.com/quote/UGRO/analyst-ratings\\\">traded</a>
6.54% up at $1.385 per share at the time of writing on Tuesday afternoon.</p>\\r\\n\\r\\n<p>Interested
in learning about&nbsp;<a href=\\\"https://www.benzinga.com/money/marijuana-stocks\\\"
target=\\\"_blank\\\">how to invest in cannabis stocks</a>, consider joining
us at the 17th edition of the&nbsp;<a href=\\\"https://www.benzinga.com/go/cannabis-conference-article-link\\\"
target=\\\"_blank\\\"><strong>Benzinga Cannabis Capital Conference</strong></a>&nbsp;in&nbsp;<strong>Chicago
on Sept 27-28</strong>.&nbsp;<a href=\\\"http://www.benzinga.com/go/cannabis-conference-article-link\\\"
target=\\\"_blank\\\">Get your tickets today</a>&nbsp;before prices increase
and secure a spot at the epicenter of cannabis investment and branding.</p>\\r\\n\\r\\n<p><em>Read
Next:</em> <em><a href=\\\"https://www.benzinga.com/markets/cannabis/23/08/34149329/cashing-in-on-cannabis-urban-gro-secures-over-2m-in-deal-with-xs-financial-whats-next\\\">Cashing
In On Cannabis: Urban-gro Secures Over $2M In Deal With XS Financial, What&#39;s
Next?</a></em></p>\\r\\n\\r\\n<p><em>Image by <a href=\\\"https://www.benzinga.com/author/el-planteo\\\">El
Planteo</a></em></p>\\r\\n\",\"url\":\"https://www.benzinga.com/markets/cannabis/23/09/34920989/urban-gro-secures-11m-hospitality-recreation-contract-in-southeast-u-s\",\"image\":[{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/mercadocannabis.jpg\"},{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/mercadocannabis.jpg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/mercadocannabis.jpg\"}],\"channels\":[{\"name\":\"Cannabis\"},{\"name\":\"News\"},{\"name\":\"Penny
Stocks\"},{\"name\":\"Financing\"},{\"name\":\"Events\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"UGRO\"}],\"tags\":[{\"name\":\"Bradley
Nattrass\"},{\"name\":\"Cannabis Capital Conference\"},{\"name\":\"CCC\"},{\"name\":\"CCC
Chicago\"},{\"name\":\"Controlled Environment Agriculture\"},{\"name\":\"Inc.\"},{\"name\":\"urban-gro\"}]},{\"id\":34915565,\"author\":\"Lekha
Gupta\",\"created\":\"Tue, 26 Sep 2023 14:11:00 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:11:01 -0400\",\"title\":\"Boosting Digital Fortresses: Broadcom
& Google Cloud Partner To Boost Cybersecurity With AI\",\"teaser\":\"Broadcom
Inc&#39;s (NASDAQ: AVGO) division Symantec announced a collaboration with\_Alphabet
Inc.\_(NASDAQ: GOOGL) (NASDAQ: GOOG) Google C\",\"body\":\"<p><strong>Broadcom
Inc</strong>&#39;s (NASDAQ:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/AVGO#NASDAQ\\\">AVGO</a>)
division Symantec announced a collaboration with&nbsp;<strong>Alphabet Inc.</strong>&nbsp;(NASDAQ:<a
class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/GOOGL#NASDAQ\\\">GOOGL</a>)
(NASDAQ:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/GOOG#NASDAQ\\\">GOOG</a>)
Google Cloud to boost&nbsp;<a class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/pressreleases/23/09/g34906060/broadcom-partners-with-google-cloud-to-strengthen-gen-ai-powered-cybersecurity\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\">generative AI-powered cybersecurity</a>.</p>\\r\\n\\r\\n<p>As
per the deal, Google Cloud will integrate generative AI into the Symantec
Security platform in a phased rollout manner to offer customers a strong technical
edge to detect and eradicate cyber-attacks. This will result in increased
security and higher SOC productivity.</p>\\r\\n\\r\\n<p>AVGO has been a partner
of Google Cloud for several years, which has helped accelerate its growth
strategy.&nbsp;</p>\\r\\n\\r\\n<p><strong><em>Related</em></strong><em>:&nbsp;</em><a
class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/news/23/09/34787271/googles-power-move-broadcom-ditched-in-favor-of-marvell-amid-ai-chip-wars\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\"><em>Google&#39;s Power Move: Broadcom Ditched in Favor
of Marvell Amid AI Chip Wars</em></a></p>\\r\\n\\r\\n<p>&quot;Symantec has
a long history of designing, implementing, and fine-tuning artificial intelligence
and machine learning for critical security use cases and helping our customers
drive value out of AI. Our partnership with Google Cloud is part of that continuing
journey to put the most innovative security solutions possible into user hands.
Our engineers have simplified the process in ways that will enable customers
to be much more productive and effective. This is just the beginning of a
great collaboration that will help to kickstart the benefits of AI throughout
the broader security ecosystem,&quot; said Adam Bromwich, CTO and Head of
Engineering, Symantec Enterprise Division.</p>\\r\\n\\r\\n<p><strong><em>Also
Read</em></strong><em>:&nbsp;</em><a class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/news/23/09/34829001/despite-speculation-of-googles-shift-analyst-sees-strong-ties-and-momentum-in-broadcoms-decade-long\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\"><em>Despite Speculation of Google&#39;s Shift, Analyst
Sees Strong Ties and Momentum in Broadcom&#39;s Decade-Long Partnership</em></a></p>\\r\\n\\r\\n<p><strong>Price
Action</strong>: AVGO shares are trading lower by 1.97% at $817.44 on the
last check Tuesday.</p>\\r\\n \",\"url\":\"https://www.benzinga.com/markets/equities/23/09/34915565/boosting-digital-fortresses-broadcom-google-cloud-partner-to-boost-cybersecurity-with-ai\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/avgo.png\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/avgo.png\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/avgo.png\"}],\"channels\":[{\"name\":\"Equities\"},{\"name\":\"Large
Cap\"},{\"name\":\"News\"},{\"name\":\"Contracts\"},{\"name\":\"Markets\"}],\"stocks\":[{\"name\":\"AVGO\"},{\"name\":\"GOOG\"},{\"name\":\"GOOGL\"}],\"tags\":[{\"name\":\"Briefs\"}]},{\"id\":34911177,\"author\":\"Shivani
Kumaresan\",\"created\":\"Tue, 26 Sep 2023 14:02:31 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:02:32 -0400\",\"title\":\"From Vintage Vibes To Modern Street:
Lee Unveils '80s-Inspired Collection With China's ROARINGWILD\",\"teaser\":\"Kontoor
Brands Inc\_(NYSE: KTB) owned denim brand Lee has unveiled its unisex apparel
collection in partnership with ROARINGWILD, the\_\",\"body\":\"<p><strong>Kontoor
Brands Inc&nbsp;</strong>(NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/KTB#NYSE\\\">KTB</a>)
owned denim brand Lee has unveiled its unisex apparel collection in partnership
with ROARINGWILD, the&nbsp;<a class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/pressreleases/23/09/b34901988/lee-launches-its-first-unisex-apparel-collaboration-with-global-streetwear-brand-roaringwild\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\">China-based brand known for their streetwear.</a></p>\\r\\n\\r\\n<p>The
partnership creates a global unisex capsule showcasing the link between music
and culture through streetwear.&nbsp;</p>\\r\\n\\r\\n<p>&quot;This collection
is a big step for Lee as it is our first fully unisex collection and one that&#39;s
available globally,&quot; said Brigid Stevens, global vice president of marketing,
Lee.</p>\\r\\n\\r\\n<p><strong><em>Also Read</em></strong><em>:&nbsp;</em><a
class=\\\"editor-rtfLink\\\" href=\\\"https://www.benzinga.com/markets/equities/23/09/34305997/weaving-modernity-with-tradition-staud-and-wrangler-bring-out-new-collection\\\"
style=\\\"background-image:initial; background-position:initial; background-size:initial;
background-repeat:initial; background-attachment:initial; background-origin:initial;
background-clip:initial; margin-top:0pt; margin-bottom:0pt; color:#4a6ee0\\\"
target=\\\"_blank\\\"><em>Weaving Modernity With Tradition - STAUD And Wrangler
Bring Out New Collection</em></a></p>\\r\\n\\r\\n<p>The collection&#39;s essence
is deeply rooted in the vibrant &#39;80s, with the capsule featuring graphic
tees, denim jackets, and jeans in oversized fits.</p>\\r\\n\\r\\n<p>Noteworthy
pieces include the Oversized Rider Jacket and the Wide Leg Jean, a piece that
combines a global design perspective with a side-leg zipper.</p>\\r\\n\\r\\n<p>The
range is priced between $80 and $230, available in sizes XS through XL. Customers
can shop the collection across Lee&#39;s digital platforms and physical outlets
in the U.S. and Europe.</p>\\r\\n\\r\\n<p>Additionally, the collection is
featured in ROARINGWILD outlets and its online presence in China and other
selected regions.</p>\\r\\n\\r\\n<p><strong>Price Action</strong>: KTB shares
are trading higher by 1.23% at $42.21 on the last check Tuesday.</p>\\r\\n\\r\\n<p><em>This
content was partially produced with the help of AI tools and was reviewed
and published by Benzinga editors.</em></p>\\r\\n \",\"url\":\"https://www.benzinga.com/markets/equities/23/09/34911177/from-vintage-vibes-to-modern-street-lee-unveils-80s-inspired-collection-with-chinas-roaringwild\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/09/26/ktb.png\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/09/26/ktb.png\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/09/26/ktb.png\"}],\"channels\":[{\"name\":\"Equities\"},{\"name\":\"News\"},{\"name\":\"Markets\"},{\"name\":\"General\"}],\"stocks\":[{\"name\":\"KTB\"}],\"tags\":[{\"name\":\"AI
Generated\"},{\"name\":\"Briefs\"}]},{\"id\":34921760,\"author\":\"Benzinga
Insights\",\"created\":\"Tue, 26 Sep 2023 14:00:50 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:00:51 -0400\",\"title\":\"The Latest Analyst Ratings for Clarivate\",\"teaser\":\"
\",\"body\":\"<p>Clarivate (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/CLVT#NYSE\\\">CLVT</a>)
has observed the following analyst ratings within the last quarter: </p>\\n<table>\\n<thead>\\n<tr>\\n<th></th>\\n<th
align=\\\"right\\\"><strong>Bullish</strong></th>\\n<th align=\\\"right\\\"><strong>Somewhat
Bullish</strong></th>\\n<th align=\\\"right\\\"><strong>Indifferent</strong></th>\\n<th
align=\\\"right\\\"><strong>Somewhat Bearish</strong></th>\\n<th align=\\\"right\\\"><strong>Bearish</strong></th>\\n</tr>\\n</thead>\\n<tbody>\\n<tr>\\n<td><strong>Total
Ratings</strong></td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">3</td>\\n<td
align=\\\"right\\\">5</td>\\n<td align=\\\"right\\\">2</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>Last
30D</strong></td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>1M
Ago</strong></td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>2M
Ago</strong></td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">2</td>\\n<td
align=\\\"right\\\">4</td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>3M
Ago</strong></td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">1</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n</tbody>\\n</table>\\n\\n<p>According
to 11 analyst offering 12-month price targets in the last 3 months, Clarivate
has an average price target of $8.68 with a high of $12.00 and a low of $6.00.
</p>\\n<p>Below is a summary of how these 11 analysts rated Clarivate over
the past 3 months. The greater the number of bullish ratings, the more positive
analysts are on the stock and the greater the number of bearish ratings, the
more negative analysts are on the stock</p>\\n<p><img src=\\\"https://www.benzinga.com/files/images/story/2023/1695751247_0.png\\\"
alt=\\\"price target chart\\\" /></p>\\n\\n<p>This current average has decreased
by 22.36% from the previous average price target of $11.18. </p>\\n<p><a href=\\\"https://www.benzinga.com/quote/CLVT/analyst-ratings?utm_source=Benzinga
Insights\\\"><strong>Stay up to date on Clarivate analyst ratings.</strong></a></p>\\n<h1>What
Are Analyst Ratings?</h1>\\n<p>Analysts are specialists within banking and
financial systems that typically report for specific stocks or within defined
sectors. These people research company financial statements, sit in conference
calls and meetings, and speak with relevant insiders to determine what are
known as analyst ratings for stocks. Typically, analysts will rate each stock
once a quarter. </p>\\n<p>Some analysts publish their predictions for metrics
such as growth estimates, earnings, and revenue to provide additional guidance
with their ratings. When using analyst ratings, it is important to keep in
mind that stock and sector analysts are also human and are only offering their
opinions to investors.</p>\\n<p>This article was generated by Benzinga's automated
content engine and reviewed by an editor.</p>\",\"url\":\"https://www.benzinga.com/analyst-ratings/23/09/34921760/the-latest-analyst-ratings-for-clarivate\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/analyst_ratings_image_1.jpeg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/analyst_ratings_image_1.jpeg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/analyst_ratings_image_1.jpeg\"}],\"channels\":[{\"name\":\"Analyst
Ratings\"}],\"stocks\":[{\"name\":\"CLVT\"}],\"tags\":[{\"name\":\"BZI-AAR\"}]},{\"id\":34921757,\"author\":\"Benzinga
Insights\",\"created\":\"Tue, 26 Sep 2023 14:00:31 -0400\",\"updated\":\"Tue,
26 Sep 2023 14:00:31 -0400\",\"title\":\"Expert Ratings for Genmab\",\"teaser\":\"
\",\"body\":\"<p>Genmab (NASDAQ:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/GMAB#NASDAQ\\\">GMAB</a>)
has observed the following analyst ratings within the last quarter: </p>\\n<table>\\n<thead>\\n<tr>\\n<th></th>\\n<th
align=\\\"right\\\"><strong>Bullish</strong></th>\\n<th align=\\\"right\\\"><strong>Somewhat
Bullish</strong></th>\\n<th align=\\\"right\\\"><strong>Indifferent</strong></th>\\n<th
align=\\\"right\\\"><strong>Somewhat Bearish</strong></th>\\n<th align=\\\"right\\\"><strong>Bearish</strong></th>\\n</tr>\\n</thead>\\n<tbody>\\n<tr>\\n<td><strong>Total
Ratings</strong></td>\\n<td align=\\\"right\\\">8</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">5</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>Last
30D</strong></td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>1M
Ago</strong></td>\\n<td align=\\\"right\\\">2</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>2M
Ago</strong></td>\\n<td align=\\\"right\\\">4</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">3</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n<tr>\\n<td><strong>3M
Ago</strong></td>\\n<td align=\\\"right\\\">1</td>\\n<td align=\\\"right\\\">0</td>\\n<td
align=\\\"right\\\">0</td>\\n<td align=\\\"right\\\">2</td>\\n<td align=\\\"right\\\">0</td>\\n</tr>\\n</tbody>\\n</table>\\n\\n<p>According
to 13 analyst offering 12-month price targets in the last 3 months, Genmab
has an average price target of $44.23 with a high of $54.00 and a low of $33.00.
</p>\\n<p>Below is a summary of how these 13 analysts rated Genmab over the
past 3 months. The greater the number of bullish ratings, the more positive
analysts are on the stock and the greater the number of bearish ratings, the
more negative analysts are on the stock</p>\\n<p><img src=\\\"https://www.benzinga.com/files/images/story/2023/1695751228_0.png\\\"
alt=\\\"price target chart\\\" /></p>\\n\\n<p>This current average has increased
by 34.03% from the previous average price target of $33.00. </p>\\n<p><a href=\\\"https://www.benzinga.com/quote/GMAB/analyst-ratings?utm_source=Benzinga
Insights\\\"><strong>Stay up to date on Genmab analyst ratings.</strong></a></p>\\n<h1>How
Are Analyst Ratings Determined?</h1>\\n<p>Benzinga tracks 150 analyst firms
and reports on their stock expectations. Analysts typically arrive at their
conclusions by predicting how much money a company will make in the future,
usually the upcoming five years, and how risky or predictable that company's
revenue streams are.</p>\\n<p>Analysts attend company conference calls and
meetings, research company financial statements, and communicate with insiders
to publish their ratings on stocks. Analysts typically rate each stock once
per quarter or whenever the company has a major update.</p>\\n<p>Some analysts
also offer predictions for helpful metrics such as earnings, revenue, and
growth estimates to provide further guidance as to what to do with certain
tickers. It is important to keep in mind that while stock and sector analysts
are specialists, they are also human and can only forecast their beliefs to
traders.</p>\\n<p>This article was generated by Benzinga's automated content
engine and reviewed by an editor.</p>\",\"url\":\"https://www.benzinga.com/analyst-ratings/23/09/34921757/expert-ratings-for-genmab\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2023/analyst_ratings_image_0.png\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2023/analyst_ratings_image_0.png\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2023/analyst_ratings_image_0.png\"}],\"channels\":[{\"name\":\"Analyst
Ratings\"}],\"stocks\":[{\"name\":\"GMAB\"}],\"tags\":[{\"name\":\"BZI-AAR\"}]}]\n"
string: !!binary |
H4sIAAAAAAAA/62SQWvbQBCF7/0Vgwg9eSxbtuPExwQKPrix60IpwYeRNZa2lrRid1XFDfnvnZVk
Q1sogeSy7DIzb/d7+x6fA5UEi+n8JprPx/NBQLXLtAkWwR2Xv1SZEnzmxiZsj8Eg2Bsmx9IffK15
ANEEtlxBNIpmMJ6KyGI8AxxNRyPpravk1b1OuZylc0U/tAFVJvzEFsgw5LphA2TBZQzbj2uo6jy3
ENP+CAejC6A8R6cKhkylmR3CsvzJ1mljoaCTVK2GmEXAsrWCA7HRlIjkXhcFl47MqdP5xAncZ6QM
rOXKPB/6ZzFZ9l7IPtbJqdvVJpdN5lxlF2HYNM0w7q0aimjYKFWE0Swc3YZnV8PCc2HPhcKFLReS
ReFCixW2XOi50L8Hz1zYcslsj4WChR4LY8YLFsZGXqYKSsXGx538VEZlybmV03NQUuHd/bZcroIX
KYrQ/vhHabPZyDw/+TEvEQQvg0txu/7+d1FEHKVeYucbLxG6nszeFqFoMb1+bYSkd/5PhNZGTDNq
T2AzMbpLkTOU+M/3Xvo4HZysd6sHuKdKOcqhrlJpkQj4mLX2dKlYkTmygzWbgzYFOA0Ptav6E5UJ
GFJWxpSzUMmtchWZVCba6avoNvIzV5PxzbvlSSwOqzMkdpBtpHpI7CCxhcS40LjvIPEM2WXOQ3ZR
K1pI7LHQadQC+aY8rb8s/xeZ3Yff84coYHgEAAA=
headers:
Cache-Control:
- max-age=3, must-revalidate
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 26 Sep 2023 19:06:27 GMT
ETag:
- Ad_qMZ8R2oY2FIw8KqnTuD0be6cMGbEqRGrqTLEaLAo=
- Tue, 23 Sep 2025 18:49:50 GMT
RateLimit-Limit:
- '250'
RateLimit-Remaining:
- '249'
RateLimit-Reset:
- '1'
Transfer-Encoding:
- chunked
Vary:
- Accept,Accept-Encoding
Via:
- kong/2.8.3, 1.1 5888333a47f4bcb4270f45fea45f08bc.cloudfront.net (CloudFront)
- kong/3.5.0, 1.1 723f5b4846bb0ac4d05b8fe23c0fa9e0.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- ns7pUh-G2tFcPJmWDK8PoG6AqogGH9xcNUe3-6cuo5nxdLdH-5Y_Zw==
- v709e2sLVh_quNjk6nkrqtNxE2rgyghPBvg9rK42je1RwjNMbLCC-A==
X-Amz-Cf-Pop:
- MIA3-P4
- ORD56-P3
X-Bz-Request-Id:
- 192.168.240.19-8000-38-51085582-1-1695755186.987
- dbec12af-61f6-4dfd-a717-6a8ad1365047#75850974
X-Cache:
- Miss from cloudfront
X-Kong-Proxy-Latency:
- '0'
- '2'
X-Kong-Request-Id:
- 6daf563644f58f567a7dfe0fea247a11
X-Kong-Upstream-Latency:
- '47'
- '109'
X-Powered-By:
- Pentium
X-RateLimit-Limit-Minute:
- '5000'
X-RateLimit-Limit-Second:
- '250'
X-RateLimit-Remaining-Minute:
- '4998'
X-RateLimit-Remaining-Second:
- '249'
status:
code: 200
message: OK

View File

@@ -9,441 +9,67 @@ interactions:
Connection:
- keep-alive
method: GET
uri: https://api.benzinga.com/api/v2/news?displayOutput=full&page=0&pageSize=20&sort=created%3Adesc&token=MOCK_TOKEN
uri: https://api.benzinga.com/api/v2/news?displayOutput=full&page=0&pageSize=2&sort=created%3Adesc&token=MOCK_TOKEN
response:
body:
string: "[{\"id\":39519753,\"author\":\"Benzinga Newsdesk\",\"created\":\"Thu,
27 Jun 2024 06:11:23 -0400\",\"updated\":\"Thu, 27 Jun 2024 06:11:24 -0400\",\"title\":\"JinkoSolar
Has Repurchased 4.5M ADSs In An Aggregate Amount Of Approximately $110.7M
In The Open Market Under Its Share Repurchase Program Announced In July 2022
And The Extended Share Repurchase Program Announced In December 2023\",\"teaser\":\"\",\"body\":\"\",\"url\":\"https://www.benzinga.com/news/24/06/39519753/jinkosolar-has-repurchased-4-5m-adss-in-an-aggregate-amount-of-approximately-110-7m-in-the-open-mark\",\"image\":[],\"channels\":[{\"name\":\"News\"},{\"name\":\"Commodities\"},{\"name\":\"Buybacks\"},{\"name\":\"Markets\"},{\"name\":\"General\"}],\"stocks\":[{\"name\":\"JKS\"}],\"tags\":[]},{\"id\":39519696,\"author\":\"Benzinga
Neuro\",\"created\":\"Thu, 27 Jun 2024 06:09:18 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:09:19 -0400\",\"title\":\"Fundstrat's Tom Lee Predicts S&P
500 Will Hit 15,000 By 2030 Driven By Gen Z And AI Wave\",\"teaser\":\"Tom
Lee, the managing partner and head of research at Fundstrat Global Advisors,
shared his bullish prediction for the S&amp;P 500, foreseeing it reaching
15,000 by 2030.\",\"body\":\"<p><strong>Tom Lee</strong>, the managing partner
and head of research at<strong> Fundstrat Global Advisors</strong>, shared
his <a href=\\\"https://www.benzinga.com/analyst-ratings/analyst-color/24/06/39350137/s-p-500-poised-to-extend-bull-run-market-strategist-sees-ai-fervor-and-ebbing-infla\\\">bullish
prediction</a> for the S&amp;P 500, foreseeing it reaching 15,000 by 2030
riding the AI wave.</p>\\n\\n\\n\\n<p><strong>What Happened</strong>: Lee&#8217;s
forecast is based on the emergence of a new cycle in the stock market, where
annual returns are expected to compound at a high rate. This cycle is being
driven by the younger generations, particularly millennials and Gen Z.</p>\\n\\n\\n\\n<p>&#8220;This
will be the 3rd time that stocks entered a cycle where annual returns compound
at high teens,&#8221; Lee said in an interview with CNBC&#8217;s Last Call.
</p>\\n\\n\\n\\n<figure class=\\\"wp-block-embed is-type-rich is-provider-twitter
wp-block-embed-twitter\\\"><div class=\\\"wp-block-embed__wrapper\\\">\\n<blockquote
class=\\\"twitter-tweet\\\" data-width=\\\"500\\\" data-dnt=\\\"true\\\"><p
lang=\\\"en\\\" dir=\\\"ltr\\\">.<a href=\\\"https://twitter.com/fundstrat?ref_src=twsrc%5Etfw\\\">@fundstrat</a>&#39;s
Tom Lee says the S&amp;P 500 will hit 15,000 by 2030. &quot;This will be the
3rd time that stocks entered a cycle where annual returns compound at high
teens.&quot; <a href=\\\"https://twitter.com/search?q=%24SPX&amp;src=ctag&amp;ref_src=twsrc%5Etfw\\\">$SPX</a>
<a href=\\\"https://t.co/31vzaEaGpo\\\">pic.twitter.com/31vzaEaGpo</a></p>&mdash;
Last Call (@LastCallCNBC) <a href=\\\"https://twitter.com/LastCallCNBC/status/1806110011669549190?ref_src=twsrc%5Etfw\\\">June
26, 2024</a></blockquote><script async src=\\\"https://platform.twitter.com/widgets.js\\\"
charset=\\\"utf-8\\\"></script>\\n</div></figure>\\n\\n\\n\\n<p>The growth
of companies focused on artificial intelligence is expected to yield high
returns, according to Lee. Additionally, the increasing number of digital
laborers investing in the economy is contributing to this cycle.</p>\\n\\n\\n\\n<p>S&amp;P
500 ETF options provide investors with <a href=\\\"https://www.benzinga.com/money/best-sp-500-etfs\\\">multiple
ways to invest</a> in America&#8217;s largest 500 companies. Interested investors
may check out the <strong>Schwab U.S. Large-Cap ETF</strong> (NYSE:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/SCHX#NYSE\\\">SCHX</a>), <strong>Vanguard
S&amp;P 500 ETF</strong> (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/VOO#NYSE\\\">VOO</a>),
and<strong> iShares Core S&amp;P 500 ETF</strong> (NYSE:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/IVV#NYSE\\\">IVV</a>) to diversify
their portfolios.</p>\\n\\n\\n\\n<p><em>See Also: <a href=\\\"https://www.benzinga.com/news/24/06/39493328/hillary-clinton-offers-advice-to-biden-ahead-of-presidential-debate-its-a-waste-of-time-to-try-to-re?itm_source=parsely-api\\\"
target=\\\"_blank\\\" rel=\\\"noreferrer noopener\\\">Hillary Clinton Offers
Advice To Biden Ahead Of Presidential Debate: &#8216;It&#8217;s A Waste Of
Time To Try To Refute Mr. Trump&#8217;s Arguments&#8217;</a></em></p>\\n\\n\\n\\n<p><strong>Why
It Matters</strong>: Lee&#8217;s prediction is in stark contrast to the forecasts
of other financial experts. In May, <strong>Goldman Sachs</strong> predicted
a <a href=\\\"https://www.benzinga.com/markets/equities/24/05/38863242/stock-market-rally-for-2024-has-peaked-goldman-sachs-forecasts-flat-return-from-now-till-the-end\\\">flat
return for the S&amp;P 500</a> for the remainder of 2024, suggesting that
the stock market rally had peaked. However, Lee&#8217;s forecast indicates
a more extended period of growth.</p>\\n\\n\\n\\n<p>Another analyst, <strong>Gene
Munster</strong>, predicted that the stock market would continue to rise for
<a href=\\\"https://www.benzinga.com/markets/equities/24/05/38910879/stock-market-to-rally-for-3-5-years-before-ai-bubble-bursts-predicts-veteran-analyst-gene-munste\\\">another
three to five years</a> before an AI bubble bursts. This aligns with Lee&#8217;s
forecast of a sustained period of growth driven by AI-focused companies.</p>\\n\\n\\n\\n<p>However,
not all experts share this optimistic outlook. Economist <strong>Harry Dent</strong>
warned of a <a href=\\\"https://www.benzinga.com/analyst-ratings/analyst-color/24/06/39257592/economist-warns-crash-of-a-lifetime-is-coming-predicts-s-p-500-to-plummet-86-from-t\\\">looming
&#8220;crash of a lifetime&#8221;</a> due to the current &#8220;everything&#8221;
bubble, which he believes has yet to burst. Despite this, Lee&#8217;s prediction
suggests a period of sustained growth, particularly in the AI sector.</p>\\n\\n\\n\\n<p><em>Read
Next: <a href=\\\"https://www.benzinga.com/news/24/06/39516013/mark-cuban-accuses-trump-of-ripping-off-thousands-of-hard-working-americans-and-not-wanting-to-leave?itm_source=parsely-api\\\"
target=\\\"_blank\\\" rel=\\\"noreferrer noopener\\\">Mark Cuban Accuses Trump
Of Ripping Off &#8216;Thousands Of Hard Working Americans&#8217; And Not Wanting
To Leave The White House</a></em></p>\\n\\n\\n\\n<p>Image Via Shutterstock
</p>\\n\\n\\n\\n<p>This story was generated using <a href=\\\"https://www.benzinga.com/author/benzinga-neuro\\\">Benzinga
Neuro</a> and edited by <u><a href=\\\"https://www.benzinga.com/topic/Kaustubh-Bagalkote\\\"
target=\\\"_blank\\\" rel=\\\"noreferrer noopener\\\">Kaustubh Bagalkote</a></u></p>\",\"url\":\"https://www.benzinga.com/markets/equities/24/06/39519696/fundstrats-tom-lee-predicts-s-p-500-will-hit-15-000-by-2030-driven-by-gen-z-and-ai-wave\",\"image\":[{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2024/06/27/Southwest-Gas-Holdings-Inc-.jpeg\"},{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2024/06/27/Southwest-Gas-Holdings-Inc-.jpeg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2024/06/27/Southwest-Gas-Holdings-Inc-.jpeg\"}],\"channels\":[{\"name\":\"Equities\"},{\"name\":\"News\"},{\"name\":\"Guidance\"},{\"name\":\"Economics\"},{\"name\":\"Markets\"},{\"name\":\"Analyst
Ratings\"}],\"stocks\":[{\"name\":\"IVV\"},{\"name\":\"SCHX\"},{\"name\":\"VOO\"}],\"tags\":[{\"name\":\"Fundstrat
Global Advisors\"},{\"name\":\"Kaustubh Bagalkote\"},{\"name\":\"Tom Lee\"}]},{\"id\":39519681,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:08:36 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:08:37 -0400\",\"title\":\"Assure Holdings On Wednesday Filed
Two Form RWs, Requesting Withdrawal Of Offering And Business Combination Filings\",\"teaser\":\"\",\"body\":\"\",\"url\":\"https://www.benzinga.com/m-a/24/06/39519681/assure-holdings-on-wednesday-filed-two-form-rws-requesting-withdrawal-of-offering-and-business-combi\",\"image\":[],\"channels\":[{\"name\":\"M&A\"},{\"name\":\"News\"},{\"name\":\"Offerings\"}],\"stocks\":[{\"name\":\"IONM\"}],\"tags\":[]},{\"id\":39519678,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:07:25 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:07:25 -0400\",\"title\":\"LanzaTech and LanzaJet Introduce
CirculAir for Sustainable Aviation Fuel\",\"teaser\":\"LanzaTech Global, Inc.\_(NASDAQ:LNZA),
the carbon recycling company transforming waste carbon into sustainable fuels,
chemicals, and materials, and\_LanzaJet, Inc., a leading sustainable fuels
technology company\",\"body\":\"<p><a href=\\\"https://www.globenewswire.com/Tracker?data=hzva6c58Hgdr-3ljzxFh_1jkzXsUHnGwpJbfp6TLLSEmnrWYDKw2rZCvnI1CtH1f5Mx1-EZsjnrJjJdNSuPA69Q9niQny5l2i_yGWp5vkZQ=\\\"
target=\\\"_blank\\\">LanzaTech Global, Inc.</a>&nbsp;(NASDAQ:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/LNZA#NASDAQ\\\">LNZA</a>), the carbon
recycling company transforming waste carbon into sustainable fuels, chemicals,
and materials, and&nbsp;<a href=\\\"https://www.globenewswire.com/Tracker?data=hzva6c58Hgdr-3ljzxFh_07GxgCzPvFFKv5K8IRKKsqDthIMnkO744UmX8P-jxV_ABEPwTAZKNl_1zU9b0Asvg==\\\"
target=\\\"_blank\\\">LanzaJet, Inc.</a>, a leading sustainable fuels technology
company and fuels producer, are launching CirculAir&trade;, a new joint offering
to convert waste, carbon, and renewable power into sustainable aviation fuel
(SAF) and immediately accelerate decarbonization of the aviation industry
globally. CirculAir is a breakthrough offering that provides an economical
and commercialized alternative to Fischer-Tropsch technology to create eFuels,
Power-to-Liquids (PtL), and Waste-to-Fuels leveraging the already ASTM-approved
SAF production pathway that uses ethanol as the biointermediate and Alcohol-to-Jet
(ATJ) technology to produce SAF and Renewable Diesel (RD).<br />\\n&nbsp;</p>\\n\\n<p>CirculAir
is a formalized joint offering and end-to-end technology solution that turns
nearly any waste source including municipal solid waste (MSW), agricultural
residues, carbon emissions from industrial and refining processes, carbon
dioxide (CO2) through direct air capture, and renewable power into SAF. CirculAir
combines the groundbreaking technologies of LanzaTech and LanzaJet to form
an efficient and economically compelling offering that provides the aviation
industry with a solution to produce waste-based SAF on a global scale. CirculAir
will combine LanzaTech and LanzaJet&#39;s technologies; first incorporating
LanzaTech&#39;s novel gas fermentation technology to convert nearly any waste
resource into CarbonSmart&trade; ethanol and then second, executing LanzaJet&#39;s
Alcohol-to-Jet (ATJ) technology by taking the ethanol and converting it to
drop-in SAF. The SAF made through this process is expected to reduce aviation
emissions by at least&nbsp;85%&nbsp;and can also produce carbon negative results,
depending on the feedstock.</p>\\n\\n<p>SAF is estimated to account for 65&ndash;70%&nbsp;of
overall aviation emissions reduction to achieve net zero by 2050, making it
a critical tool for this hard-to-abate industry; however, historic supply
constraints, high costs, and technical barriers have made it difficult for
the industry to default to SAF as the primary fuel source. CirculAir breaks
down these barriers. The technology is able to turn a wide range of waste-based
feedstocks into SAF, and it is being adopted by a range of customers across
the globe. This widespread adoption is projected to accelerate the production
and economies of scale necessary to bring down the global cost of SAF.</p>\",\"url\":\"https://www.benzinga.com/news/24/06/39519678/lanzatech-and-lanzajet-introduce-circulair-for-sustainable-aviation-fuel\",\"image\":[],\"channels\":[{\"name\":\"News\"}],\"stocks\":[{\"name\":\"LNZA\"}],\"tags\":[]},{\"id\":39519665,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:06:55 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:06:56 -0400\",\"title\":\"Needham Reiterates Buy on Sarepta
Therapeutics, Maintains $235 Price Target\",\"teaser\":\"Needham analyst
Gil Blum reiterates Sarepta Therapeutics (NASDAQ:SRPT) with a Buy and maintains
$235 price target.\",\"body\":\"Needham analyst Gil Blum reiterates Sarepta
Therapeutics (NASDAQ:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/SRPT#NASDAQ\\\">SRPT</a>)
with a Buy and maintains $235 price target.\",\"url\":\"https://www.benzinga.com/news/24/06/39519665/needham-reiterates-buy-on-sarepta-therapeutics-maintains-235-price-target\",\"image\":[],\"channels\":[{\"name\":\"News\"},{\"name\":\"Price
Target\"},{\"name\":\"Reiteration\"},{\"name\":\"Analyst Ratings\"}],\"stocks\":[{\"name\":\"SRPT\"}],\"tags\":[]},{\"id\":39519662,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:05:52 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:05:53 -0400\",\"title\":\"GNL Reduces Debt with $170M Sale
of Americold-Leased Properties\",\"teaser\":\"Global Net Lease, Inc. (NYSE:GNL)
(&#34;GNL&#34; or the &#34;Company&#34;) today announced the successful disposition
of a portfolio of nine cold storage properties that are currently leased to
subsidiaries of\",\"body\":\"<p>Global Net Lease, Inc. (NYSE:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/GNL#NYSE\\\">GNL</a>) (&quot;GNL&quot;
or the &quot;Company&quot;) today announced the successful disposition of
a portfolio of nine cold storage properties that are currently leased to subsidiaries
of Americold Realty Trust, Inc. (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/COLD#NYSE\\\">COLD</a>)
for&nbsp;$170 million, at a&nbsp;7.88%&nbsp;cash cap rate on 3.3 years of
weighted average remaining lease-term.</p>\\n\\n<p>This disposition is a significant
achievement in GNL&#39;s ongoing strategic disposition initiative and aligns
with the Company&#39;s 2024 full-year&nbsp;guidance, which projected a disposition
cash cap rate range of&nbsp;7%&nbsp;to&nbsp;8%. GNL plans to use the net proceeds
from this sale to reduce outstanding debt and further lower the Company&#39;s
leverage. The sale of this portfolio, which GNL acquired for&nbsp;$153.4 million,
is part of the previously announced&nbsp;$567 million1&nbsp;of closed and
pipeline dispositions at a cash cap rate of&nbsp;7.2%.</p>\",\"url\":\"https://www.benzinga.com/news/24/06/39519662/gnl-reduces-debt-with-170m-sale-of-americold-leased-properties\",\"image\":[],\"channels\":[{\"name\":\"News\"},{\"name\":\"Asset
Sales\"}],\"stocks\":[{\"name\":\"COLD\"},{\"name\":\"GNL\"}],\"tags\":[]},{\"id\":39519629,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:04:50 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:04:51 -0400\",\"title\":\"Nkarta Begins Ntrust-1 Clinical Trial
Of NKX019 In Lupus Nephritis, With The First Patient In Screening; Says IND
Cleared For Ntrust-2\",\"teaser\":\"The company also announced the clearance
by the U.S. Food and Drug Administration (FDA) of its second Investigational
New Drug (IND) application for NKX019 in autoimmune disease.\\n\\nWith this
new IND, Nkarta plans to\",\"body\":\"<p>The company also announced the clearance
by the U.S. Food and Drug Administration (FDA) of its second Investigational
New Drug (IND) application for NKX019 in autoimmune disease.</p>\\n\\n<p>With
this new IND, Nkarta plans to initiate Ntrust-2, an open-label, multi-center
clinical trial of NKX019 for the treatment of systemic sclerosis (SSc, scleroderma),
idiopathic inflammatory myopathy (IIM, myositis) and ANCA-associated vasculitis
(AAV).</p>\\n\\n<p>Clinical data from Ntrust-1 and Ntrust-2 planned for 2025.</p>\",\"url\":\"https://www.benzinga.com/news/24/06/39519629/nkarta-begins-ntrust-1-clinical-trial-of-nkx019-in-lupus-nephritis-with-the-first-patient-in-screeni\",\"image\":[],\"channels\":[{\"name\":\"News\"}],\"stocks\":[{\"name\":\"NKTX\"}],\"tags\":[]},{\"id\":39519626,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:04:18 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:04:19 -0400\",\"title\":\"Entero Therapeutics Publishes Innovative
Celiac Disease Research in Top Gastroenterology Journal\",\"teaser\":\"Entero
Therapeutics, Inc., (NASDAQ:ENTO), (&#34;Entero Therapeutics&#34; or the &#34;Company&#34;),
a clinical-stage biopharmaceutical company specializing in the development
of targeted, non-systemic therapies\",\"body\":\"<p>Entero Therapeutics, Inc.,
(NASDAQ:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/ENTO#NASDAQ\\\">ENTO</a>),
(&quot;Entero Therapeutics&quot; or the &quot;Company&quot;), a clinical-stage
biopharmaceutical company specializing in the development of targeted, non-systemic
therapies for&nbsp;gastrointestinal (GI) diseases, today announced the publication
of &quot;<a href=\\\"https://www.globenewswire.com/Tracker?data=h1ge28PUKiAz2IQRtYAA0SJfZUgHAckbiDzteTYi9l8E2W0CnDTx_7ezI5x8VIMa_Wg2k3zipLcko2Av_SJvn_YqB86D2_mvrekcSAqxiQ6X9xBpjdri1PzvYFtLCS5UyIAfampZnL10YfhDRNPNN_QRw5fCtj5xjAdou5NqsB0k9gt5BdDl8iwvU7rc-8SH18hztI6ucqenOv4l7Y52Po1PwNhmZshxk4x1WTRu1EZPz5H5PVvedeWlwxDhfzf2EotxEigcF8uAmCKc-SX9nQ==\\\"
target=\\\"_blank\\\">A Composite Morphometric Duodenal Biopsy Mucosal Scale
for Celiac Disease Encompassing Both Morphology and Inflammation</a>&quot;
in&nbsp;<em>Clinical Gastroenterology and Hepatology</em>, a pre-eminent journal
of the American Gastroenterological Association (AGA) that publishes innovative
diagnostic and therapeutic advances in clinical gastroenterology<em>.</em></p>\\n\\n<p>As
the number of promising therapies for celiac disease (CeD) grows, so does
the need to improve the measurement of clinically relevant histological endpoints.
The new approach presented in a peer-reviewed publication, led by Jack Syage,
Ph.D., President and Chief Scientific Officer of Entero Therapeutics, recognized
the need to advance beyond qualitative measures of histologic small intestinal
health and to develop a more accurate and sensitive scale based on the independent
quantitative measures of architectural changes (villus height to crypt depth
ratio, Vh:Cd) and inflammation (intraepithelial lymphocyte count, IEL) already
in use. The team created a composite score (VCIEL) for these two measures
that helps overcome the individual variances to improve the overall accuracy
of histological evaluation. The publication demonstrated the significant benefits
of the VCIEL scale based on the results of four previous clinical trials.
The significance of this achievement was highlighted in a special editorial
feature by the journal, demonstrating the importance of this novel metric
to CeD researchers.</p>\",\"url\":\"https://www.benzinga.com/news/24/06/39519626/entero-therapeutics-publishes-innovative-celiac-disease-research-in-top-gastroenterology-journal\",\"image\":[],\"channels\":[{\"name\":\"News\"}],\"stocks\":[{\"name\":\"ENTO\"}],\"tags\":[]},{\"id\":39519625,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:04:17 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:04:17 -0400\",\"title\":\"Nkarta Initiates Ntrust-1 Of NKX019
In Lupus Nephritis, With First Patient In Screening. FDA Also Clears Second
Investigational New Drug Application For NKX019 In Autoimmune Disease\",\"teaser\":\"\",\"body\":\"\",\"url\":\"https://www.benzinga.com/general/biotech/24/06/39519625/nkarta-initiates-ntrust-1-of-nkx019-in-lupus-nephritis-with-first-patient-in-screening-fda-also-c\",\"image\":[],\"channels\":[{\"name\":\"Biotech\"},{\"name\":\"News\"},{\"name\":\"Health
Care\"},{\"name\":\"General\"}],\"stocks\":[{\"name\":\"NKTX\"}],\"tags\":[]},{\"id\":39519611,\"author\":\"Benzinga
Neuro\",\"created\":\"Thu, 27 Jun 2024 06:03:50 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:06:56 -0400\",\"title\":\"International Paper Shares Dip 15%
In Pre-Market After Suzano Drops Acquisition Pursuit\",\"teaser\":\" \",\"body\":\"<p><strong>Suzano
SA</strong> (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/SUZ#NYSE\\\">SUZ</a>)
has decided to abandon its pursuit to acquire <strong>International Paper
Co.</strong> (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/IP#NYSE\\\">IP</a>),&nbsp;one
of&nbsp;North America&#8217;s&nbsp;largest recyclers and a global producer
of packaging, pulp and other fiber-based products. This news triggered a nearly
15% drop in International Paper&#8217;s shares during pre-market trading on
Thursday.</p>\\n\\n\\n\\n<p>Suzano&#8217;s decision was prompted by International
Paper&#8217;s rejection of its advances, Bloomberg <a href=\\\"https://www.bloomberg.com/news/articles/2024-06-26/suzano-drops-its-pursuit-of-international-paper-acquisition\\\">reported</a>
on Thursday. Instead, International Paper has chosen to focus on its own merger
plans with a different competitor.</p>\\n\\n\\n\\n<p>At the time of writing,
International Paper&#8217;s shares were trading at $39.67, a significant drop
from Wednesday&#8217;s close of $46.61, according to <a href=\\\"https://pro.benzinga.com\\\">Benzinga
Pro</a>.</p>\\n\\n\\n\\n<p><em>See Also: <a href=\\\"https://www.benzinga.com/markets/cryptocurrency/24/06/39448955/dogecoin-killer-shiba-inu-falters-here-is-what-these-key-indicators-reveal-about-its-short?itm_source=parsely-api\\\"
target=\\\"_blank\\\" rel=\\\"noreferrer noopener\\\">&#8216;Dogecoin Killer&#8217;
Shiba Inu Falters \u2014 Here Is What These Key Indicators Reveal About Its
Short-Term Prosp</a></em></p>\\n\\n\\n\\n<p>Suzano, a Brazil-based company,
had initially proposed a $15 billion acquisition offer to International Paper.
However, the deal was based on the condition that International Paper abandon
its plans to acquire British packaging firm <strong>DS Smith.</strong></p>\\n\\n\\n\\n<p>Suzano
stated that it was unable to engage International Paper in serious discussions
and was not willing to increase its offer. This announcement led Suzano&#8217;s
U.S.-listed shares to see an 11.94% jump in the pre-market on Thursday.</p>\\n\\n\\n\\n<p>Suzano
had hoped to negotiate on &#8220;private, confidential and amicable terms,&#8221;
as per its statement. However, as this was not feasible, the company decided
to terminate the talks.</p>\\n\\n\\n\\n<p>This development simplifies International
Paper&#8217;s plan to acquire DS Smith Plc, a deal that is expected to close
in the fourth quarter. If Suzano&#8217;s acquisition had been successful,
the world&#8217;s largest pulp producer would have had the chance to expand
internationally and diversify into the more stable packaging sector. However,
there were investor concerns about Suzano&#8217;s ability to maintain its
investment grade if it took on additional debt for the deal.</p>\\n\\n\\n\\n<p><em>Read
Next: <a href=\\\"https://www.benzinga.com/markets/equities/24/06/39493523/rivian-lucid-micron-technology-novo-nordisk-tesla-why-these-5-stocks-are-on-investors-radars-tod?itm_source=parsely-api\\\"
target=\\\"_blank\\\" rel=\\\"noreferrer noopener\\\">RIVN, LCID, MU, NVO,
TSLA: Top 5 Trending Stocks Today</a></em></p>\\n\\n\\n\\n<p><em>Image via
Shutterstock</em></p>\\n\\n\\n\\n<p>This story was generated using <a href=\\\"https://www.benzinga.com/author/benzinga-neuro\\\">Benzinga
Neuro</a> and edited by <u><a href=\\\"https://www.benzinga.com/topic/Pooja-Rajkumari\\\"
target=\\\"_blank\\\" rel=\\\"noreferrer noopener\\\">Pooja Rajkumari</a></u></p>\",\"url\":\"https://www.benzinga.com/markets/equities/24/06/39519611/international-paper-shares-dip-15-in-pre-market-after-suzano-drops-acquisition-pursuit\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2024/06/27/Down-Crisis-Financial-Business-Market-Gr.jpeg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2024/06/27/Down-Crisis-Financial-Business-Market-Gr.jpeg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2024/06/27/Down-Crisis-Financial-Business-Market-Gr.jpeg\"}],\"channels\":[{\"name\":\"Equities\"},{\"name\":\"M&A\"},{\"name\":\"News\"},{\"name\":\"Pre-Market
Outlook\"},{\"name\":\"Markets\"},{\"name\":\"General\"}],\"stocks\":[{\"name\":\"IP\"},{\"name\":\"SUZ\"}],\"tags\":[{\"name\":\"Pooja
Rajkumari\"},{\"name\":\"Stories That Matter\"}]},{\"id\":39519608,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:02:54 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:02:54 -0400\",\"title\":\"Wolfe Research Downgrades Agilent
Technologies to Peer Perform\",\"teaser\":\"Wolfe Research analyst Doug Schenkel
\ downgrades Agilent Technologies (NYSE:A) from Outperform to Peer Perform.\",\"body\":\"Wolfe
Research analyst Doug Schenkel downgrades Agilent Technologies (NYSE:<a
class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/A#NYSE\\\">A</a>)
from Outperform to Peer Perform.\",\"url\":\"https://www.benzinga.com/news/24/06/39519608/wolfe-research-downgrades-agilent-technologies-to-peer-perform\",\"image\":[],\"channels\":[{\"name\":\"News\"},{\"name\":\"Downgrades\"},{\"name\":\"Analyst
Ratings\"}],\"stocks\":[{\"name\":\"A\"}],\"tags\":[]},{\"id\":39519607,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:02:45 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:02:45 -0400\",\"title\":\"Acuity Brands Q3 2024 Adj EPS $4.15
Beats $4.13 Estimate\",\"teaser\":\"\",\"body\":\"\",\"url\":\"https://www.benzinga.com/news/earnings/24/06/39519607/acuity-brands-q3-2024-adj-eps-4-15-beats-4-13-estimate\",\"image\":[],\"channels\":[{\"name\":\"Earnings\"},{\"name\":\"Earnings
Beats\"},{\"name\":\"Earnings Misses\"},{\"name\":\"News\"}],\"stocks\":[{\"name\":\"AYI\"}],\"tags\":[]},{\"id\":39519592,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:02:29 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:02:30 -0400\",\"title\":\"RH Chairman & CEO Gary Friedman Buys
$10M Of RH Stock At Average Price Per Share Of $216.10 Representing 46,274
Shares\",\"teaser\":\"With this purchase, Friedman beneficially owns as of
June 26, 2024, 5.05 million shares, or\_25.1%\_of the outstanding shares of
RH common stock, based on 18.44 million shares outstanding as of June 7, 2024,
as\",\"body\":\"<p>With this purchase, Friedman beneficially owns as of June
26, 2024, 5.05 million shares, or&nbsp;25.1%&nbsp;of the outstanding shares
of RH common stock, based on 18.44 million shares outstanding as of June 7,
2024, as reported in RH&#39;s&nbsp;first quarter&nbsp;fiscal 2024 Form 10-Q.
This represents an increase in Friedman&#39;s beneficial ownership by&nbsp;0.2%.&nbsp;</p>\",\"url\":\"https://www.benzinga.com/news/24/06/39519592/rh-chairman-ceo-gary-friedman-buys-10m-of-rh-stock-at-average-price-per-share-of-216-10-representing\",\"image\":[],\"channels\":[{\"name\":\"News\"}],\"stocks\":[{\"name\":\"RH\"}],\"tags\":[]},{\"id\":39519584,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 06:01:53 -0400\",\"updated\":\"Thu,
27 Jun 2024 06:01:53 -0400\",\"title\":\"Acuity Brands Q3 2024 GAAP EPS $3.62
Misses $3.68 Estimate, Sales $968.100M Miss $1.009B Estimate\",\"teaser\":\"\",\"body\":\"\",\"url\":\"https://www.benzinga.com/news/earnings/24/06/39519584/acuity-brands-q3-2024-gaap-eps-3-62-misses-3-68-estimate-sales-968-100m-miss-1-009b-estimate\",\"image\":[],\"channels\":[{\"name\":\"Earnings\"},{\"name\":\"Earnings
Misses\"},{\"name\":\"News\"}],\"stocks\":[{\"name\":\"AYI\"}],\"tags\":[]},{\"id\":39519493,\"author\":\"Benzinga
Neuro\",\"created\":\"Thu, 27 Jun 2024 05:58:54 -0400\",\"updated\":\"Thu,
27 Jun 2024 05:58:55 -0400\",\"title\":\"Morgan Stanley To Roll Out OpenAI-Powered
Assistant For Financial Advisors: 'Quality And Depth Of The Notes Are Just
Significantly Better'\",\"teaser\":\"Morgan Stanley is set to launch an AI-powered
assistant that will revolutionize the workflow of its financial advisors.\",\"body\":\"<p><strong>Morgan
Stanley</strong> (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/MS#NYSE\\\">MS</a>)
is set to launch an AI-powered assistant to enhance the workflow of its financial
advisors.</p>\\n\\n\\n\\n<p><strong>What Happened</strong>: The AI assistant,
named <strong>Debrief</strong>, will automate the note-taking process during
client meetings, creating draft emails and summaries of the discussions, CNBC
<a href=\\\"https://www.cnbc.com/2024/06/26/morgan-stanley-openai-powered-assistant-for-wealth-advisors.html\\\">reported</a>
on Thursday. The program, built using <strong>Microsoft Corp.-backed OpenAI&#8217;s</strong>
GPT-4, will be rolled out to the firm&#8217;s approximately 15,000 advisors
by early July.</p>\\n\\n\\n\\n<p>\\\"What we're finding is that the quality
and depth of the notes are just significantly better,\\\" <strong>Jeff McMillan</strong>,
Morgan Stanley&#8217;s head of firmwide artificial intelligence said. \\\"The
truth is, this does a better job of taking notes than the average human.\\\"</p>\\n\\n\\n\\n<p>The
AI assistant will also free up valuable time for advisors, potentially leading
to increased client engagement and business growth.</p>\\n\\n\\n\\n<p>Debrief
will initially be used in client meetings, with future versions allowing advisors
to use the program on corporate devices during in-person meetings. Clients
must consent to be recorded each time Debrief is used.</p>\\n\\n\\n\\n<p>\\\"As
a financial adviser I'm doing four, five or six meetings a day,\\\" said <strong>Don
Whitehead</strong>, a Houston-based advisor who's been testing the software.
By \\\"having the note-taking service built in through AI, you can really
be invested in the meeting, you're actually a lot more present.\\\"</p>\\n\\n\\n\\n<p><em>See
Also: <a href=\\\"https://www.benzinga.com/news/24/06/39450621/mark-cuban-falls-victim-to-this-common-account-takeover-tactic-by-hackers-and-loses-access-to-his-gm?itm_source=parsely-api\\\"
target=\\\"_blank\\\" rel=\\\"noreferrer noopener\\\">Mark Cuban Falls Victim
To This Common Account Takeover Tactic By Hackers And Loses Access To His
Gmail: Here&#8217;s How You Can Avoid It</a></em></p>\\n\\n\\n\\n<p><strong>Why
It Matters</strong>: This move by Morgan Stanley is in line with the increasing
adoption of AI in the financial sector. Earlier in May, a PricewaterhouseCoopers
report <a href=\\\"https://www.benzinga.com/news/24/05/38931261/pwc-report-shows-ai-driven-sectors-surging-in-productivity-signaling-economic-upswing\\\">highlighted
a significant surge in productivity</a> in AI-driven sectors, including professional
and financial services. This growth was nearly five times faster than in other
sectors.</p>\\n\\n\\n\\n<p>Moreover, OpenAI&#8217;s ChatGPT Enterprise has
been gaining traction in the industry, with PwC becoming <a href=\\\"https://www.benzinga.com/news/24/05/39058054/big-four-accounting-firm-becomes-openais-largest-chatgpt-enterprise-customer\\\">its
largest customer</a> in May. A study by the University of Chicago also found
that OpenAI&#8217;s GPT-4 had a relative advantage over humans in <a href=\\\"https://www.benzinga.com/news/24/05/39055523/openais-chatgpt-4-has-a-relative-advantage-over-humans-in-financial-analysis-study-finds-even-withou\\\">financial
analysis and forecasting</a>, further underlining the potential of AI in the
financial sector.</p>\\n\\n\\n\\n<p>This development comes at a time when
the financial services industry is increasingly relying on AI and machine
learning technologies to <a href=\\\"https://www.benzinga.com/partner/general/24/04/38278223/the-future-of-finance-how-ai-and-machine-learning-are-transforming-financial-call-center-operatio\\\">enhance
customer experiences and operational efficiency</a>. The integration of AI
in financial services is seen as crucial for adapting to remote working conditions
and meeting evolving consumer needs.</p>\\n\\n\\n\\n<p><em>Read Next: <a href=\\\"https://www.benzinga.com/general/education/24/06/39466991/michael-saylor-2012-apple-stock-prediction-goes-viral-heres-what-bitcoin-bull-got-right-whoever?itm_source=parsely-api\\\"
target=\\\"_blank\\\" rel=\\\"noreferrer noopener\\\">Michael Saylor 2012
Apple Stock Prediction Goes Viral, Here's What Bitcoin Bull Got Right: \u2018Whoever
Is Selling That Stock Must Be A Moron&#8217;</a></em></p>\\n\\n\\n\\n<p>Image
Via Shutterstock </p>\\n\\n\\n\\n<p>This story was generated using <a href=\\\"https://www.benzinga.com/author/benzinga-neuro\\\">Benzinga
Neuro</a> and edited by <u><a href=\\\"https://www.benzinga.com/topic/Kaustubh-Bagalkote\\\"
target=\\\"_blank\\\" rel=\\\"noreferrer noopener\\\">Kaustubh Bagalkote</a></u></p>\",\"url\":\"https://www.benzinga.com/news/24/06/39519493/morgan-stanley-to-roll-out-openai-powered-assistant-for-financial-advisors-quality-and-depth-of-the\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2024/06/27/morgan-stanley-shutter3.jpeg\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2024/06/27/morgan-stanley-shutter3.jpeg\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2024/06/27/morgan-stanley-shutter3.jpeg\"}],\"channels\":[{\"name\":\"News\"},{\"name\":\"Global\"},{\"name\":\"Markets\"},{\"name\":\"Tech\"}],\"stocks\":[{\"name\":\"MS\"}],\"tags\":[{\"name\":\"artificial
intelligence\"},{\"name\":\"ChatGPT\"},{\"name\":\"Kaustubh Bagalkote\"},{\"name\":\"Morgan
Stanley\"},{\"name\":\"OpenAi\"}]},{\"id\":39519482,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 05:56:34 -0400\",\"updated\":\"Thu,
27 Jun 2024 05:56:35 -0400\",\"title\":\"Nokia Inks Deal With The French State
Regarding The Sale Of Submarine Networks Business ASN For An Enterprise Value
Of \u20AC350M\",\"teaser\":\"Nokia will retain a 20% shareholding with board
representation to ensure a smooth transition until targeted exit, at which
point the French State would acquire Nokia\u2019s remaining interest.\\n\\n\_\\n\\nThe
sale is\",\"body\":\"<div>Nokia will retain a 20% shareholding with board
representation to ensure a smooth transition until targeted exit, at which
point the French State would acquire Nokia&rsquo;s remaining interest.</div>\\n\\n<div>&nbsp;</div>\\n\\n<div>The
sale is expected to close at the end of 2024 or beginning of 2025, subject
to formal consultation of ASN&rsquo;s French Works Council and other customary
closing conditions and regulatory approvals.</div>\",\"url\":\"https://www.benzinga.com/news/24/06/39519482/nokia-inks-deal-with-the-french-state-regarding-the-sale-of-submarine-networks-business-asn-for-an-e\",\"image\":[],\"channels\":[{\"name\":\"News\"},{\"name\":\"Asset
Sales\"}],\"stocks\":[{\"name\":\"NOK\"}],\"tags\":[]},{\"id\":39519479,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 05:55:53 -0400\",\"updated\":\"Thu,
27 Jun 2024 05:55:54 -0400\",\"title\":\"Stifel Initiates Coverage On Jasper
Therapeutics with Buy Rating, Announces Price Target of $86\",\"teaser\":\"Stifel
\ analyst Ben Burnett initiates coverage on Jasper Therapeutics (NASDAQ:JSPR)
with a Buy rating and announces Price Target of $86.\",\"body\":\"Stifel analyst
Ben Burnett initiates coverage on Jasper Therapeutics (NASDAQ:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/JSPR#NASDAQ\\\">JSPR</a>) with a Buy
rating and announces Price Target of $86.\",\"url\":\"https://www.benzinga.com/news/24/06/39519479/stifel-initiates-coverage-on-jasper-therapeutics-with-buy-rating-announces-price-target-of-86\",\"image\":[],\"channels\":[{\"name\":\"News\"},{\"name\":\"Price
Target\"},{\"name\":\"Initiation\"},{\"name\":\"Analyst Ratings\"}],\"stocks\":[{\"name\":\"JSPR\"}],\"tags\":[]},{\"id\":39519471,\"author\":\"Benzinga
Newsdesk\",\"created\":\"Thu, 27 Jun 2024 05:54:22 -0400\",\"updated\":\"Thu,
27 Jun 2024 05:54:22 -0400\",\"title\":\"Roth MKM Initiates Coverage On Denison
Mines with Buy Rating, Announces Price Target of $2.6\",\"teaser\":\"Roth
MKM analyst Joe Reagor initiates coverage on Denison Mines (AMEX:DNN) with
a Buy rating and announces Price Target of $2.6.\",\"body\":\"Roth MKM analyst
Joe Reagor initiates coverage on Denison Mines (AMEX:<a class=\\\"ticker\\\"
href=\\\"https://www.benzinga.com/stock/DNN#AMEX\\\">DNN</a>) with a Buy rating
and announces Price Target of $2.6.\",\"url\":\"https://www.benzinga.com/news/24/06/39519471/roth-mkm-initiates-coverage-on-denison-mines-with-buy-rating-announces-price-target-of-2-6\",\"image\":[],\"channels\":[{\"name\":\"News\"},{\"name\":\"Price
Target\"},{\"name\":\"Initiation\"},{\"name\":\"Analyst Ratings\"}],\"stocks\":[{\"name\":\"DNN\"}],\"tags\":[]},{\"id\":39519449,\"author\":\"Lekha
Gupta\",\"created\":\"Thu, 27 Jun 2024 05:52:52 -0400\",\"updated\":\"Thu,
27 Jun 2024 05:52:53 -0400\",\"title\":\"Is BP Bailing on Green? Reportedly
Shifts Gears From Renewables To Oil And Gas\",\"teaser\":\"BP reportedly shifts
focus back to oil and gas, halting new offshore wind projects and implementing
hiring freeze, due to investor dissatisfaction with energy transition strategy.\",\"body\":\"<p><strong>BP
p.l.c.</strong> (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/BP#NYSE\\\">BP</a>)
reportedly implemented a hiring freeze and halted new offshore wind projects,
shifting focus back to oil and gas due to investor dissatisfaction with the
company&#8217;s energy transition strategy.</p>\\n\\n\\n\\n<p>These moves
are part of CEO Murray Auchincloss&#8217; strategy to reduce investments in
high-budget, low-carbon projects, especially offshore wind, which are not
expected to generate immediate cash flow, reported Reuters.</p>\\n\\n\\n\\n<p>These
actions signify a major departure from the approach of Auchincloss&#8217;
predecessor, Bernard Looney, who aimed to <a target=\\\"_blank\\\" href=\\\"https://www.benzinga.com/markets/equities/23/09/34526407/oil-giant-bps-ceo-resigns-amid-investigation-on-personal-conduct\\\"
rel=\\\"noreferrer noopener\\\">transition away from fossil fuels quickly</a>.</p>\\n\\n\\n\\n<p>The
shift has affected BP&#8217;s stock performance, as returns from renewable
energy projects have declined, while profits from oil and gas have soared
in the aftermath of the COVID-19 pandemic and Russia&#8217;s invasion of Ukraine.</p>\\n\\n\\n\\n<p>Auchincloss
and Chief Financial Officer Kate Thomson have prioritized investing in and
acquiring new oil and gas assets, with a particular focus on the Gulf of Mexico
and U.S. onshore shale basins, where BP already has significant operations,
the report added, citing people familiar with the matter.</p>\\n\\n\\n\\n<p><strong><em>Related</em></strong><em>:
<a target=\\\"_blank\\\" href=\\\"https://www.benzinga.com/markets/equities/24/06/39247864/bp-tightens-workplace-relationship-rules-after-ceo-firing-report\\\"
rel=\\\"noreferrer noopener\\\">BP Tightens Workplace Relationship Rules After
CEO Firing: Report</a></em></p>\\n\\n\\n\\n<p>BP said in a statement to Reuters
that Auchincloss introduced six priorities &#8220;to deliver as a simpler,
more focused and higher value company.&#8221;</p>\\n\\n\\n\\n<p>&#8220;The
actions we are taking are part of delivering this &#8211; and of course are
all in service of our aim of growing the value of BP.&#8221;</p>\\n\\n\\n\\n<p>As
per the report, BP has reassigned dozens of employees, who were previously
focused on identifying new renewable opportunities, to existing projects such
as offshore wind initiatives in Britain and Germany.</p>\\n\\n\\n\\n<p><strong><em>Read</em></strong><em>:
<a target=\\\"_blank\\\" href=\\\"https://www.benzinga.com/markets/equities/24/06/39416447/bp-doubles-down-on-biofuels-buys-out-bunge-bioenergia-for-1-4b\\\"
rel=\\\"noreferrer noopener\\\">BP Doubles Down On Biofuels: Buys Out Bunge
Bioenergia for $1.4B</a></em></p>\\n\\n\\n\\n<p>This week, BP said it plans
to expand and diversify its biofuel operations in Brazil after acquiring <a
target=\\\"_blank\\\" href=\\\"https://www.benzinga.com/markets/equities/24/06/39452342/bp-eyes-biofuel-dominance-in-brazil-with-expansion-plans-report\\\"
rel=\\\"noreferrer noopener\\\">stake in BP Bunge Bioenergia</a>.</p>\\n\\n\\n\\n<p>Investors
can gain exposure to the stock via&nbsp;<strong>Direxion Hydrogen ETF&nbsp;</strong>(ARCA:&nbsp;HJEN)&nbsp;and&nbsp;<strong>First
Trust Exchange-Traded Fund IV FT Energy Income Partners Strategy ETF</strong>
(ARCA:&nbsp;EIPX).</p>\\n\\n\\n\\n<p><strong>Price Action</strong>: BP shares
closed lower by 1.00% at $35.72 on Wednesday.</p>\\n\\n\\n\\n<p><strong><em>Disclaimer:</em></strong><em>
This content was partially produced with the help of AI tools and was reviewed
and published by Benzinga editors.</em></p>\",\"url\":\"https://www.benzinga.com/markets/equities/24/06/39519449/is-bp-bailing-on-green-reportedly-shifts-gears-from-renewables-to-oil-and-gas\",\"image\":[{\"size\":\"thumb\",\"url\":\"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2024/06/27/BP-Logo.png\"},{\"size\":\"small\",\"url\":\"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2024/06/27/BP-Logo.png\"},{\"size\":\"large\",\"url\":\"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2024/06/27/BP-Logo.png\"}],\"channels\":[{\"name\":\"Equities\"},{\"name\":\"Large
Cap\"},{\"name\":\"News\"},{\"name\":\"Media\"}],\"stocks\":[{\"name\":\"BP\"}],\"tags\":[{\"name\":\"AI
Generated\"},{\"name\":\"Briefs\"},{\"name\":\"Eurasia\"},{\"name\":\"Stories
That Matter\"}]},{\"id\":39519448,\"author\":\"Benzinga Newsdesk\",\"created\":\"Thu,
27 Jun 2024 05:52:51 -0400\",\"updated\":\"Thu, 27 Jun 2024 05:52:51 -0400\",\"title\":\"Piper
Sandler Initiates Coverage On Dynatrace with Neutral Rating, Announces Price
Target of $50\",\"teaser\":\"Piper Sandler analyst Rob Owens initiates
coverage on Dynatrace (NYSE:DT) with a Neutral rating and announces Price
Target of $50.\",\"body\":\"Piper Sandler analyst Rob Owens initiates coverage
on Dynatrace (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/DT#NYSE\\\">DT</a>)
with a Neutral rating and announces Price Target of $50.\",\"url\":\"https://www.benzinga.com/news/24/06/39519448/piper-sandler-initiates-coverage-on-dynatrace-with-neutral-rating-announces-price-target-of-50\",\"image\":[],\"channels\":[{\"name\":\"News\"},{\"name\":\"Price
Target\"},{\"name\":\"Initiation\"},{\"name\":\"Analyst Ratings\"}],\"stocks\":[{\"name\":\"DT\"}],\"tags\":[]}]\n"
string: !!binary |
H4sIAAAAAAAA/62SQWvbQBCF7/0Vgwg9eSxbtuPExwQKPrix60IpwYeRNZa2lrRid1XFDfnvnZVk
Q1sogeSy7DIzb/d7+x6fA5UEi+n8JprPx/NBQLXLtAkWwR2Xv1SZEnzmxiZsj8Eg2Bsmx9IffK15
ANEEtlxBNIpmMJ6KyGI8AxxNRyPpravk1b1OuZylc0U/tAFVJvzEFsgw5LphA2TBZQzbj2uo6jy3
ENP+CAejC6A8R6cKhkylmR3CsvzJ1mljoaCTVK2GmEXAsrWCA7HRlIjkXhcFl47MqdP5xAncZ6QM
rOXKPB/6ZzFZ9l7IPtbJqdvVJpdN5lxlF2HYNM0w7q0aimjYKFWE0Swc3YZnV8PCc2HPhcKFLReS
ReFCixW2XOi50L8Hz1zYcslsj4WChR4LY8YLFsZGXqYKSsXGx538VEZlybmV03NQUuHd/bZcroIX
KYrQ/vhHabPZyDw/+TEvEQQvg0txu/7+d1FEHKVeYucbLxG6nszeFqFoMb1+bYSkd/5PhNZGTDNq
T2AzMbpLkTOU+M/3Xvo4HZysd6sHuKdKOcqhrlJpkQj4mLX2dKlYkTmygzWbgzYFOA0Ptav6E5UJ
GFJWxpSzUMmtchWZVCba6avoNvIzV5PxzbvlSSwOqzMkdpBtpHpI7CCxhcS40LjvIPEM2WXOQ3ZR
K1pI7LHQadQC+aY8rb8s/xeZ3Yff84coYHgEAAA=
headers:
Cache-Control:
- max-age=3, must-revalidate
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Thu, 27 Jun 2024 10:11:56 GMT
Etag:
- 0s89sQhzSjYCAljvgqhRmS3_vrTWj3RTe3jnQPfA0cM=
- Tue, 23 Sep 2025 18:49:31 GMT
RateLimit-Limit:
- '250'
RateLimit-Remaining:
- '249'
RateLimit-Reset:
- '1'
Transfer-Encoding:
- chunked
Vary:
- Accept,Accept-Encoding
Via:
- kong/3.5.0, 1.1 e94fc0df161940e9096df2b4fe60d4f8.cloudfront.net (CloudFront)
- kong/3.5.0, 1.1 74530a379e4f3e81aa5adba81b20ad86.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- HvN937tz80oUTJRSEBHZwEJUR19_RcgBLvoG9YMVRWFSB-fvgvjR6Q==
- HoO7AupeJ9jMUn6YbWb-H51ajArBO6u7MqPZShq1-CVQ_R1Sd_sfgw==
X-Amz-Cf-Pop:
- AMS58-P1
- ORD56-P3
X-Bz-Request-Id:
- a9958c17-e64e-4cca-8325-d1b8cf90e19d#14564991
- a924f550-b92b-46cb-b067-415c25e20615#74195639
X-Cache:
- Miss from cloudfront
X-Kong-Proxy-Latency:
- '0'
- '2'
X-Kong-Request-Id:
- 8d0e3c06517321657c7a4b3abf927d60
- 66addf15fd5a6530610c700891d6455c
X-Kong-Upstream-Latency:
- '47'
- '83'
X-Powered-By:
- Pentium
X-RateLimit-Limit-Minute:
- '5000'
X-RateLimit-Limit-Second:
- '250'
X-RateLimit-Remaining-Minute:
- '4999'
X-RateLimit-Remaining-Second:
- '249'
status:
code: 200
message: OK

View File

@@ -26,7 +26,7 @@ def vcr_config():
@pytest.mark.record_http
def test_benzinga_world_news_fetcher(credentials=test_credentials):
"""Test the world news fetcher."""
params = {"limit": 20}
params = {"limit": 2}
fetcher = BenzingaWorldNewsFetcher()
result = fetcher.test(params, credentials)

View File

@@ -26,6 +26,9 @@ class FinvizKeyMetricsQueryParams(KeyMetricsQueryParams):
class FinvizKeyMetricsData(KeyMetricsData):
"""Finviz Key Metrics Data."""
pe_ratio: Optional[float] = Field(
default=None, description="Price-to-earnings ratio (TTM)."
)
foward_pe: Optional[float] = Field(
default=None, description="Forward price-to-earnings ratio (forward P/E)"
)

View File

@@ -8,12 +8,12 @@ from openbb_fmp.models.balance_sheet_growth import FMPBalanceSheetGrowthFetcher
from openbb_fmp.models.calendar_dividend import FMPCalendarDividendFetcher
from openbb_fmp.models.calendar_earnings import FMPCalendarEarningsFetcher
from openbb_fmp.models.calendar_events import FMPCalendarEventsFetcher
from openbb_fmp.models.calendar_ipo import FMPCalendarIpoFetcher
from openbb_fmp.models.calendar_splits import FMPCalendarSplitsFetcher
from openbb_fmp.models.cash_flow import FMPCashFlowStatementFetcher
from openbb_fmp.models.cash_flow_growth import FMPCashFlowStatementGrowthFetcher
from openbb_fmp.models.company_filings import FMPCompanyFilingsFetcher
from openbb_fmp.models.company_news import FMPCompanyNewsFetcher
from openbb_fmp.models.company_overview import FMPCompanyOverviewFetcher
from openbb_fmp.models.crypto_historical import FMPCryptoHistoricalFetcher
from openbb_fmp.models.crypto_search import FMPCryptoSearchFetcher
from openbb_fmp.models.currency_historical import FMPCurrencyHistoricalFetcher
@@ -22,19 +22,19 @@ from openbb_fmp.models.currency_snapshots import FMPCurrencySnapshotsFetcher
from openbb_fmp.models.discovery_filings import FMPDiscoveryFilingsFetcher
from openbb_fmp.models.earnings_call_transcript import FMPEarningsCallTranscriptFetcher
from openbb_fmp.models.economic_calendar import FMPEconomicCalendarFetcher
from openbb_fmp.models.equity_gainers import FMPGainersFetcher
from openbb_fmp.models.equity_historical import FMPEquityHistoricalFetcher
from openbb_fmp.models.equity_losers import FMPLosersFetcher
from openbb_fmp.models.equity_most_active import FMPEquityActiveFetcher
from openbb_fmp.models.equity_ownership import FMPEquityOwnershipFetcher
from openbb_fmp.models.equity_peers import FMPEquityPeersFetcher
from openbb_fmp.models.equity_profile import FMPEquityProfileFetcher
from openbb_fmp.models.equity_quote import FMPEquityQuoteFetcher
from openbb_fmp.models.equity_screener import FMPEquityScreenerFetcher
from openbb_fmp.models.equity_valuation_multiples import (
FMPEquityValuationMultiplesFetcher,
)
from openbb_fmp.models.esg_score import FMPEsgScoreFetcher
from openbb_fmp.models.etf_countries import FMPEtfCountriesFetcher
from openbb_fmp.models.etf_equity_exposure import FMPEtfEquityExposureFetcher
from openbb_fmp.models.etf_holdings import FMPEtfHoldingsFetcher
from openbb_fmp.models.etf_holdings_date import FMPEtfHoldingsDateFetcher
from openbb_fmp.models.etf_info import FMPEtfInfoFetcher
from openbb_fmp.models.etf_search import FMPEtfSearchFetcher
from openbb_fmp.models.etf_sectors import FMPEtfSectorsFetcher
@@ -57,6 +57,7 @@ from openbb_fmp.models.institutional_ownership import FMPInstitutionalOwnershipF
from openbb_fmp.models.key_executives import FMPKeyExecutivesFetcher
from openbb_fmp.models.key_metrics import FMPKeyMetricsFetcher
from openbb_fmp.models.market_snapshots import FMPMarketSnapshotsFetcher
from openbb_fmp.models.nport_disclosure import FMPNportDisclosureFetcher
from openbb_fmp.models.price_performance import FMPPricePerformanceFetcher
from openbb_fmp.models.price_target import FMPPriceTargetFetcher
from openbb_fmp.models.price_target_consensus import FMPPriceTargetConsensusFetcher
@@ -82,12 +83,12 @@ stock market information (news, currencies, and stock prices).""",
"CalendarDividend": FMPCalendarDividendFetcher,
"CalendarEarnings": FMPCalendarEarningsFetcher,
"CalendarEvents": FMPCalendarEventsFetcher,
"CalendarIpo": FMPCalendarIpoFetcher,
"CalendarSplits": FMPCalendarSplitsFetcher,
"CashFlowStatement": FMPCashFlowStatementFetcher,
"CashFlowStatementGrowth": FMPCashFlowStatementGrowthFetcher,
"CompanyFilings": FMPCompanyFilingsFetcher,
"CompanyNews": FMPCompanyNewsFetcher,
"CompanyOverview": FMPCompanyOverviewFetcher,
"CryptoHistorical": FMPCryptoHistoricalFetcher,
"CryptoSearch": FMPCryptoSearchFetcher,
"CurrencyHistorical": FMPCurrencyHistoricalFetcher,
@@ -96,17 +97,18 @@ stock market information (news, currencies, and stock prices).""",
"DiscoveryFilings": FMPDiscoveryFilingsFetcher,
"EarningsCallTranscript": FMPEarningsCallTranscriptFetcher,
"EconomicCalendar": FMPEconomicCalendarFetcher,
"EquityActive": FMPEquityActiveFetcher,
"EquityHistorical": FMPEquityHistoricalFetcher,
"EquityOwnership": FMPEquityOwnershipFetcher,
"EquityPeers": FMPEquityPeersFetcher,
"EquityInfo": FMPEquityProfileFetcher,
"EquityGainers": FMPGainersFetcher,
"EquityLosers": FMPLosersFetcher,
"EquityQuote": FMPEquityQuoteFetcher,
"EquityScreener": FMPEquityScreenerFetcher,
"EquityValuationMultiples": FMPEquityValuationMultiplesFetcher,
"EsgScore": FMPEsgScoreFetcher,
"EtfCountries": FMPEtfCountriesFetcher,
"EtfEquityExposure": FMPEtfEquityExposureFetcher,
"EtfHoldings": FMPEtfHoldingsFetcher,
"EtfHoldingsDate": FMPEtfHoldingsDateFetcher,
"EtfInfo": FMPEtfInfoFetcher,
"EtfPricePerformance": FMPPricePerformanceFetcher,
"EtfSearch": FMPEtfSearchFetcher,
@@ -129,6 +131,7 @@ stock market information (news, currencies, and stock prices).""",
"KeyExecutives": FMPKeyExecutivesFetcher,
"KeyMetrics": FMPKeyMetricsFetcher,
"MarketSnapshots": FMPMarketSnapshotsFetcher,
"NportDisclosure": FMPNportDisclosureFetcher,
"PricePerformance": FMPPricePerformanceFetcher,
"PriceTarget": FMPPriceTargetFetcher,
"PriceTargetConsensus": FMPPriceTargetConsensusFetcher,

View File

@@ -1,8 +1,8 @@
"""FMP Analyst Estimates Model."""
import asyncio
from typing import Any, Dict, List, Literal, Optional
from warnings import warn
# pylint: disable=unused-argument
from typing import Any, Literal, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.analyst_estimates import (
@@ -11,8 +11,6 @@ from openbb_core.provider.standard_models.analyst_estimates import (
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request
from openbb_fmp.utils.helpers import create_url, response_callback
from pydantic import Field
@@ -30,48 +28,83 @@ class FMPAnalystEstimatesQueryParams(AnalystEstimatesQueryParams):
limit: Optional[int] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("limit", "")
)
page: Optional[int] = Field(
default=None, description="Page number for paginated results. Used with limit."
)
class FMPAnalystEstimatesData(AnalystEstimatesData):
"""FMP Analyst Estimates Data."""
__alias_dict__ = {
"estimated_revenue_low": "revenueLow",
"estimated_revenue_high": "revenueHigh",
"estimated_revenue_avg": "revenueAvg",
"estimated_sga_expense_low": "sgaExpenseLow",
"estimated_sga_expense_high": "sgaExpenseHigh",
"estimated_sga_expense_avg": "sgaExpenseAvg",
"estimated_ebitda_low": "ebitdaLow",
"estimated_ebitda_high": "ebitdaHigh",
"estimated_ebitda_avg": "ebitdaAvg",
"estimated_ebit_low": "ebitLow",
"estimated_ebit_high": "ebitHigh",
"estimated_ebit_avg": "ebitAvg",
"estimated_net_income_low": "netIncomeLow",
"estimated_net_income_high": "netIncomeHigh",
"estimated_net_income_avg": "netIncomeAvg",
"estimated_eps_low": "epsLow",
"estimated_eps_high": "epsHigh",
"estimated_eps_avg": "epsAvg",
"number_analysts_estimated_revenue": "numAnalystsRevenue",
"number_analysts_eps": "numAnalystsEps",
}
class FMPAnalystEstimatesFetcher(
Fetcher[
FMPAnalystEstimatesQueryParams,
List[FMPAnalystEstimatesData],
list[FMPAnalystEstimatesData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPAnalystEstimatesQueryParams:
def transform_query(params: dict[str, Any]) -> FMPAnalystEstimatesQueryParams:
"""Transform the query params."""
return FMPAnalystEstimatesQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPAnalystEstimatesQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
import asyncio # noqa
import warnings
from openbb_core.provider.utils.helpers import amake_request
from openbb_fmp.utils.helpers import response_callback
api_key = credentials.get("fmp_api_key") if credentials else ""
symbols = query.symbol.split(",") # type: ignore
results: List[dict] = []
results: list[dict] = []
async def get_one(symbol):
"""Get data for one symbol."""
url = create_url(
3, f"analyst-estimates/{symbol}", api_key, query, ["symbol"]
url = (
"https://financialmodelingprep.com/stable/analyst-estimates?"
+ f"symbol={symbol}&period={query.period}"
+ f"&page={query.page if query.page else 0}&limit={query.limit if query.limit else 1000}"
+ f"&apikey={api_key}"
)
result = await amake_request(
url, response_callback=response_callback, **kwargs
)
if not result or len(result) == 0:
warn(f"Symbol Error: No data found for {symbol}")
warnings.warn(f"Symbol Error: No data found for {symbol}")
if result:
results.extend(result)
@@ -84,7 +117,7 @@ class FMPAnalystEstimatesFetcher(
@staticmethod
def transform_data(
query: FMPAnalystEstimatesQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPAnalystEstimatesData]:
query: FMPAnalystEstimatesQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPAnalystEstimatesData]:
"""Return the transformed data."""
return [FMPAnalystEstimatesData.model_validate(d) for d in data]

View File

@@ -1,69 +1,58 @@
"""FMP Available Indices Model."""
from typing import Any, Dict, List, Optional
# pylint: disable=unused-argument
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.available_indices import (
AvailableIndicesData,
AvailableIndicesQueryParams,
)
from openbb_fmp.utils.helpers import get_data_many
from pydantic import Field
class FMPAvailableIndicesQueryParams(AvailableIndicesQueryParams):
"""FMP Available Indices Query.
Source: https://site.financialmodelingprep.com/developer/docs/#Historical-stock-index-prices
Source: https://site.financialmodelingprep.com/developer/docs#indexes-list
"""
class FMPAvailableIndicesData(AvailableIndicesData):
"""FMP Available Indices Data."""
__alias_dict__ = {
"stock_exchange": "stockExchange",
"exchange_short_name": "exchangeShortName",
}
stock_exchange: str = Field(
description="Stock exchange where the index is listed.",
)
exchange_short_name: str = Field(
description="Short name of the stock exchange where the index is listed.",
)
class FMPAvailableIndicesFetcher(
Fetcher[
FMPAvailableIndicesQueryParams,
List[FMPAvailableIndicesData],
list[FMPAvailableIndicesData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
"""FMP Available Indices Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPAvailableIndicesQueryParams:
def transform_query(params: dict[str, Any]) -> FMPAvailableIndicesQueryParams:
"""Transform the query params."""
return FMPAvailableIndicesQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPAvailableIndicesQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
api_key = credentials.get("fmp_api_key") if credentials else ""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
base_url = "https://financialmodelingprep.com/api/v3"
url = f"{base_url}/symbol/available-indexes?apikey={api_key}"
api_key = credentials.get("fmp_api_key") if credentials else ""
url = f"https://financialmodelingprep.com/stable/index-list?apikey={api_key}"
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPAvailableIndicesQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPAvailableIndicesData]:
query: FMPAvailableIndicesQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPAvailableIndicesData]:
"""Return the transformed data."""
return [FMPAvailableIndicesData.model_validate(d) for d in data]

View File

@@ -1,11 +1,12 @@
"""FMP Balance Sheet Model."""
# pylint: disable=unused-argument
from datetime import (
date as dateType,
datetime,
)
from typing import Any, Dict, List, Literal, Optional
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.balance_sheet import (
@@ -13,23 +14,17 @@ from openbb_core.provider.standard_models.balance_sheet import (
BalanceSheetQueryParams,
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_fmp.utils.helpers import create_url, get_data_many
from pydantic import Field, model_validator
from openbb_fmp.utils.definitions import FinancialStatementPeriods
from pydantic import Field
class FMPBalanceSheetQueryParams(BalanceSheetQueryParams):
"""FMP Balance Sheet Query.
Source: https://financialmodelingprep.com/developer/docs/#Balance-Sheet
Source: https://site.financialmodelingprep.com/developer/docs#balance-sheet-statement
"""
__json_schema_extra__ = {
"period": {
"choices": ["annual", "quarter"],
}
}
period: Literal["annual", "quarter"] = Field(
period: FinancialStatementPeriods = Field(
default="annual",
description=QUERY_DESCRIPTIONS.get("period", ""),
)
@@ -54,6 +49,7 @@ class FMPBalanceSheetData(BalanceSheetData):
"total_current_assets": "totalCurrentAssets",
"plant_property_equipment_net": "propertyPlantEquipmentNet",
"goodwill": "goodwill",
"prepaid_expenses": "prepaids",
"intangible_assets": "intangibleAssets",
"goodwill_and_intangible_assets": "goodwillAndIntangibleAssets",
"long_term_investments": "longTermInvestments",
@@ -81,7 +77,7 @@ class FMPBalanceSheetData(BalanceSheetData):
"retained_earnings": "retainedEarnings",
"accumulated_other_comprehensive_income": "accumulatedOtherComprehensiveIncomeLoss",
"other_shareholders_equity": "otherStockholdersEquity",
"other_total_shareholders_equity": "othertotalStockholdersEquity",
"other_total_shareholders_equity": "otherTotalStockholdersEquity",
"total_common_equity": "totalStockholdersEquity",
"total_equity_non_controlling_interests": "totalEquity",
"total_liabilities_and_shareholders_equity": "totalLiabilitiesAndStockholdersEquity",
@@ -90,8 +86,6 @@ class FMPBalanceSheetData(BalanceSheetData):
"total_investments": "totalInvestments",
"total_debt": "totalDebt",
"net_debt": "netDebt",
"link": "link",
"final_link": "finalLink",
}
filing_date: Optional[dateType] = Field(
@@ -102,244 +96,282 @@ class FMPBalanceSheetData(BalanceSheetData):
default=None,
description="The date and time when the filing was accepted.",
)
cik: Optional[str] = Field(
default=None,
description="The Central Index Key (CIK) assigned by the SEC, if applicable.",
)
symbol: Optional[str] = Field(
default=None,
description="The stock ticker symbol.",
)
reported_currency: Optional[str] = Field(
default=None,
description="The currency in which the balance sheet was reported.",
)
cash_and_cash_equivalents: Optional[float] = Field(
cash_and_cash_equivalents: Optional[int] = Field(
default=None,
description="Cash and cash equivalents.",
)
short_term_investments: Optional[float] = Field(
short_term_investments: Optional[int] = Field(
default=None,
description="Short term investments.",
)
cash_and_short_term_investments: Optional[float] = Field(
cash_and_short_term_investments: Optional[int] = Field(
default=None,
description="Cash and short term investments.",
)
net_receivables: Optional[float] = Field(
accounts_receivables: Optional[int] = Field(
default=None,
description="Accounts receivables.",
)
other_receivables: Optional[int] = Field(
default=None,
description="Other receivables.",
)
net_receivables: Optional[int] = Field(
default=None,
description="Net receivables.",
)
inventory: Optional[float] = Field(
inventory: Optional[int] = Field(
default=None,
description="Inventory.",
)
other_current_assets: Optional[float] = Field(
other_current_assets: Optional[int] = Field(
default=None,
description="Other current assets.",
)
total_current_assets: Optional[float] = Field(
total_current_assets: Optional[int] = Field(
default=None,
description="Total current assets.",
)
plant_property_equipment_net: Optional[float] = Field(
plant_property_equipment_net: Optional[int] = Field(
default=None,
description="Plant property equipment net.",
)
goodwill: Optional[float] = Field(
goodwill: Optional[int] = Field(
default=None,
description="Goodwill.",
)
intangible_assets: Optional[float] = Field(
intangible_assets: Optional[int] = Field(
default=None,
description="Intangible assets.",
)
goodwill_and_intangible_assets: Optional[float] = Field(
goodwill_and_intangible_assets: Optional[int] = Field(
default=None,
description="Goodwill and intangible assets.",
)
long_term_investments: Optional[float] = Field(
long_term_investments: Optional[int] = Field(
default=None,
description="Long term investments.",
)
tax_assets: Optional[float] = Field(
tax_assets: Optional[int] = Field(
default=None,
description="Tax assets.",
)
other_non_current_assets: Optional[float] = Field(
other_non_current_assets: Optional[int] = Field(
default=None,
description="Other non current assets.",
)
non_current_assets: Optional[float] = Field(
non_current_assets: Optional[int] = Field(
default=None,
description="Total non current assets.",
)
other_assets: Optional[float] = Field(
other_assets: Optional[int] = Field(
default=None,
description="Other assets.",
)
total_assets: Optional[float] = Field(
total_assets: Optional[int] = Field(
default=None,
description="Total assets.",
)
accounts_payable: Optional[float] = Field(
accounts_payable: Optional[int] = Field(
default=None,
description="Accounts payable.",
)
short_term_debt: Optional[float] = Field(
prepaid_expenses: Optional[int] = Field(
default=None,
description="Prepaid expenses.",
)
accrued_expenses: Optional[int] = Field(
default=None,
description="Accrued expenses.",
)
short_term_debt: Optional[int] = Field(
default=None,
description="Short term debt.",
)
tax_payables: Optional[float] = Field(
tax_payables: Optional[int] = Field(
default=None,
description="Tax payables.",
)
current_deferred_revenue: Optional[float] = Field(
current_deferred_revenue: Optional[int] = Field(
default=None,
description="Current deferred revenue.",
)
other_current_liabilities: Optional[float] = Field(
other_current_liabilities: Optional[int] = Field(
default=None,
description="Other current liabilities.",
)
total_current_liabilities: Optional[float] = Field(
other_payables: Optional[int] = Field(
default=None,
description="Other payables.",
)
total_current_liabilities: Optional[int] = Field(
default=None,
description="Total current liabilities.",
)
long_term_debt: Optional[float] = Field(
total_payables: Optional[int] = Field(
default=None,
description="Total payables.",
)
long_term_debt: Optional[int] = Field(
default=None,
description="Long term debt.",
)
deferred_revenue_non_current: Optional[float] = Field(
deferred_revenue_non_current: Optional[int] = Field(
default=None,
description="Non current deferred revenue.",
)
deferred_tax_liabilities_non_current: Optional[float] = Field(
deferred_tax_liabilities_non_current: Optional[int] = Field(
default=None,
description="Deferred tax liabilities non current.",
)
other_non_current_liabilities: Optional[float] = Field(
other_non_current_liabilities: Optional[int] = Field(
default=None,
description="Other non current liabilities.",
)
total_non_current_liabilities: Optional[float] = Field(
total_non_current_liabilities: Optional[int] = Field(
default=None,
description="Total non current liabilities.",
)
other_liabilities: Optional[float] = Field(
capital_lease_obligations_current: Optional[int] = Field(
default=None,
description="Other liabilities.",
description="Current capital lease obligations.",
)
capital_lease_obligations: Optional[float] = Field(
capital_lease_obligations_non_current: Optional[int] = Field(
default=None,
description="Non current capital lease obligations.",
)
capital_lease_obligations: Optional[int] = Field(
default=None,
description="Capital lease obligations.",
)
total_liabilities: Optional[float] = Field(
other_liabilities: Optional[int] = Field(
default=None,
description="Other liabilities.",
)
total_liabilities: Optional[int] = Field(
default=None,
description="Total liabilities.",
)
preferred_stock: Optional[float] = Field(
preferred_stock: Optional[int] = Field(
default=None,
description="Preferred stock.",
)
common_stock: Optional[float] = Field(
common_stock: Optional[int] = Field(
default=None,
description="Common stock.",
)
retained_earnings: Optional[float] = Field(
treasury_stock: Optional[int] = Field(
default=None,
description="Treasury stock.",
)
retained_earnings: Optional[int] = Field(
default=None,
description="Retained earnings.",
)
accumulated_other_comprehensive_income: Optional[float] = Field(
additional_paid_in_capital: Optional[int] = Field(
default=None,
description="Additional paid in capital.",
)
accumulated_other_comprehensive_income: Optional[int] = Field(
default=None,
description="Accumulated other comprehensive income (loss).",
)
other_shareholders_equity: Optional[float] = Field(
other_shareholders_equity: Optional[int] = Field(
default=None,
description="Other shareholders equity.",
)
other_total_shareholders_equity: Optional[float] = Field(
other_total_shareholders_equity: Optional[int] = Field(
default=None,
description="Other total shareholders equity.",
)
total_common_equity: Optional[float] = Field(
total_common_equity: Optional[int] = Field(
default=None,
description="Total common equity.",
)
total_equity_non_controlling_interests: Optional[float] = Field(
total_equity_non_controlling_interests: Optional[int] = Field(
default=None,
description="Total equity non controlling interests.",
)
total_liabilities_and_shareholders_equity: Optional[float] = Field(
total_liabilities_and_shareholders_equity: Optional[int] = Field(
default=None,
description="Total liabilities and shareholders equity.",
)
minority_interest: Optional[float] = Field(
minority_interest: Optional[int] = Field(
default=None,
description="Minority interest.",
)
total_liabilities_and_total_equity: Optional[float] = Field(
total_liabilities_and_total_equity: Optional[int] = Field(
default=None,
description="Total liabilities and total equity.",
)
total_investments: Optional[float] = Field(
total_investments: Optional[int] = Field(
default=None,
description="Total investments.",
)
total_debt: Optional[float] = Field(
total_debt: Optional[int] = Field(
default=None,
description="Total debt.",
)
net_debt: Optional[float] = Field(
net_debt: Optional[int] = Field(
default=None,
description="Net debt.",
)
link: Optional[str] = Field(
default=None,
description="Link to the filing.",
)
final_link: Optional[str] = Field(
default=None,
description="Link to the filing document.",
)
@model_validator(mode="before")
@classmethod
def replace_zero(cls, values): # pylint: disable=no-self-argument
"""Check for zero values and replace with None."""
return (
{k: None if v == 0 else v for k, v in values.items()}
if isinstance(values, dict)
else values
)
class FMPBalanceSheetFetcher(
Fetcher[
FMPBalanceSheetQueryParams,
List[FMPBalanceSheetData],
list[FMPBalanceSheetData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPBalanceSheetQueryParams:
def transform_query(params: dict[str, Any]) -> FMPBalanceSheetQueryParams:
"""Transform the query params."""
return FMPBalanceSheetQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPBalanceSheetQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
api_key = credentials.get("fmp_api_key") if credentials else ""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
url = create_url(
3, f"balance-sheet-statement/{query.symbol}", api_key, query, ["symbol"]
api_key = credentials.get("fmp_api_key") if credentials else ""
base_url = "https://financialmodelingprep.com/stable/balance-sheet-statement"
if query.period == "ttm":
base_url += "-ttm"
url = (
base_url
+ f"?symbol={query.symbol}{'&period=' + query.period if query.period != 'ttm' else ''}"
+ f"&limit={query.limit if query.limit else 5}"
+ f"&apikey={api_key}"
)
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPBalanceSheetQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPBalanceSheetData]:
query: FMPBalanceSheetQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPBalanceSheetData]:
"""Return the transformed data."""
for result in data:
result.pop("symbol", None)
result.pop("cik", None)
return [FMPBalanceSheetData.model_validate(d) for d in data]

View File

@@ -1,6 +1,8 @@
"""FMP Balance Sheet Growth Model."""
from typing import Any, Dict, List, Literal, Optional
# pylint: disable=unused-argument
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.balance_sheet_growth import (
@@ -11,26 +13,23 @@ from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from openbb_fmp.utils.helpers import create_url, get_data_many
from pydantic import Field, model_validator
from openbb_fmp.utils.definitions import FinancialPeriods
from pydantic import Field
class FMPBalanceSheetGrowthQueryParams(BalanceSheetGrowthQueryParams):
"""FMP Balance Sheet Growth Query.
Source: https://site.financialmodelingprep.com/developer/docs/#Financial-Statements-Growth
Source: https://site.financialmodelingprep.com/developer/docs#balance-sheet-statement-growth
"""
__json_schema_extra__ = {
"period": {
"choices": ["annual", "quarter"],
}
}
period: Literal["annual", "quarter"] = Field(
period: FinancialPeriods = Field(
default="annual",
description=QUERY_DESCRIPTIONS.get("period", ""),
)
limit: Optional[int] = Field(
default=None, description=QUERY_DESCRIPTIONS.get("limit", "") + " (default 5)"
)
class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
@@ -40,13 +39,19 @@ class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
"period_ending": "date",
"fiscal_year": "calendarYear",
"fiscal_period": "period",
"growth_other_total_shareholders_equity": "growthOtherTotalStockholdersEquity",
"currency": "reportedCurrency",
"growth_other_total_shareholders_equity": "growthOthertotalStockholdersEquity",
"growth_total_shareholders_equity": "growthTotalStockholdersEquity",
"growth_total_liabilities_and_shareholders_equity": "growthTotalLiabilitiesAndStockholdersEquity",
"growth_accumulated_other_comprehensive_income": "growthAccumulatedOtherComprehensiveIncomeLoss",
"growth_prepaid_expenses": "growthPrepaids",
}
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
reported_currency: Optional[str] = Field(
description="The currency in which the financial data is reported.",
default=None,
)
growth_cash_and_cash_equivalents: Optional[float] = Field(
default=None,
description="Growth rate of cash and cash equivalents.",
@@ -62,6 +67,16 @@ class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
description="Growth rate of cash and short-term investments.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_accounts_receivables: Optional[float] = Field(
default=None,
description="Growth rate of accounts receivable.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_other_receivables: Optional[float] = Field(
default=None,
description="Growth rate of other receivables.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_net_receivables: Optional[float] = Field(
default=None,
description="Growth rate of net receivables.",
@@ -133,6 +148,31 @@ class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
description="Growth rate of accounts payable.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_other_payables: Optional[float] = Field(
default=None,
description="Growth rate of other payables.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_total_payables: Optional[float] = Field(
default=None,
description="Growth rate of total payables.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_accrued_expenses: Optional[float] = Field(
default=None,
description="Growth rate of accrued expenses.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_prepaid_expenses: Optional[float] = Field(
default=None,
description="Growth rate of prepaid expenses.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_capital_lease_obligations_current: Optional[float] = Field(
default=None,
description="Growth rate of current capital lease obligations.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_short_term_debt: Optional[float] = Field(
default=None,
description="Growth rate of short-term debt.",
@@ -143,6 +183,11 @@ class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
description="Growth rate of tax payables.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_deferred_tax_liabilities_non_current: Optional[float] = Field(
default=None,
description="Growth rate of non-current deferred tax liabilities.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_deferred_revenue: Optional[float] = Field(
default=None,
description="Growth rate of deferred revenue.",
@@ -158,16 +203,16 @@ class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
description="Growth rate of total current liabilities.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_long_term_debt: Optional[float] = Field(
default=None,
description="Growth rate of long-term debt.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_deferred_revenue_non_current: Optional[float] = Field(
default=None,
description="Growth rate of non-current deferred revenue.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_long_term_debt: Optional[float] = Field(
default=None,
description="Growth rate of long-term debt.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_deferrred_tax_liabilities_non_current: Optional[float] = Field(
default=None,
description="Growth rate of non-current deferred tax liabilities.",
@@ -191,10 +236,6 @@ class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
description="Growth rate of total liabilities.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_common_stock: Optional[float] = Field(
description="Growth rate of common stock.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_retained_earnings: Optional[float] = Field(
description="Growth rate of retained earnings.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
@@ -203,6 +244,16 @@ class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
description="Growth rate of accumulated other comprehensive income/loss.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_minority_interest: Optional[float] = Field(
default=None,
description="Growth rate of minority interest.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_additional_paid_in_capital: Optional[float] = Field(
default=None,
description="Growth rate of additional paid-in capital.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_other_total_shareholders_equity: Optional[float] = Field(
default=None,
description="Growth rate of other total stockholders' equity.",
@@ -213,6 +264,24 @@ class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
description="Growth rate of total stockholders' equity.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_common_stock: Optional[float] = Field(
description="Growth rate of common stock.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_preferred_stock: Optional[float] = Field(
description="Growth rate of preferred stock.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_treasury_stock: Optional[float] = Field(
default=None,
description="Growth rate of treasury stock.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_total_equity: Optional[float] = Field(
default=None,
description="Growth rate of total equity.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_total_liabilities_and_shareholders_equity: Optional[float] = Field(
default=None,
description="Growth rate of total liabilities and stockholders' equity.",
@@ -234,52 +303,45 @@ class FMPBalanceSheetGrowthData(BalanceSheetGrowthData):
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
@model_validator(mode="before")
@classmethod
def replace_zero(cls, values):
"""Check for zero values and replace with None."""
return (
{k: None if v == 0 else v for k, v in values.items()}
if isinstance(values, dict)
else values
)
class FMPBalanceSheetGrowthFetcher(
Fetcher[
FMPBalanceSheetGrowthQueryParams,
List[FMPBalanceSheetGrowthData],
list[FMPBalanceSheetGrowthData],
]
):
"""FMP Balance Sheet Growth Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPBalanceSheetGrowthQueryParams:
def transform_query(params: dict[str, Any]) -> FMPBalanceSheetGrowthQueryParams:
"""Transform the query params."""
return FMPBalanceSheetGrowthQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPBalanceSheetGrowthQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
url = create_url(
3,
f"balance-sheet-statement-growth/{query.symbol}",
api_key,
query,
["symbol"],
url = (
"https://financialmodelingprep.com/stable/balance-sheet-statement-growth"
+ f"?symbol={query.symbol}"
+ f"&period={query.period}"
+ f"&limit={query.limit if query.limit else 5}"
+ f"&apikey={api_key}"
)
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPBalanceSheetGrowthQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPBalanceSheetGrowthData]:
query: FMPBalanceSheetGrowthQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPBalanceSheetGrowthData]:
"""Return the transformed data."""
return [FMPBalanceSheetGrowthData.model_validate(d) for d in data]

View File

@@ -1,16 +1,15 @@
"""FMP Dividend Calendar Model."""
from datetime import datetime
from typing import Any, Dict, List, Optional
# pylint: disable=unused-argument
from datetime import datetime, timedelta
from typing import Any, Optional
from dateutil.relativedelta import relativedelta
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.calendar_dividend import (
CalendarDividendData,
CalendarDividendQueryParams,
)
from openbb_core.provider.utils.helpers import get_querystring
from openbb_fmp.utils.helpers import get_data_many
from pydantic import Field, field_validator
@@ -35,14 +34,21 @@ class FMPCalendarDividendData(CalendarDividendData):
"payment_date": "paymentDate",
"declaration_date": "declarationDate",
"adjusted_amount": "adjDividend",
"dividend_yield": "yield",
}
adjusted_amount: Optional[float] = Field(
default=None,
description="The adjusted-dividend amount.",
)
label: Optional[str] = Field(
default=None, description="Ex-dividend date formatted for display."
dividend_yield: Optional[float] = Field(
default=None,
description="Annualized dividend yield.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
frequency: Optional[str] = Field(
default=None,
description="Frequency of the regular dividend payment.",
)
@field_validator(
@@ -54,50 +60,70 @@ class FMPCalendarDividendData(CalendarDividendData):
check_fields=False,
)
@classmethod
def date_validate(cls, v: str): # pylint: disable=E0213
def date_validate(cls, v):
"""Return the date as a datetime object."""
return datetime.strptime(v, "%Y-%m-%d") if v else None
@field_validator("dividend_yield", mode="before", check_fields=False)
@classmethod
def dividend_yield_validate(cls, v):
"""Return the dividend yield as a float."""
return v / 100 if v else None
class FMPCalendarDividendFetcher(
Fetcher[
FMPCalendarDividendQueryParams,
List[FMPCalendarDividendData],
list[FMPCalendarDividendData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCalendarDividendQueryParams:
def transform_query(params: dict[str, Any]) -> FMPCalendarDividendQueryParams:
"""Transform the query params."""
transformed_params = params
now = datetime.now().date()
if params.get("start_date") is None:
transformed_params["start_date"] = now
if params.get("end_date") is None:
transformed_params["end_date"] = now + relativedelta(days=30)
return FMPCalendarDividendQueryParams(**transformed_params)
return FMPCalendarDividendQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPCalendarDividendQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import amake_requests # noqa
from openbb_fmp.utils.helpers import response_callback
api_key = credentials.get("fmp_api_key") if credentials else ""
base_url = "https://financialmodelingprep.com/api/v3"
query_str = get_querystring(query.model_dump(), [])
url = f"{base_url}/stock_dividend_calendar?{query_str}&apikey={api_key}"
base_url = "https://financialmodelingprep.com/stable/dividends-calendar?"
start_date = query.start_date or datetime.now().date()
end_date = query.end_date or datetime.now().date() + timedelta(days=14)
return await get_data_many(url, **kwargs)
# Create 14-day chunks between start_date and end_date
urls: list = []
current_start = start_date
while current_start <= end_date:
chunk_end = min(current_start + timedelta(days=14), end_date)
url = f"{base_url}from={current_start}&to={chunk_end}&apikey={api_key}"
urls.append(url)
current_start = chunk_end + timedelta(days=1)
# Get data from all URLs
all_data: list = await amake_requests(
urls, response_callback=response_callback, **kwargs
)
return all_data
@staticmethod
def transform_data(
query: FMPCalendarDividendQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPCalendarDividendData]:
query: FMPCalendarDividendQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCalendarDividendData]:
"""Return the transformed data."""
return [FMPCalendarDividendData.model_validate(d) for d in data]
return [
FMPCalendarDividendData.model_validate(d)
for d in sorted(data, key=lambda x: x["date"])
]

View File

@@ -1,25 +1,26 @@
"""FMP Earnings Calendar Model."""
# pylint: disable=unused-argument
from datetime import (
date as dateType,
datetime,
timedelta,
)
from typing import Any, Dict, List, Optional, Union
from typing import Any, Optional, Union
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.calendar_earnings import (
CalendarEarningsData,
CalendarEarningsQueryParams,
)
from openbb_fmp.utils.helpers import create_url, get_data_many
from pydantic import Field, field_validator
class FMPCalendarEarningsQueryParams(CalendarEarningsQueryParams):
"""FMP Earnings Calendar Query.
Source: https://site.financialmodelingprep.com/developer/docs/earnings-calendar-api/
Source: https://site.financialmodelingprep.com/developer/docs#earnings-calendar
"""
__alias_dict__ = {
@@ -34,43 +35,32 @@ class FMPCalendarEarningsData(CalendarEarningsData):
__alias_dict__ = {
"report_date": "date",
"eps_consensus": "epsEstimated",
"eps_actual": "eps",
"revenue_actual": "revenue",
"eps_actual": "epsActual",
"revenue_actual": "revenueActual",
"revenue_consensus": "revenueEstimated",
"period_ending": "fiscalDateEnding",
"reporting_time": "time",
"updated_date": "updatedFromDate",
"last_updated": "lastUpdated",
}
eps_actual: Optional[float] = Field(
default=None,
description="The actual earnings per share announced.",
)
revenue_actual: Optional[float] = Field(
default=None,
description="The actual reported revenue.",
)
revenue_consensus: Optional[float] = Field(
default=None,
description="The revenue forecast consensus.",
)
period_ending: Optional[dateType] = Field(
revenue_actual: Optional[float] = Field(
default=None,
description="The fiscal period end date.",
description="The actual reported revenue.",
)
reporting_time: Optional[str] = Field(
default=None,
description="The reporting time - e.g. after market close.",
)
updated_date: Optional[dateType] = Field(
last_updated: Optional[dateType] = Field(
default=None,
description="The date the data was updated last.",
)
@field_validator(
"report_date",
"updated_date",
"period_ending",
"last_updated",
mode="before",
check_fields=False,
)
@@ -84,13 +74,13 @@ class FMPCalendarEarningsData(CalendarEarningsData):
class FMPCalendarEarningsFetcher(
Fetcher[
FMPCalendarEarningsQueryParams,
List[FMPCalendarEarningsData],
list[FMPCalendarEarningsData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCalendarEarningsQueryParams:
def transform_query(params: dict[str, Any]) -> FMPCalendarEarningsQueryParams:
"""Transform the query params."""
now = datetime.today().date()
transformed_params = params
@@ -106,20 +96,41 @@ class FMPCalendarEarningsFetcher(
@staticmethod
async def aextract_data(
query: FMPCalendarEarningsQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import amake_requests
from openbb_fmp.utils.helpers import response_callback
api_key = credentials.get("fmp_api_key") if credentials else ""
url = create_url(3, "earning_calendar", api_key, query, [])
base_url = "https://financialmodelingprep.com/stable/earnings-calendar?"
start_date = query.start_date or datetime.now().date()
end_date = query.end_date or datetime.now().date() + timedelta(days=7)
return await get_data_many(url, **kwargs)
# Create 7-day chunks between start_date and end_date
urls: list = []
current_start = start_date
while current_start <= end_date:
chunk_end = min(current_start + timedelta(days=7), end_date)
url = f"{base_url}from={current_start}&to={chunk_end}&apikey={api_key}"
urls.append(url)
current_start = chunk_end + timedelta(days=1)
# Get data from all URLs
all_data: list = await amake_requests(
urls, response_callback=response_callback, **kwargs
)
return all_data
@staticmethod
def transform_data(
query: FMPCalendarEarningsQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPCalendarEarningsData]:
query: FMPCalendarEarningsQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCalendarEarningsData]:
"""Return the transformed data."""
data = sorted(data, key=lambda x: x["date"], reverse=True)
return [FMPCalendarEarningsData.model_validate(d) for d in data]

View File

@@ -76,6 +76,7 @@ class FMPCalendarEventsFetcher(
"""Extract data from the API."""
# pylint: disable=import-outside-toplevel
import asyncio # noqa
import warnings
from datetime import timedelta
from openbb_core.provider.utils.errors import EmptyDataError, OpenBBError
from openbb_fmp.utils.helpers import get_data
@@ -83,6 +84,9 @@ class FMPCalendarEventsFetcher(
api_key = credentials.get("fmp_api_key") if credentials else ""
warnings.warn(
message="This endpoint appears to be deprecated. Please use the earnings calendar endpoint instead.",
)
base_url = (
"https://financialmodelingprep.com/api/v4/earning-calendar-confirmed?"
)

View File

@@ -0,0 +1,134 @@
"""FMP Earnings Calendar Model."""
# pylint: disable=unused-argument
from datetime import (
datetime,
timedelta,
)
from typing import Any, Optional, Union
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.calendar_ipo import (
CalendarIpoData,
CalendarIpoQueryParams,
)
from pydantic import Field
class FMPCalendarIpoQueryParams(CalendarIpoQueryParams):
"""FMP IPO Calendar Query.
Source: https://site.financialmodelingprep.com/developer/docs#ipos-calendar
"""
__alias_dict__ = {
"start_date": "from",
"end_date": "to",
}
class FMPCalendarIpoData(CalendarIpoData):
"""FMP Earnings Calendar Data."""
__alias_dict__ = {
"ipo_date": "date",
"exchange_date": "daa",
"name": "company",
}
exchange_date: Optional[datetime] = Field(
default=None,
description="Timezone information for the exchange and date of the IPO.",
)
name: Optional[str] = Field(
default=None,
description="The name of the entity going public.",
)
exchange: Optional[str] = Field(
default=None,
description="The exchange where the IPO is listed.",
)
actions: Optional[str] = Field(
default=None,
description="Actions related to the IPO, such as, Expected, Priced, Filed, Amended.",
)
shares: Optional[Union[int, float]] = Field(
default=None,
description="The number of shares being offered in the IPO.",
)
price_range: Optional[str] = Field(
default=None,
description="The expected price range for the IPO shares.",
)
market_cap: Optional[Union[int, float]] = Field(
default=None,
description="The estimated market capitalization of the company at the time of the IPO.",
)
class FMPCalendarIpoFetcher(
Fetcher[
FMPCalendarIpoQueryParams,
list[FMPCalendarIpoData],
]
):
"""FMP IPO Calendar Fetcher."""
@staticmethod
def transform_query(params: dict[str, Any]) -> FMPCalendarIpoQueryParams:
"""FMP IPO Calendar Query Params."""
now = datetime.today().date()
transformed_params = params
if params.get("start_date") is None:
transformed_params["start_date"] = now
if params.get("end_date") is None:
transformed_params["end_date"] = now + timedelta(days=3)
return FMPCalendarIpoQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPCalendarIpoQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import amake_requests # noqa
from openbb_fmp.utils.helpers import response_callback
api_key = credentials.get("fmp_api_key") if credentials else ""
base_url = "https://financialmodelingprep.com/stable/ipos-calendar?"
start_date = query.start_date or datetime.now().date() - timedelta(days=7)
end_date = query.end_date or datetime.now().date() + timedelta(days=7)
# Create 90-day chunks between start_date and end_date
urls: list = []
current_start = start_date
while current_start <= end_date:
chunk_end = min(current_start + timedelta(days=89), end_date)
url = f"{base_url}from={current_start}&to={chunk_end}&apikey={api_key}"
urls.append(url)
current_start = chunk_end + timedelta(days=1)
# Get data from all URLs
all_data: list = await amake_requests(
urls, response_callback=response_callback, **kwargs
)
return all_data
@staticmethod
def transform_data(
query: FMPCalendarIpoQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCalendarIpoData]:
"""Return the transformed data."""
return [
FMPCalendarIpoData.model_validate(d)
for d in sorted(data, key=lambda x: x["date"], reverse=True)
]

View File

@@ -1,21 +1,21 @@
"""FMP Calendar Splits Model."""
from datetime import date
from typing import Any, Dict, List, Optional
# pylint: disable=unused-argument
from datetime import datetime, timedelta
from typing import Any, Optional
from dateutil.relativedelta import relativedelta
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.calendar_splits import (
CalendarSplitsData,
CalendarSplitsQueryParams,
)
from openbb_fmp.utils.helpers import create_url, get_data_many
class FMPCalendarSplitsQueryParams(CalendarSplitsQueryParams):
"""FMP Calendar Splits Query.
Source: https://site.financialmodelingprep.com/developer/docs/stock-split-calendar-api/
Source: https://site.financialmodelingprep.com/developer/docs#splits-calendar
"""
__alias_dict__ = {"start_date": "from", "end_date": "to"}
@@ -28,42 +28,53 @@ class FMPCalendarSplitsData(CalendarSplitsData):
class FMPCalendarSplitsFetcher(
Fetcher[
FMPCalendarSplitsQueryParams,
List[FMPCalendarSplitsData],
list[FMPCalendarSplitsData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
"""FMP Calendar Splits Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCalendarSplitsQueryParams:
def transform_query(params: dict[str, Any]) -> FMPCalendarSplitsQueryParams:
"""Transform the query params. Start and end dates are set to a 1 year interval."""
transformed_params = params
now = date.today()
if params.get("start_date") is None:
transformed_params["start_date"] = now
if params.get("end_date") is None:
transformed_params["end_date"] = now + relativedelta(days=30)
return FMPCalendarSplitsQueryParams(**transformed_params)
return FMPCalendarSplitsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPCalendarSplitsQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import amake_requests # noqa
from openbb_fmp.utils.helpers import response_callback
api_key = credentials.get("fmp_api_key") if credentials else ""
query_str = f"from={query.start_date}&to={query.end_date}"
url = create_url(3, f"stock_split_calendar?{query_str}", api_key)
base_url = "https://financialmodelingprep.com/stable/splits-calendar?"
start_date = query.start_date or datetime.now().date() - timedelta(days=7)
end_date = query.end_date or datetime.now().date() + timedelta(days=14)
return await get_data_many(url, **kwargs)
# Create 90-day chunks between start_date and end_date
urls: list = []
current_start = start_date
while current_start <= end_date:
chunk_end = min(current_start + timedelta(days=89), end_date)
url = f"{base_url}from={current_start}&to={chunk_end}&apikey={api_key}"
urls.append(url)
current_start = chunk_end + timedelta(days=1)
# Get data from all URLs
all_data: list = await amake_requests(
urls, response_callback=response_callback, **kwargs
)
return all_data
@staticmethod
def transform_data(
query: FMPCalendarSplitsQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPCalendarSplitsData]:
query: FMPCalendarSplitsQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCalendarSplitsData]:
"""Return the transformed data."""
return [FMPCalendarSplitsData.model_validate(d) for d in data]

View File

@@ -6,7 +6,7 @@ from datetime import (
date as dateType,
datetime,
)
from typing import Any, Dict, List, Literal, Optional
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.cash_flow import (
@@ -14,8 +14,8 @@ from openbb_core.provider.standard_models.cash_flow import (
CashFlowStatementQueryParams,
)
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
from openbb_fmp.utils.helpers import create_url, get_data_many
from pydantic import Field, model_validator
from openbb_fmp.utils.definitions import FinancialStatementPeriods
from pydantic import Field
class FMPCashFlowStatementQueryParams(CashFlowStatementQueryParams):
@@ -24,13 +24,7 @@ class FMPCashFlowStatementQueryParams(CashFlowStatementQueryParams):
Source: https://financialmodelingprep.com/developer/docs/#Cash-Flow-Statement
"""
__json_schema_extra__ = {
"period": {
"choices": ["annual", "quarter"],
}
}
period: Literal["annual", "quarter"] = Field(
period: FinancialStatementPeriods = Field(
default="annual",
description=QUERY_DESCRIPTIONS.get("period", ""),
)
@@ -61,14 +55,17 @@ class FMPCashFlowStatementData(CashFlowStatementData):
"acquisitions": "acquisitionsNet",
"purchase_of_investment_securities": "purchasesOfInvestments",
"sale_and_maturity_of_investments": "salesMaturitiesOfInvestments",
"other_investing_activities": "otherInvestingActivites",
"net_cash_from_investing_activities": "netCashUsedForInvestingActivites",
"other_investing_activities": "otherInvestingActivities",
"net_cash_from_investing_activities": "netCashProvidedByInvestingActivities",
"repayment_of_debt": "debtRepayment",
"issuance_of_common_equity": "commonStockIssued",
"issuance_of_common_equity": "commonStockIssuance",
"repurchase_of_common_equity": "commonStockRepurchased",
"net_common_equity_issuance": "netCommonStockIssuance",
"net_preferred_equity_issuance": "netPreferredStockIssuance",
"net_equity_issuance": "netStockIssuance",
"payment_of_dividends": "dividendsPaid",
"other_financing_activities": "otherFinancingActivites",
"net_cash_from_financing_activities": "netCashUsedProvidedByFinancingActivities",
"net_cash_from_financing_activities": "netCashProvidedByFinancingActivities",
"effect_of_exchange_rate_changes_on_cash": "effectOfForexChangesOnCash",
"net_change_in_cash_and_equivalents": "netChangeInCash",
"cash_at_beginning_of_period": "cashAtBeginningOfPeriod",
@@ -76,8 +73,6 @@ class FMPCashFlowStatementData(CashFlowStatementData):
"operating_cash_flow": "operatingCashFlow",
"capital_expenditure": "capitalExpenditure",
"free_cash_flow": "freeCashFlow",
"link": "link",
"final_link": "finalLink",
}
fiscal_year: Optional[int] = Field(
@@ -91,183 +86,220 @@ class FMPCashFlowStatementData(CashFlowStatementData):
accepted_date: Optional[datetime] = Field(
default=None, description="The date the filing was accepted."
)
cik: Optional[str] = Field(
default=None,
description="The Central Index Key (CIK) assigned by the SEC, if applicable.",
)
symbol: Optional[str] = Field(
default=None,
description="The stock ticker symbol.",
)
reported_currency: Optional[str] = Field(
default=None,
description="The currency in which the cash flow statement was reported.",
)
net_income: Optional[float] = Field(
net_income: Optional[int] = Field(
default=None,
description="Net income.",
)
depreciation_and_amortization: Optional[float] = Field(
depreciation_and_amortization: Optional[int] = Field(
default=None,
description="Depreciation and amortization.",
)
deferred_income_tax: Optional[float] = Field(
deferred_income_tax: Optional[int] = Field(
default=None,
description="Deferred income tax.",
)
stock_based_compensation: Optional[float] = Field(
stock_based_compensation: Optional[int] = Field(
default=None,
description="Stock-based compensation.",
)
change_in_working_capital: Optional[float] = Field(
change_in_working_capital: Optional[int] = Field(
default=None,
description="Change in working capital.",
)
change_in_account_receivables: Optional[float] = Field(
change_in_account_receivables: Optional[int] = Field(
default=None,
description="Change in account receivables.",
)
change_in_inventory: Optional[float] = Field(
change_in_inventory: Optional[int] = Field(
default=None,
description="Change in inventory.",
)
change_in_account_payable: Optional[float] = Field(
change_in_account_payable: Optional[int] = Field(
default=None,
description="Change in account payable.",
)
change_in_other_working_capital: Optional[float] = Field(
change_in_other_working_capital: Optional[int] = Field(
default=None,
description="Change in other working capital.",
)
change_in_other_non_cash_items: Optional[float] = Field(
change_in_other_non_cash_items: Optional[int] = Field(
default=None,
description="Change in other non-cash items.",
)
net_cash_from_operating_activities: Optional[float] = Field(
net_cash_from_operating_activities: Optional[int] = Field(
default=None,
description="Net cash from operating activities.",
)
purchase_of_property_plant_and_equipment: Optional[float] = Field(
purchase_of_property_plant_and_equipment: Optional[int] = Field(
default=None,
description="Purchase of property, plant and equipment.",
)
acquisitions: Optional[float] = Field(
acquisitions: Optional[int] = Field(
default=None,
description="Acquisitions.",
)
purchase_of_investment_securities: Optional[float] = Field(
purchase_of_investment_securities: Optional[int] = Field(
default=None,
description="Purchase of investment securities.",
)
sale_and_maturity_of_investments: Optional[float] = Field(
sale_and_maturity_of_investments: Optional[int] = Field(
default=None,
description="Sale and maturity of investments.",
)
other_investing_activities: Optional[float] = Field(
other_investing_activities: Optional[int] = Field(
default=None,
description="Other investing activities.",
)
net_cash_from_investing_activities: Optional[float] = Field(
net_cash_from_investing_activities: Optional[int] = Field(
default=None,
description="Net cash from investing activities.",
)
repayment_of_debt: Optional[float] = Field(
repayment_of_debt: Optional[int] = Field(
default=None,
description="Repayment of debt.",
)
issuance_of_common_equity: Optional[float] = Field(
issuance_of_common_equity: Optional[int] = Field(
default=None,
description="Issuance of common equity.",
)
repurchase_of_common_equity: Optional[float] = Field(
repurchase_of_common_equity: Optional[int] = Field(
default=None,
description="Repurchase of common equity.",
)
payment_of_dividends: Optional[float] = Field(
net_common_equity_issuance: Optional[int] = Field(
default=None,
description="Payment of dividends.",
description="Net common equity issuance.",
)
other_financing_activities: Optional[float] = Field(
net_preferred_equity_issuance: Optional[int] = Field(
default=None,
description="Net preferred equity issuance.",
)
net_equity_issuance: Optional[int] = Field(
default=None,
description="Net equity issuance.",
)
short_term_net_debt_issuance: Optional[int] = Field(
default=None,
description="Short term net debt issuance.",
)
long_term_net_debt_issuance: Optional[int] = Field(
default=None,
description="Long term net debt issuance.",
)
net_debt_issuance: Optional[int] = Field(
default=None,
description="Net debt issuance.",
)
common_dividends_paid: Optional[int] = Field(
default=None,
description="Payment of common dividends.",
)
preferred_dividends_paid: Optional[int] = Field(
default=None,
description="Payment of preferred dividends.",
)
net_dividends_paid: Optional[int] = Field(
default=None,
description="Net dividends paid.",
)
other_financing_activities: Optional[int] = Field(
default=None,
description="Other financing activities.",
)
net_cash_from_financing_activities: Optional[float] = Field(
net_cash_from_financing_activities: Optional[int] = Field(
default=None,
description="Net cash from financing activities.",
)
effect_of_exchange_rate_changes_on_cash: Optional[float] = Field(
effect_of_exchange_rate_changes_on_cash: Optional[int] = Field(
default=None,
description="Effect of exchange rate changes on cash.",
)
net_change_in_cash_and_equivalents: Optional[float] = Field(
net_change_in_cash_and_equivalents: Optional[int] = Field(
default=None,
description="Net change in cash and equivalents.",
)
cash_at_beginning_of_period: Optional[float] = Field(
cash_at_beginning_of_period: Optional[int] = Field(
default=None,
description="Cash at beginning of period.",
)
cash_at_end_of_period: Optional[float] = Field(
cash_at_end_of_period: Optional[int] = Field(
default=None,
description="Cash at end of period.",
)
operating_cash_flow: Optional[float] = Field(
operating_cash_flow: Optional[int] = Field(
default=None,
description="Operating cash flow.",
)
capital_expenditure: Optional[float] = Field(
capital_expenditure: Optional[int] = Field(
default=None,
description="Capital expenditure.",
)
free_cash_flow: Optional[float] = Field(
income_taxes_paid: Optional[int] = Field(
default=None,
description="Income taxes paid.",
)
interest_paid: Optional[int] = Field(
default=None,
description="Interest paid.",
)
free_cash_flow: Optional[int] = Field(
default=None,
)
link: Optional[str] = Field(
default=None,
description="Link to the filing.",
)
final_link: Optional[str] = Field(
default=None,
description="Link to the filing document.",
)
@model_validator(mode="before")
@classmethod
def replace_zero(cls, values): # pylint: disable=no-self-argument
"""Check for zero values and replace with None."""
return (
{k: None if v == 0 else v for k, v in values.items()}
if isinstance(values, dict)
else values
)
class FMPCashFlowStatementFetcher(
Fetcher[
FMPCashFlowStatementQueryParams,
List[FMPCashFlowStatementData],
list[FMPCashFlowStatementData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCashFlowStatementQueryParams:
def transform_query(params: dict[str, Any]) -> FMPCashFlowStatementQueryParams:
"""Transform the query params."""
return FMPCashFlowStatementQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPCashFlowStatementQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
api_key = credentials.get("fmp_api_key") if credentials else ""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
url = create_url(
3, f"cash-flow-statement/{query.symbol}", api_key, query, ["symbol"]
api_key = credentials.get("fmp_api_key") if credentials else ""
base_url = "https://financialmodelingprep.com/stable/cash-flow-statement"
if query.period == "ttm":
base_url += "-ttm"
url = (
base_url
+ f"?symbol={query.symbol}{'&period=' + query.period if query.period != 'ttm' else ''}"
+ f"&limit={query.limit if query.limit else 5}"
+ f"&apikey={api_key}"
)
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPCashFlowStatementQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPCashFlowStatementData]:
query: FMPCashFlowStatementQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCashFlowStatementData]:
"""Return the transformed data."""
for result in data:
result.pop("symbol", None)
result.pop("cik", None)
return [FMPCashFlowStatementData.model_validate(d) for d in data]

View File

@@ -1,6 +1,8 @@
"""FMP Cash Flow Statement Growth Model."""
from typing import Any, Dict, List, Literal, Optional
# pylint: disable=unused-argument
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.cash_flow_growth import (
@@ -11,23 +13,17 @@ from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from openbb_fmp.utils.helpers import create_url, get_data_many
from openbb_fmp.utils.definitions import FinancialPeriods
from pydantic import Field
class FMPCashFlowStatementGrowthQueryParams(CashFlowStatementGrowthQueryParams):
"""FMP Cash Flow Statement Growth Query.
Source: https://site.financialmodelingprep.com/developer/docs/financial-statements-growth-api/
Source: https://site.financialmodelingprep.com/developer/docs#cashflow-statement-growth
"""
__json_schema_extra__ = {
"period": {
"choices": ["annual", "quarter"],
}
}
period: Literal["annual", "quarter"] = Field(
period: FinancialPeriods = Field(
default="annual",
description=QUERY_DESCRIPTIONS.get("period", ""),
)
@@ -40,6 +36,7 @@ class FMPCashFlowStatementGrowthData(CashFlowStatementGrowthData):
"period_ending": "date",
"fiscal_year": "calendarYear",
"fiscal_period": "period",
"reported_currency": "reportedCurrency",
"growth_acquisitions": "growthAcquisitionsNet",
"growth_sale_and_maturity_of_investments": "growthSalesMaturitiesOfInvestments",
"growth_net_cash_from_operating_activities": "growthNetCashProvidedByOperatingActivites",
@@ -53,8 +50,16 @@ class FMPCashFlowStatementGrowthData(CashFlowStatementGrowthData):
"growth_repayment_of_debt": "growthDebtRepayment",
"growth_net_change_in_cash_and_equivalents": "growthNetChangeInCash",
"growth_effect_of_exchange_rate_changes_on_cash": "growthEffectOfForexChangesOnCash",
"growth_net_cash_from_financing_activities": "growthNetCashUsedProvidedByFinancingActivities",
"growth_net_equity_issuance": "growthNetStockIssuance",
"growth_common_equity_issuance": "growthCommonStockIssued",
"growth_common_equity_repurchased": "growthCommonStockRepurchased",
}
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
reported_currency: Optional[str] = Field(
description="The currency in which the financial data is reported.",
default=None,
)
growth_net_income: Optional[float] = Field(
default=None,
description="Growth rate of net income.",
@@ -140,19 +145,39 @@ class FMPCashFlowStatementGrowthData(CashFlowStatementGrowthData):
description="Growth rate of net cash used for investing activities.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_short_term_net_debt_issuance: Optional[float] = Field(
default=None,
description="Growth rate of short term net debt issuance.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_long_term_net_debt_issuance: Optional[float] = Field(
default=None,
description="Growth rate of long term net debt issuance.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_net_debt_issuance: Optional[float] = Field(
default=None,
description="Growth rate of net debt issuance.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_repayment_of_debt: Optional[float] = Field(
default=None,
description="Growth rate of debt repayment.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_common_stock_issued: Optional[float] = Field(
growth_common_equity_issuance: Optional[float] = Field(
default=None,
description="Growth rate of common stock issued.",
description="Growth rate of common equity issued.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_common_stock_repurchased: Optional[float] = Field(
growth_common_equity_repurchased: Optional[float] = Field(
default=None,
description="Growth rate of common stock repurchased.",
description="Growth rate of common equity repurchased.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_net_equity_issuance: Optional[float] = Field(
default=None,
description="Growth rate of net equity issuance.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_dividends_paid: Optional[float] = Field(
@@ -160,6 +185,11 @@ class FMPCashFlowStatementGrowthData(CashFlowStatementGrowthData):
description="Growth rate of dividends paid.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_preferred_dividends_paid: Optional[float] = Field(
default=None,
description="Growth rate of preferred dividends paid.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_other_financing_activities: Optional[float] = Field(
default=None,
description="Growth rate of other financing activities.",
@@ -200,6 +230,16 @@ class FMPCashFlowStatementGrowthData(CashFlowStatementGrowthData):
description="Growth rate of capital expenditure.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_income_taxes_paid: Optional[float] = Field(
default=None,
description="Growth rate of income taxes paid.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_interest_paid: Optional[float] = Field(
default=None,
description="Growth rate of interest paid.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
growth_free_cash_flow: Optional[float] = Field(
default=None,
description="Growth rate of free cash flow.",
@@ -210,14 +250,14 @@ class FMPCashFlowStatementGrowthData(CashFlowStatementGrowthData):
class FMPCashFlowStatementGrowthFetcher(
Fetcher[
FMPCashFlowStatementGrowthQueryParams,
List[FMPCashFlowStatementGrowthData],
list[FMPCashFlowStatementGrowthData],
]
):
"""FMP Cash Flow Statement Growth Fetcher."""
@staticmethod
def transform_query(
params: Dict[str, Any]
params: dict[str, Any],
) -> FMPCashFlowStatementGrowthQueryParams:
"""Transform the query params."""
return FMPCashFlowStatementGrowthQueryParams(**params)
@@ -225,21 +265,28 @@ class FMPCashFlowStatementGrowthFetcher(
@staticmethod
async def aextract_data(
query: FMPCashFlowStatementGrowthQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Transform the query, extract and transform the data from the FMP endpoints."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
url = create_url(
3, f"cash-flow-statement-growth/{query.symbol}", api_key, query, ["symbol"]
url = (
"https://financialmodelingprep.com/stable/cash-flow-statement-growth"
+ f"?symbol={query.symbol}"
+ f"&period={query.period}"
+ f"&limit={query.limit if query.limit else 5}"
+ f"&apikey={api_key}"
)
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPCashFlowStatementGrowthQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPCashFlowStatementGrowthData]:
query: FMPCashFlowStatementGrowthQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCashFlowStatementGrowthData]:
"""Return the transformed data."""
return [FMPCashFlowStatementGrowthData.model_validate(d) for d in data]

View File

@@ -2,8 +2,11 @@
# pylint: disable=unused-argument
from datetime import datetime
from typing import Any, Dict, List, Optional
from datetime import (
date as dateType,
timedelta,
)
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.company_filings import (
@@ -13,23 +16,37 @@ from openbb_core.provider.standard_models.company_filings import (
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
)
from openbb_core.provider.utils.errors import EmptyDataError
from pydantic import Field
class FMPCompanyFilingsQueryParams(CompanyFilingsQueryParams):
"""FMP Copmany Filings Query.
"""FMP Company Filings Query.
Source: https://site.financialmodelingprep.com/developer/docs/sec-filings-api/
"""
__alias_dict__ = {"form_type": "type"}
form_type: Optional[str] = Field(
default=None, description="SEC form type to filter by."
cik: Optional[str] = Field(
default=None, description="CIK number to look up. Overrides symbol."
)
start_date: Optional[dateType] = Field(
default=None,
description="Start date for filtering filings. Default is one year ago.",
)
end_date: Optional[dateType] = Field(
default=None, description="End date for filtering filings."
)
limit: int = Field(
default=1000,
description="Number of results to return.",
le=1000,
gt=0,
description="Number of results to return. Max results is 1000.",
)
page: int = Field(
default=0,
description="Page number for paginated results. Max page is 100.",
le=100,
gt=0,
)
@@ -37,9 +54,8 @@ class FMPCompanyFilingsData(CompanyFilingsData):
"""FMP Company Filings Data."""
__alias_dict__ = {
"filing_date": "fillingDate", # FMP spells 'filing' wrong everywhere.
"accepted_date": "acceptedDate",
"report_type": "type",
"report_type": "formType",
"filing_url": "link",
"report_url": "finalLink",
}
@@ -52,7 +68,7 @@ class FMPCompanyFilingsData(CompanyFilingsData):
cik: Optional[str] = Field(
default=None, description=DATA_DESCRIPTIONS.get("cik", "")
)
accepted_date: Optional[datetime] = Field(
accepted_date: Optional[dateType] = Field(
default=None, description="Accepted date of the filing."
)
@@ -60,65 +76,58 @@ class FMPCompanyFilingsData(CompanyFilingsData):
class FMPCompanyFilingsFetcher(
Fetcher[
FMPCompanyFilingsQueryParams,
List[FMPCompanyFilingsData],
list[FMPCompanyFilingsData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
"""FMP Company Filings Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCompanyFilingsQueryParams:
def transform_query(params: dict[str, Any]) -> FMPCompanyFilingsQueryParams:
"""Transform the query params."""
return FMPCompanyFilingsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPCompanyFilingsQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
import asyncio # noqa
import math
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request, get_querystring
from openbb_fmp.utils.helpers import response_callback
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
base_url = "https://financialmodelingprep.com/api/v3/sec_filings"
query_str = get_querystring(query.model_dump(by_alias=True), ["symbol"])
base_url = "https://financialmodelingprep.com/stable/sec-filings-search"
url: str = ""
# FMP only allows 1000 results per page
pages = math.ceil(query.limit / 1000)
if query.symbol and not query.cik:
url = base_url + f"/symbol?symbol={query.symbol}"
elif query.cik:
url = base_url + f"/cik?cik={query.cik}"
urls = [
f"{base_url}/{query.symbol}?{query_str}&page={page}&apikey={api_key}"
for page in range(pages)
]
if not url:
raise ValueError("Either symbol or cik must be provided.")
results: list = []
start_date = (
query.start_date
if query.start_date
else dateType.today() - timedelta(days=360)
)
url += f"&from={start_date}"
end_date = query.end_date if query.end_date else dateType.today()
url += f"&to={end_date}"
url += f"&page={query.page}&limit={query.limit}&apikey={api_key}"
async def get_one(url):
"""Get the data from one URL."""
result = await amake_request(
url, response_callback=response_callback, **kwargs
)
if result:
results.extend(result)
await asyncio.gather(*[get_one(url) for url in urls])
if not results:
raise EmptyDataError("No data was returned for the symbol provided.")
return sorted(results, key=lambda x: x["fillingDate"], reverse=True)[
: query.limit
]
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPCompanyFilingsQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPCompanyFilingsData]:
query: FMPCompanyFilingsQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCompanyFilingsData]:
"""Return the transformed data."""
if not data:
raise EmptyDataError(
f"No data found for the given query -> {query.model_dump()}"
)
return [FMPCompanyFilingsData.model_validate(d) for d in data]

View File

@@ -1,6 +1,6 @@
"""FMP Company News Model."""
from typing import Any, Dict, List, Optional
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.company_news import (
@@ -8,8 +8,6 @@ from openbb_core.provider.standard_models.company_news import (
CompanyNewsQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import filter_by_dates
from openbb_fmp.utils.helpers import get_data_many
from pydantic import Field, field_validator
@@ -21,10 +19,16 @@ class FMPCompanyNewsQueryParams(CompanyNewsQueryParams):
__json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
page: Optional[int] = Field(
page: int = Field(
default=0,
le=100,
ge=0,
description="Page number of the results. Use in combination with limit.",
)
press_release: Optional[bool] = Field(
default=None,
description="When true, will return only press releases for the given symbol(s).",
)
@field_validator("symbol", mode="before", check_fields=False)
@classmethod
@@ -41,49 +45,59 @@ class FMPCompanyNewsData(CompanyNewsData):
__alias_dict__ = {
"symbols": "symbol",
"date": "publishedDate",
"author": "publisher",
"images": "image",
"source": "site",
"excerpt": "text",
}
source: str = Field(description="Name of the news source.")
@field_validator("images", mode="before", check_fields=False)
@classmethod
def validate_images(cls, v):
"""Validate the images field."""
if v is None:
return v
if isinstance(v, str):
return [{"url": v}]
if isinstance(v, dict):
return [v]
return v
source: str = Field(description="Name of the news site.")
class FMPCompanyNewsFetcher(
Fetcher[
FMPCompanyNewsQueryParams,
List[FMPCompanyNewsData],
list[FMPCompanyNewsData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCompanyNewsQueryParams:
def transform_query(params: dict[str, Any]) -> FMPCompanyNewsQueryParams:
"""Transform the query params."""
return FMPCompanyNewsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPCompanyNewsQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
api_key = credentials.get("fmp_api_key") if credentials else ""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
limit = query.limit if query.limit else 250
page = query.page if query.page else 0
base_url = "https://financialmodelingprep.com/stable/news/"
symbols = query.symbol
if query.press_release:
base_url += "press-releases?"
else:
base_url += "stock?"
url = base_url + f"symbols={symbols}"
if query.start_date:
url += f"&from={query.start_date}"
if query.end_date:
url += f"&to={query.end_date}"
url += f"&limit={limit}&page={page}&apikey={api_key}"
base_url = "https://financialmodelingprep.com/api/v3/stock_news"
url = f"{base_url}?page={query.page}&tickers={query.symbol}&limit={query.limit}&apikey={api_key}"
response = await get_data_many(url, **kwargs)
if not response:
@@ -94,8 +108,7 @@ class FMPCompanyNewsFetcher(
# pylint: disable=unused-argument
@staticmethod
def transform_data(
query: FMPCompanyNewsQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPCompanyNewsData]:
query: FMPCompanyNewsQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCompanyNewsData]:
"""Return the transformed data."""
modeled_data = [FMPCompanyNewsData.model_validate(d) for d in data]
return filter_by_dates(modeled_data, query.start_date, query.end_date)
return [FMPCompanyNewsData.model_validate(d) for d in data]

View File

@@ -1,71 +0,0 @@
"""FMP Company Overview Model."""
from typing import Any, Dict, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.company_overview import (
CompanyOverviewData,
CompanyOverviewQueryParams,
)
from pydantic import field_validator
class FMPCompanyOverviewQueryParams(CompanyOverviewQueryParams):
"""FMP Company Overview Query.
Source: https://site.financialmodelingprep.com/developer/docs/companies-key-stats-free-api/
"""
class FMPCompanyOverviewData(CompanyOverviewData):
"""FMP Company Overview Data."""
@field_validator("ipo_date", mode="before", check_fields=False)
@classmethod
def ipoDate_validate(cls, v): # pylint: disable=E0213
"""Return the date as a datetime object."""
from datetime import date # pylint: disable=import-outside-toplevel
if isinstance(v, date) or v is None:
return v
return date.fromisoformat(v) if v else None
class FMPCompanyOverviewFetcher(
Fetcher[
FMPCompanyOverviewQueryParams,
FMPCompanyOverviewData,
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCompanyOverviewQueryParams:
"""Transform the query params."""
return FMPCompanyOverviewQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPCompanyOverviewQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> Dict:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_one
api_key = credentials.get("fmp_api_key") if credentials else ""
base_url = "https://financialmodelingprep.com/api/v3"
url = f"{base_url}/profile/{query.symbol}?apikey={api_key}"
return await get_data_one(url, **kwargs)
@staticmethod
def transform_data(
query: FMPCompanyOverviewQueryParams,
data: Dict,
**kwargs: Any,
) -> FMPCompanyOverviewData:
"""Return the transformed data."""
return FMPCompanyOverviewData.model_validate(data)

View File

@@ -12,10 +12,9 @@ from openbb_core.provider.standard_models.crypto_historical import (
CryptoHistoricalQueryParams,
)
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from pydantic import Field
from pydantic import Field, field_validator
class FMPCryptoHistoricalQueryParams(CryptoHistoricalQueryParams):
@@ -23,16 +22,15 @@ class FMPCryptoHistoricalQueryParams(CryptoHistoricalQueryParams):
FMP Crypto Historical Price Query.
Source:
https://site.financialmodelingprep.com/developer/docs/cryptocurrency-historical-data-api/#Historical-Daily-Prices
https://site.financialmodelingprep.com/developer/docs#cryptocurrency-historical-price-eod-full
"""
__alias_dict__ = {"start_date": "from", "end_date": "to"}
__json_schema_extra__ = {
"symbol": {"multiple_items_allowed": True},
"interval": {"choices": ["1m", "5m", "15m", "30m", "1h", "4h", "1d"]},
}
interval: Literal["1m", "5m", "15m", "30m", "1h", "4h", "1d"] = Field(
interval: Literal["1m", "5m", "1h", "1d"] = Field(
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
)
@@ -44,9 +42,6 @@ class FMPCryptoHistoricalData(CryptoHistoricalData):
"change_percent": "changeOverTime",
}
adj_close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("adj_close", "")
)
change: Optional[float] = Field(
default=None,
description="Change in the price from the previous close.",
@@ -57,6 +52,12 @@ class FMPCryptoHistoricalData(CryptoHistoricalData):
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
@field_validator("change_percent", mode="before", check_fields=False)
@classmethod
def _normalize_percent(cls, v):
"""Normalize percent."""
return v / 100 if v else None
class FMPCryptoHistoricalFetcher(
Fetcher[
@@ -88,100 +89,24 @@ class FMPCryptoHistoricalFetcher(
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
import asyncio # noqa
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import (
amake_request,
get_querystring,
)
from openbb_fmp.utils.helpers import get_interval, response_callback
from warnings import warn
from openbb_fmp.utils.helpers import get_historical_ohlc
api_key = credentials.get("fmp_api_key") if credentials else ""
interval = get_interval(query.interval)
base_url = "https://financialmodelingprep.com/api/v3"
query_str = get_querystring(query.model_dump(), ["symbol"])
def get_url_params(symbol: str) -> str:
url_params = f"{symbol}?{query_str}&apikey={api_key}"
url = f"{base_url}/historical-chart/{interval}/{url_params}"
if interval == "1day":
url = f"{base_url}/historical-price-full/{url_params}"
return url
symbols = query.symbol.split(",")
results: list = []
messages: list = []
async def get_one(symbol):
"""Get data for one symbol."""
url = get_url_params(symbol)
data: list = []
response = await amake_request(url, response_callback=response_callback)
if isinstance(response, dict) and response.get("Error Message"):
message = f"Error fetching data for {symbol}: {response.get('Error Message', '')}"
warn(message)
messages.append(message)
if not response:
message = f"No data found for {symbol}."
warn(message)
messages.append(message)
if isinstance(response, list) and len(response) > 0:
data = response
if len(symbols) > 1:
for d in data:
d["symbol"] = symbol
if isinstance(response, dict) and response.get("historical"):
data = response["historical"]
if len(symbols) > 1:
for d in data:
d["symbol"] = symbol
if data:
results.extend(data)
tasks = [get_one(symbol) for symbol in symbols]
await asyncio.gather(*tasks)
if not results:
raise EmptyDataError(
f"{str(','.join(messages)).replace(',',' ') if messages else 'No data found'}"
)
return results
return await get_historical_ohlc(query, credentials, **kwargs)
@staticmethod
def transform_data(
query: FMPCryptoHistoricalQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCryptoHistoricalData]:
"""Return the transformed data."""
# Get rid of duplicate fields.
to_pop = ["label", "changePercent", "unadjustedVolume"]
results: list[FMPCryptoHistoricalData] = []
for d in sorted(
data,
key=lambda x: (
(x["date"], x["symbol"])
if len(query.symbol.split(",")) > 1
else x["date"]
),
reverse=False,
):
_ = [d.pop(pop) for pop in to_pop if pop in d]
if d.get("unadjusted_volume") and d["adjusted_volume"] == d.get("volume"):
_ = d.pop("unadjusted_volume", None)
results.append(FMPCryptoHistoricalData.model_validate(d))
return results
return [
FMPCryptoHistoricalData.model_validate(d)
for d in sorted(
data,
key=lambda x: (
(x["date"], x["symbol"])
if len(query.symbol.split(",")) > 1
else x["date"]
),
reverse=False,
)
]

View File

@@ -2,7 +2,8 @@
# pylint: disable=unused-argument
from typing import Any, Dict, List, Optional
from datetime import date as dateType
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.crypto_search import (
@@ -15,7 +16,7 @@ from pydantic import Field, field_validator
class FMPCryptoSearchQueryParams(CryptoSearchQueryParams):
"""FMP Crypto Search Query.
Source: https://site.financialmodelingprep.com/developer/docs/cryptocurrency-historical-data-api
Source: https://site.financialmodelingprep.com/developer/docs#cryptocurrency-list
"""
@field_validator("query", mode="after", check_fields=False)
@@ -29,20 +30,20 @@ class FMPCryptoSearchQueryParams(CryptoSearchQueryParams):
class FMPCryptoSearchData(CryptoSearchData):
"""FMP Crypto Search Data."""
__alias_dict__ = {
"exchange": "stockExchange",
"exchange_name": "exchangeShortName",
}
currency: Optional[str] = Field(
description="The currency the crypto trades for.", default=None
)
exchange: Optional[str] = Field(
description="The exchange code the crypto trades on.",
default=None,
)
exchange_name: Optional[str] = Field(
description="The short name of the exchange the crypto trades on.",
ico_date: Optional[dateType] = Field(
description="The ICO date of the token.",
default=None,
)
circulating_supply: Optional[float] = Field(
description="The circulating supply of the token.",
default=None,
)
total_supply: Optional[float] = Field(
description="The total supply of the token.",
default=None,
)
@@ -50,42 +51,38 @@ class FMPCryptoSearchData(CryptoSearchData):
class FMPCryptoSearchFetcher(
Fetcher[
FMPCryptoSearchQueryParams,
List[FMPCryptoSearchData],
list[FMPCryptoSearchData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCryptoSearchQueryParams:
def transform_query(params: dict[str, Any]) -> FMPCryptoSearchQueryParams:
"""Transform the query."""
return FMPCryptoSearchQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPCryptoSearchQueryParams, # pylint: disable=unused-argument
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import create_url, get_data_many
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
url = create_url(
version=3,
endpoint="symbol/available-cryptocurrencies",
api_key=api_key,
)
url = f"https://financialmodelingprep.com/stable/cryptocurrency-list?apikey={api_key}"
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPCryptoSearchQueryParams,
data: List[Dict],
data: list[dict],
**kwargs: Any,
) -> List[FMPCryptoSearchData]:
) -> list[FMPCryptoSearchData]:
"""Return the transformed data."""
# pylint: disable=import-outside-toplevel
from pandas import DataFrame
@@ -95,13 +92,8 @@ class FMPCryptoSearchFetcher(
cryptos = cryptos[
cryptos["symbol"].str.contains(query.query, case=False)
| cryptos["name"].str.contains(query.query, case=False)
| cryptos["currency"].str.contains(query.query, case=False)
| cryptos["stockExchange"].str.contains(query.query, case=False)
| cryptos["exchangeShortName"].str.contains(query.query, case=False)
| cryptos["exchange"].str.contains(query.query, case=False)
]
for col in cryptos:
if cryptos[col].dtype in ("int", "float"):
cryptos[col] = cryptos[col].fillna(0)
return [
FMPCryptoSearchData.model_validate(d) for d in cryptos.to_dict("records")
]

View File

@@ -2,10 +2,8 @@
# pylint: disable=unused-argument
import asyncio
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
from warnings import warn
from typing import Any, Literal, Optional
from dateutil.relativedelta import relativedelta
from openbb_core.provider.abstract.fetcher import Fetcher
@@ -14,31 +12,23 @@ from openbb_core.provider.standard_models.currency_historical import (
CurrencyHistoricalQueryParams,
)
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import (
amake_request,
get_querystring,
)
from openbb_fmp.utils.helpers import get_interval
from pydantic import Field
from pydantic import Field, field_validator
class FMPCurrencyHistoricalQueryParams(CurrencyHistoricalQueryParams):
"""FMP Currency Historical Price Query.
Source: https://site.financialmodelingprep.com/developer/docs/#Historical-Forex-Price
Source: https://site.financialmodelingprep.com/developer/docs#forex-historical-price-eod-full
"""
__alias_dict__ = {"start_date": "from", "end_date": "to"}
__json_schema_extra__ = {
"symbol": {"multiple_items_allowed": True},
"interval": {"choices": ["1m", "5m", "15m", "30m", "1h", "4h", "1d"]},
}
interval: Literal["1m", "5m", "15m", "30m", "1h", "4h", "1d"] = Field(
interval: Literal["1m", "5m", "1h", "1d"] = Field(
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
)
@@ -46,34 +36,33 @@ class FMPCurrencyHistoricalQueryParams(CurrencyHistoricalQueryParams):
class FMPCurrencyHistoricalData(CurrencyHistoricalData):
"""FMP Currency Historical Price Data."""
__alias_dict__ = {
"change_percent": "changeOverTime",
}
adj_close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("adj_close", "")
)
change: Optional[float] = Field(
default=None,
description="Change in the price from the previous close.",
)
change_percent: Optional[float] = Field(
default=None,
description="Change in the price from the previous close, as a normalized percent.",
description="Percent change in the price from the previous close.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
@field_validator("change_percent", mode="before", check_fields=False)
@classmethod
def _normalize_percent(cls, v):
"""Normalize percent."""
return v / 100 if v else None
class FMPCurrencyHistoricalFetcher(
Fetcher[
FMPCurrencyHistoricalQueryParams,
List[FMPCurrencyHistoricalData],
list[FMPCurrencyHistoricalData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
"""FMP Currency Historical Price Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCurrencyHistoricalQueryParams:
def transform_query(params: dict[str, Any]) -> FMPCurrencyHistoricalQueryParams:
"""Transform the query params. Start and end dates are set to a 1 year interval."""
transformed_params = params
@@ -89,93 +78,29 @@ class FMPCurrencyHistoricalFetcher(
@staticmethod
async def aextract_data(
query: FMPCurrencyHistoricalQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
api_key = credentials.get("fmp_api_key") if credentials else ""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_historical_ohlc
interval = get_interval(query.interval)
base_url = "https://financialmodelingprep.com/api/v3"
query_str = get_querystring(query.model_dump(), ["symbol"])
def get_url_params(symbol: str) -> str:
url_params = f"{symbol}?{query_str}&apikey={api_key}"
url = f"{base_url}/historical-chart/{interval}/{url_params}"
if interval == "1day":
url = f"{base_url}/historical-price-full/{url_params}"
return url
symbols = query.symbol.split(",")
results = []
messages = []
async def get_one(symbol):
"""Get data for one symbol."""
url = get_url_params(symbol)
data = []
response = await amake_request(url, **kwargs)
if isinstance(response, dict) and response.get("Error Message"):
message = f"Error fetching data for {symbol}: {response.get('Error Message', '')}"
warn(message)
messages.append(message)
if not response:
message = f"No data found for {symbol}."
warn(message)
messages.append(message)
if isinstance(response, list) and len(response) > 0:
data = response
if len(symbols) > 1:
for d in data:
d["symbol"] = symbol
if isinstance(response, dict) and response.get("historical"):
data = response["historical"]
if len(symbols) > 1:
for d in data:
d["symbol"] = symbol
if data:
results.extend(data)
tasks = [get_one(symbol) for symbol in symbols]
await asyncio.gather(*tasks)
if not results:
raise EmptyDataError(
f"{str(','.join(messages)).replace(',',' ') if messages else 'No data found'}"
)
return results
return await get_historical_ohlc(query, credentials, **kwargs)
@staticmethod
def transform_data(
query: FMPCurrencyHistoricalQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPCurrencyHistoricalData]:
query: FMPCurrencyHistoricalQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPCurrencyHistoricalData]:
"""Return the transformed data."""
# Get rid of duplicate fields.
to_pop = ["label", "changePercent", "unadjustedVolume"]
results: List[FMPCurrencyHistoricalData] = []
for d in sorted(
data,
key=lambda x: (
(x["date"], x["symbol"])
if len(query.symbol.split(",")) > 1
else x["date"]
),
reverse=False,
):
_ = [d.pop(pop) for pop in to_pop if pop in d]
if d.get("unadjusted_volume") == d.get("volume"):
_ = d.pop("unadjusted_volume")
results.append(FMPCurrencyHistoricalData.model_validate(d))
return results
return [
FMPCurrencyHistoricalData.model_validate(d)
for d in sorted(
data,
key=lambda x: (
(x["date"], x["symbol"])
if len(query.symbol.split(",")) > 1
else x["date"]
),
reverse=False,
)
]

View File

@@ -16,7 +16,7 @@ from pydantic import Field
class FMPCurrencyPairsQueryParams(CurrencyPairsQueryParams):
"""FMP Currency Available Pairs Query.
Source: http://site.financialmodelingprep.com/developer/docs/stock-ticker-symbol-lookup-api/?direct=true
Source: https://site.financialmodelingprep.com/developer/docs#forex
"""
@@ -24,14 +24,10 @@ class FMPCurrencyPairsData(CurrencyPairsData):
"""FMP Currency Available Pairs Data."""
symbol: str = Field(description="Symbol of the currency pair.")
currency: str = Field(description="Base currency of the currency pair.")
stock_exchange: Optional[str] = Field(
default=None, description="Stock exchange of the currency pair."
)
exchange_short_name: Optional[str] = Field(
default=None,
description="Short name of the stock exchange of the currency pair.",
)
from_currency: str = Field(description="Base currency of the currency pair.")
to_currency: str = Field(description="Quote currency of the currency pair.")
from_name: str = Field(description="Name of the base currency.")
to_name: str = Field(description="Name of the quote currency.")
class FMPCurrencyPairsFetcher(
@@ -58,8 +54,7 @@ class FMPCurrencyPairsFetcher(
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
base_url = "https://financialmodelingprep.com/api/v3"
url = f"{base_url}/symbol/available-forex-currency-pairs?apikey={api_key}"
url = f"https://financialmodelingprep.com/stable/forex-list?apikey={api_key}"
return await get_data_many(url, **kwargs)
@@ -73,18 +68,20 @@ class FMPCurrencyPairsFetcher(
if not data:
raise EmptyDataError("The request was returned empty.")
df = DataFrame(data)
if query.query:
df = df[
df["name"].str.contains(query.query, case=False)
| df["symbol"].str.contains(query.query, case=False)
| df["currency"].str.contains(query.query, case=False)
| df["stockExchange"].str.contains(query.query, case=False)
| df["exchangeShortName"].str.contains(query.query, case=False)
df["symbol"].str.contains(query.query, case=False)
| df["fromCurrency"].str.contains(query.query, case=False)
| df["toCurrency"].str.contains(query.query, case=False)
| df["fromName"].str.contains(query.query, case=False)
| df["toName"].str.contains(query.query, case=False)
]
if len(df) == 0:
raise EmptyDataError(
f"No results were found with the query supplied. -> {query.query}"
+ " Hint: Names and descriptions are not searchable from FMP, try 3-letter symbols."
)
return [FMPCurrencyPairsData.model_validate(d) for d in df.to_dict("records")]

View File

@@ -3,7 +3,7 @@
# pylint: disable=unused-argument
from datetime import datetime
from typing import Any, Dict, List, Optional
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.currency_snapshots import (
@@ -17,7 +17,7 @@ from pydantic import Field, field_validator
class FMPCurrencySnapshotsQueryParams(CurrencySnapshotsQueryParams):
"""FMP Currency Snapshots Query.
Source: https://site.financialmodelingprep.com/developer/docs#exchange-prices-quote
Source: https://site.financialmodelingprep.com/developer/docs#all-forex-quotes
"""
__json_schema_extra__ = {"base": {"multiple_items_allowed": True}}
@@ -35,7 +35,7 @@ class FMPCurrencySnapshotsData(CurrencySnapshotsData):
"year_high": "yearHigh",
"year_low": "yearLow",
"prev_close": "previousClose",
"change_percent": "changesPercentage",
"change_percent": "changePercentage",
"last_rate_timestamp": "timestamp",
}
@@ -67,52 +67,49 @@ class FMPCurrencySnapshotsData(CurrencySnapshotsData):
class FMPCurrencySnapshotsFetcher(
Fetcher[FMPCurrencySnapshotsQueryParams, List[FMPCurrencySnapshotsData]]
Fetcher[FMPCurrencySnapshotsQueryParams, list[FMPCurrencySnapshotsData]]
):
"""FMP Currency Snapshots Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPCurrencySnapshotsQueryParams:
def transform_query(params: dict[str, Any]) -> FMPCurrencySnapshotsQueryParams:
"""Transform the query parameters."""
return FMPCurrencySnapshotsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPCurrencySnapshotsQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Extract the data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import amake_request
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
url = f"https://financialmodelingprep.com/api/v3/quotes/forex?apikey={api_key}"
url = f"https://financialmodelingprep.com/stable/batch-forex-quotes?short=false&apikey={api_key}"
return await amake_request(url, **kwargs) # type: ignore
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPCurrencySnapshotsQueryParams,
data: List[Dict],
data: list[dict],
**kwargs: Any,
) -> List[FMPCurrencySnapshotsData]:
) -> list[FMPCurrencySnapshotsData]:
"""Filter by the query parameters and validate the model."""
# pylint: disable=import-outside-toplevel
from datetime import timezone # noqa
from pandas import DataFrame, concat # noqa
from openbb_core.provider.utils.helpers import safe_fromtimestamp # noqa
from numpy import nan
from pandas import DataFrame, concat
from openbb_core.provider.utils.helpers import safe_fromtimestamp
if not data:
raise EmptyDataError("No data was returned from the FMP endpoint.")
# Drop all the zombie columns FMP returns.
df = (
DataFrame(data)
.dropna(how="all", axis=1)
.drop(columns=["exchange", "avgVolume"])
)
df = DataFrame(data).dropna(how="all", axis=1).drop(columns=["exchange"])
new_df = DataFrame()
@@ -154,8 +151,8 @@ class FMPCurrencySnapshotsFetcher(
raise EmptyDataError(
"No data was found using the applied filters. Check the parameters."
)
# Fill and replace any NaN values with NoneType.
new_df = new_df.fillna("N/A").replace("N/A", None)
new_df = new_df.replace({nan: None})
return [
FMPCurrencySnapshotsData.model_validate(d)
for d in new_df.reset_index(drop=True).to_dict(orient="records")

View File

@@ -1,83 +1,117 @@
"""FMP Discovery Filings Model."""
import math
from typing import Any, Dict, List, Optional
# pylint: disable=unused-argument
from datetime import datetime, timedelta
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.discovery_filings import (
DiscoveryFilingsData,
DiscoveryFilingsQueryParams,
)
from openbb_core.provider.utils.helpers import amake_requests, get_querystring
from pydantic import Field
from openbb_core.provider.utils.errors import EmptyDataError
from pydantic import Field, model_validator
class FMPDiscoveryFilingsQueryParams(DiscoveryFilingsQueryParams):
"""FMP Discovery Filings Query.
Source: https://site.financialmodelingprep.com/developer/docs/sec-rss-feeds-api/
Source: https://site.financialmodelingprep.com/developer/docs#search-by-form-type
"""
__alias_dict__ = {
"start_date": "from",
"end_date": "to",
"form_type": "type",
"is_done": "isDone",
"form_type": "formType",
}
is_done: Optional[bool] = Field(
limit: Optional[int] = Field(
default=None,
description="Flag for whether or not the filing is done.",
description="The maximum number of results to return. Default is 10000.",
)
@model_validator(mode="before")
@classmethod
def _check_date_range(cls, values):
"""Validate date range."""
start_date = values.get("start_date")
end_date = values.get("end_date")
# Validate date range
if start_date and end_date and end_date - start_date > timedelta(days=90):
raise ValueError("Date range cannot exceed 90 days.")
return values
class FMPDiscoveryFilingsData(DiscoveryFilingsData):
"""FMP Discovery Filings Data."""
__alias_dict__ = {"symbol": "ticker"}
final_link: str = Field(
description="Direct URL to the main document of the filing."
)
class FMPDiscoveryFilingsFetcher(
Fetcher[
FMPDiscoveryFilingsQueryParams,
List[FMPDiscoveryFilingsData],
list[FMPDiscoveryFilingsData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPDiscoveryFilingsQueryParams:
def transform_query(params: dict[str, Any]) -> FMPDiscoveryFilingsQueryParams:
"""Transform the query."""
return FMPDiscoveryFilingsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPDiscoveryFilingsQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
api_key = credentials.get("fmp_api_key") if credentials else ""
data: List[Dict] = []
# pylint: disable=import-outside-toplevel
import math # noqa
from openbb_core.provider.utils.helpers import amake_requests, get_querystring
api_key = credentials.get("fmp_api_key") if credentials else ""
data: list[dict] = []
limit = query.limit or 10000
base_url = (
"https://financialmodelingprep.com/stable/sec-filings-search/form-type"
if query.form_type
else "https://financialmodelingprep.com/stable/sec-filings-financials/"
)
start_date = (
query.start_date
or (datetime.now() - timedelta(days=89 if query.form_type else 2)).date()
)
end_date = query.end_date or datetime.now().date()
query.start_date = start_date
query.end_date = end_date
base_url = "https://financialmodelingprep.com/api/v4/rss_feed"
query_str = get_querystring(query.model_dump(by_alias=True), ["limit"])
# FMP only allows 1000 results per page
pages = math.ceil(query.limit / 1000)
pages = math.ceil(limit / 1000)
urls = [
f"{base_url}?{query_str}&page={page}&apikey={api_key}"
f"{base_url}?{query_str}&page={page}&limit=1000&apikey={api_key}"
for page in range(pages)
]
data = await amake_requests(urls, **kwargs)
return data[: query.limit]
return sorted(data, key=lambda x: x["acceptedDate"], reverse=True)
@staticmethod
def transform_data(
query: FMPDiscoveryFilingsQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPDiscoveryFilingsData]:
query: FMPDiscoveryFilingsQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPDiscoveryFilingsData]:
"""Return the transformed data."""
if not data:
raise EmptyDataError("No data was returned for the given query.")
return [FMPDiscoveryFilingsData.model_validate(d) for d in data]

View File

@@ -1,16 +1,15 @@
"""FMP Earnings Call Transcript Model."""
# pylint: disable=unused-argument
from typing import Any, Optional
from datetime import datetime
from typing import Any, Dict, List, Optional
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.earnings_call_transcript import (
EarningsCallTranscriptData,
EarningsCallTranscriptQueryParams,
)
from pydantic import field_validator
from pydantic import ConfigDict
class FMPEarningsCallTranscriptQueryParams(EarningsCallTranscriptQueryParams):
@@ -19,69 +18,157 @@ class FMPEarningsCallTranscriptQueryParams(EarningsCallTranscriptQueryParams):
Source: https://site.financialmodelingprep.com/developer/docs/earning-call-transcript-api/
"""
__json_schema_extra__ = {
"symbol": {"multiple_items_allowed": True},
"year": {"multiple_items_allowed": True},
}
class FMPEarningsCallTranscriptData(EarningsCallTranscriptData):
"""FMP Earnings Call Transcript Data."""
@field_validator("date", mode="before", check_fields=False)
@classmethod
def date_validate(cls, v):
"""Return the date as a datetime object."""
return datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
model_config = ConfigDict(
json_schema_extra={
"x-widget_config": {
"$.name": "Earnings Call Transcript",
"$.description": "Earnings calls by symbol, year, and quarter.",
"$.type": "markdown",
"$.gridData": {
"h": 20,
"w": 40,
},
"$.data": {
"dataKey": "results.content",
},
"$.params": [
{
"paramName": "year",
"label": "Fiscal Year",
"type": "number",
"description": "Fiscal year of the earnings call transcript."
+ " When not provided, or not available, the most recent will be used.",
},
{
"paramName": "quarter",
"label": "Fiscal Quarter",
"description": "Fiscal quarter of the earnings call transcript."
+ " When not provided, the latest quarter available will be used.",
},
],
"$.refetchInterval": False,
}
}
)
__alias_dict__ = {
"quarter": "period",
}
class FMPEarningsCallTranscriptFetcher(
Fetcher[
FMPEarningsCallTranscriptQueryParams,
List[FMPEarningsCallTranscriptData],
FMPEarningsCallTranscriptData,
]
):
"""FMP Earnings Call Transcript Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPEarningsCallTranscriptQueryParams:
def transform_query(params: dict[str, Any]) -> FMPEarningsCallTranscriptQueryParams:
"""Transform the query params."""
return FMPEarningsCallTranscriptQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPEarningsCallTranscriptQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> dict:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data
import warnings # noqa
from openbb_fmp.utils.helpers import (
get_available_transcript_symbols,
get_data_one,
get_transcript_dates_for_symbol,
)
from pandas import DataFrame
api_key = credentials.get("fmp_api_key") if credentials else ""
symbols = query.symbol.split(",")
years = query.year.split(",") if isinstance(query.year, str) else [query.year]
def generate_url(symbol, year):
"""Generate the URL."""
url = (
f"https://financialmodelingprep.com/api/v4/batch_earning_call_transcript/{symbol}?"
+ f"year={year}&apikey={api_key}"
available_symbols = get_available_transcript_symbols(api_key=api_key)
avail_df = DataFrame(available_symbols)
if query.symbol.upper() not in avail_df["symbol"].values:
raise OpenBBError(
ValueError(
f"Symbol {query.symbol} not found in available transcripts."
+ f"\n Available symbols include: {', '.join(sorted(avail_df['symbol'].unique().tolist()))}"
)
)
return url
symbol_transcripts = get_transcript_dates_for_symbol(
query.symbol.upper(), api_key=api_key
)
results: list = []
for symbol in symbols:
for year in years:
response = await get_data(generate_url(symbol, year), **kwargs)
if isinstance(response, list):
results.extend(response)
df_dates = DataFrame(symbol_transcripts).sort_values(by="date", ascending=False)
year = df_dates.iloc[0].fiscalYear
return results
if query.year and query.year not in df_dates.fiscalYear.values:
warnings.warn(
f"Year {query.year} not found in available transcripts for {query.symbol}."
+ f" Using latest year {year} instead."
)
year = query.year if query.year in df_dates.fiscalYear.values else year
quarter = query.quarter if query.quarter else df_dates.iloc[0].quarter
if (
query.quarter
and query.quarter
not in df_dates.query("fiscalYear == @year").quarter.values
):
warnings.warn(
f"Quarter {query.quarter} not found in available transcripts for {query.symbol} in {year}."
+ f" Using latest quarter q{df_dates.query('fiscalYear == @year').iloc[0].quarter} instead."
)
url = (
"https://financialmodelingprep.com/stable/earning-call-transcript?symbol="
+ f"{query.symbol.upper()}&year={year}&quarter={quarter}&apikey={api_key}"
)
try:
return await get_data_one(url, **kwargs)
except ValueError as e:
raise OpenBBError(
f"No transcript found for {query.symbol} in {year} Q{quarter}"
f". \n Latest available transcript is {df_dates.iloc[0].fiscalYear} Q{df_dates.iloc[0].quarter}."
) from e
@staticmethod
def transform_data(
query: FMPEarningsCallTranscriptQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPEarningsCallTranscriptData]:
query: FMPEarningsCallTranscriptQueryParams, data: dict, **kwargs: Any
) -> FMPEarningsCallTranscriptData:
"""Return the transformed data."""
return [FMPEarningsCallTranscriptData.model_validate(d) for d in data]
if not data:
raise OpenBBError(
ValueError(
f"No data found for {query.symbol} for year {query.year} and period {query.quarter}."
)
)
transcript = data.get("content", "")
output_lines: list = []
intro_lines = f"""
## {data.get('symbol')} - {data.get('year')} {data.get('period')} Earnings Call Transcript - {data.get('date')}
\n\n
"""
output_lines.append(intro_lines + "\n\n")
for line in transcript.splitlines():
section_title = line.split(":", 1)[0] if ":" in line else ""
section_line = line.split(":", 1)[1] if ":" in line else ""
if section_title and section_line:
output_lines.append(f"### **{section_title.strip()}**:" + "\n\n")
output_lines.append(section_line.strip() + "\n\n")
else:
output_lines.append(line)
data["content"] = "\n".join(output_lines)
return FMPEarningsCallTranscriptData.model_validate(data)

View File

@@ -3,8 +3,7 @@
# pylint: disable=unused-argument
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
from warnings import warn
from typing import Any, Optional
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.fetcher import Fetcher
@@ -19,14 +18,14 @@ from pydantic import Field, field_validator, model_validator
class FMPEconomicCalendarQueryParams(EconomicCalendarQueryParams):
"""FMP Economic Calendar Query.
Source: https://site.financialmodelingprep.com/developer/docs/economic-calendar-api
Source: https://site.financialmodelingprep.com/developer/docs#economics-calendar
"""
class FMPEconomicCalendarData(EconomicCalendarData):
"""FMP Economics Calendar Data.
Source: https://site.financialmodelingprep.com/developer/docs/economic-calendar-api
Source: https://site.financialmodelingprep.com/developer/docs#economics-calendar
"""
__alias_dict__ = {
@@ -44,21 +43,14 @@ class FMPEconomicCalendarData(EconomicCalendarData):
change_percent: Optional[float] = Field(
description="Percentage change since previous.",
default=None,
)
last_updated: Optional[datetime] = Field(
description="Last updated timestamp.", default=None
)
created_at: Optional[datetime] = Field(
description="Created at timestamp.", default=None
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
@field_validator(
"date", "last_updated", "created_at", mode="before", check_fields=False
)
@field_validator("change_percent", mode="before", check_fields=False)
@classmethod
def date_validate(cls, v: str):
"""Return the date as a datetime object."""
return datetime.strptime(v, "%Y-%m-%d %H:%M:%S") if v else None
def _normalize_percent(cls, v):
"""Normalize percentage value."""
return v / 100 if v else None
@model_validator(mode="before")
@classmethod
@@ -74,17 +66,19 @@ class FMPEconomicCalendarData(EconomicCalendarData):
class FMPEconomicCalendarFetcher(
Fetcher[
FMPEconomicCalendarQueryParams,
List[FMPEconomicCalendarData],
list[FMPEconomicCalendarData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
"""FMP Economic Calendar Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPEconomicCalendarQueryParams:
def transform_query(params: dict[str, Any]) -> FMPEconomicCalendarQueryParams:
"""Transform the query."""
transformed_params = params
if not transformed_params.get("start_date"):
transformed_params["start_date"] = datetime.now().date()
transformed_params["start_date"] = (
datetime.now() - timedelta(days=1)
).date()
if not transformed_params.get("end_date"):
transformed_params["end_date"] = (datetime.now() + timedelta(days=7)).date()
return FMPEconomicCalendarQueryParams(**transformed_params)
@@ -92,18 +86,19 @@ class FMPEconomicCalendarFetcher(
@staticmethod
async def aextract_data(
query: FMPEconomicCalendarQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
import asyncio # noqa
import warnings
from openbb_core.provider.utils.helpers import amake_request
from openbb_fmp.utils.helpers import response_callback
api_key = credentials.get("fmp_api_key") if credentials else ""
base_url = "https://financialmodelingprep.com/api/v3/economic_calendar?"
base_url = "https://financialmodelingprep.com/stable/economic-calendar?"
# FMP allows only 3-month windows to be queried, we need to chunk to request.
def date_range(start_date, end_date):
@@ -117,10 +112,11 @@ class FMPEconomicCalendarFetcher(
date_ranges = list(date_range(query.start_date, query.end_date))
urls = [
f"{base_url}from={start_date.strftime('%Y-%m-%d')}&to={end_date.strftime('%Y-%m-%d')}&apikey={api_key}"
f"{base_url}from={start_date.strftime('%Y-%m-%d')}" # type: ignore
+ f"&to={end_date.strftime('%Y-%m-%d')}&apikey={api_key}"
for start_date, end_date in date_ranges
]
results: List[Dict] = []
results: list[dict] = []
# We need to do this because Pytest does not seem to be able to handle `amake_requests`.
async def get_one(url):
@@ -135,7 +131,7 @@ class FMPEconomicCalendarFetcher(
except OpenBBError as e:
if len(urls) == 1 or (len(urls) > 1 and n_urls == len(urls)):
raise e from e
warn(f"Error in fetching part of the data from FMP -> {e}")
warnings.warn(f"Error in fetching part of the data from FMP -> {e}")
n_urls += 1
await asyncio.gather(*[get_one(url) for url in urls])
@@ -148,8 +144,8 @@ class FMPEconomicCalendarFetcher(
@staticmethod
def transform_data(
query: FMPEconomicCalendarQueryParams,
data: List[Dict],
data: list[dict],
**kwargs: Any,
) -> List[FMPEconomicCalendarData]:
) -> list[FMPEconomicCalendarData]:
"""Transform the data."""
return [FMPEconomicCalendarData.model_validate(d) for d in data]

View File

@@ -0,0 +1,79 @@
"""FMP Top Gainers Model."""
# pylint: disable=unused-argument
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_performance import (
EquityPerformanceData,
EquityPerformanceQueryParams,
)
from pydantic import Field, field_validator
class FMPGainersQueryParams(EquityPerformanceQueryParams):
"""FMP Gainers Query.
Source: https://site.financialmodelingprep.com/developer/docs#biggest-gainers
"""
class FMPGainersData(EquityPerformanceData):
"""FMP Gainers Data."""
__alias_dict__ = {
"percent_change": "changesPercentage",
}
exchange: str = Field(
description="Stock exchange where the security is listed.",
)
@field_validator("percent_change", mode="before", check_fields=False)
@classmethod
def _normalize_percent(cls, v):
"""Normalize percent change by removing % sign and converting to float."""
return v / 100 if v else None
class FMPGainersFetcher(Fetcher[FMPGainersQueryParams, list[FMPGainersData]]):
"""FMP Gainers Fetcher."""
@staticmethod
def transform_query(params: dict[str, Any]) -> FMPGainersQueryParams:
"""Transform query params."""
return FMPGainersQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPGainersQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> list:
"""Get the raw data from the FMP API."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
url = (
f"https://financialmodelingprep.com/stable/biggest-gainers?apikey={api_key}"
)
return await get_data_many(url)
@staticmethod
def transform_data(
query: EquityPerformanceQueryParams,
data: list[dict],
**kwargs: Any,
) -> list[FMPGainersData]:
"""Transform data."""
return [
FMPGainersData.model_validate(d)
for d in sorted(
data,
key=lambda x: x["changesPercentage"],
reverse=query.sort == "desc",
)
]

View File

@@ -2,10 +2,8 @@
# pylint: disable=unused-argument
import asyncio
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
from warnings import warn
from typing import Any, Literal, Optional
from dateutil.relativedelta import relativedelta
from openbb_core.provider.abstract.fetcher import Fetcher
@@ -14,48 +12,53 @@ from openbb_core.provider.standard_models.equity_historical import (
EquityHistoricalQueryParams,
)
from openbb_core.provider.utils.descriptions import (
DATA_DESCRIPTIONS,
QUERY_DESCRIPTIONS,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import (
amake_request,
get_querystring,
)
from openbb_fmp.utils.helpers import get_interval
from pydantic import Field
from pydantic import Field, field_validator, model_validator
class FMPEquityHistoricalQueryParams(EquityHistoricalQueryParams):
"""FMP Equity Historical Price Query.
Source: https://financialmodelingprep.com/developer/docs/#Stock-Historical-Price
Source: https://site.financialmodelingprep.com/developer/docs#historical-price-eod-full
"""
__alias_dict__ = {"start_date": "from", "end_date": "to"}
__json_schema_extra__ = {
"symbol": {"multiple_items_allowed": True},
"interval": {"choices": ["1m", "5m", "15m", "30m", "1h", "4h", "1d"]},
}
interval: Literal["1m", "5m", "15m", "30m", "1h", "4h", "1d"] = Field(
default="1d", description=QUERY_DESCRIPTIONS.get("interval", "")
)
adjustment: Literal["splits_only", "splits_and_dividends", "unadjusted"] = Field(
default="splits_only",
description="Type of adjustment for historical prices. Only applies to daily data.",
)
@model_validator(mode="before")
@classmethod
def _validate_params(cls, values: dict) -> dict:
"""Validate query parameters."""
interval = values.get("interval", "1d")
adjustment = values.get("adjustment", "splits_only")
if adjustment != "splits_only" and interval != "1d":
raise ValueError("Adjustment can only be applied to daily ('1d') interval.")
return values
class FMPEquityHistoricalData(EquityHistoricalData):
"""FMP Equity Historical Price Data."""
__alias_dict__ = {
"change_percent": "changeOverTime",
"open": "adjOpen",
"high": "adjHigh",
"low": "adjLow",
"close": "adjClose",
}
adj_close: Optional[float] = Field(
default=None, description=DATA_DESCRIPTIONS.get("adj_close", "")
)
unadjusted_volume: Optional[float] = Field(
default=None, description="Unadjusted volume of the symbol."
)
change: Optional[float] = Field(
default=None,
description="Change in the price from the previous close.",
@@ -66,17 +69,23 @@ class FMPEquityHistoricalData(EquityHistoricalData):
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
@field_validator("change_percent", mode="before", check_fields=False)
@classmethod
def _normalize_percent(cls, v):
"""Normalize percent."""
return v / 100 if v else None
class FMPEquityHistoricalFetcher(
Fetcher[
FMPEquityHistoricalQueryParams,
List[FMPEquityHistoricalData],
list[FMPEquityHistoricalData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
"""FMP Equity Historical Price Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPEquityHistoricalQueryParams:
def transform_query(params: dict[str, Any]) -> FMPEquityHistoricalQueryParams:
"""Transform the query params."""
transformed_params = params
@@ -92,96 +101,31 @@ class FMPEquityHistoricalFetcher(
@staticmethod
async def aextract_data(
query: FMPEquityHistoricalQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
api_key = credentials.get("fmp_api_key") if credentials else ""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_historical_ohlc
interval = get_interval(query.interval)
base_url = "https://financialmodelingprep.com/api/v3"
query_str = get_querystring(query.model_dump(), ["symbol", "interval"])
def get_url_params(symbol: str) -> str:
url_params = f"{symbol}?{query_str}&apikey={api_key}"
url = f"{base_url}/historical-chart/{interval}/{url_params}"
if interval == "1day":
url = f"{base_url}/historical-price-full/{url_params}"
return url
symbols = query.symbol.split(",")
results = []
messages = []
async def get_one(symbol):
"""Get data for one symbol."""
url = get_url_params(symbol)
data = []
response = await amake_request(url, **kwargs)
if isinstance(response, dict) and response.get("Error Message"):
message = f"Error fetching data for {symbol}: {response.get('Error Message', '')}"
warn(message)
messages.append(message)
if not response:
message = f"No data found for {symbol}."
warn(message)
messages.append(message)
if isinstance(response, list) and len(response) > 0:
data = response
if len(symbols) > 1:
for d in data:
d["symbol"] = symbol
if isinstance(response, dict) and response.get("historical"):
data = response["historical"]
if len(symbols) > 1:
for d in data:
d["symbol"] = symbol
if data:
results.extend(data)
tasks = [get_one(symbol) for symbol in symbols]
await asyncio.gather(*tasks)
if not results:
raise EmptyDataError(
f"{str(','.join(messages)).replace(',',' ') if messages else 'No data found'}"
)
return results
return await get_historical_ohlc(query, credentials, **kwargs)
@staticmethod
def transform_data(
query: FMPEquityHistoricalQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPEquityHistoricalData]:
query: FMPEquityHistoricalQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPEquityHistoricalData]:
"""Return the transformed data."""
# Get rid of duplicate fields.
to_pop = ["label", "changePercent"]
results: List[FMPEquityHistoricalData] = []
for d in sorted(
data,
key=lambda x: (
(x["date"], x["symbol"])
if len(query.symbol.split(",")) > 1
else x["date"]
),
reverse=False,
):
_ = [d.pop(pop) for pop in to_pop if pop in d]
if d.get("unadjusted_volume") == d.get("volume"):
_ = d.pop("unadjusted_volume")
results.append(FMPEquityHistoricalData.model_validate(d))
return results
if not data:
raise EmptyDataError("No data returned from FMP for the given query.")
return [
FMPEquityHistoricalData.model_validate(d)
for d in sorted(
data,
key=lambda x: (
(x["date"], x["symbol"])
if len(query.symbol.split(",")) > 1
else x["date"]
),
reverse=False,
)
]

View File

@@ -0,0 +1,79 @@
"""FMP Top Losers Model."""
# pylint: disable=unused-argument
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_performance import (
EquityPerformanceData,
EquityPerformanceQueryParams,
)
from pydantic import Field, field_validator
class FMPLosersQueryParams(EquityPerformanceQueryParams):
"""FMP Losers Query.
Source: https://site.financialmodelingprep.com/developer/docs#biggest-losers
"""
class FMPLosersData(EquityPerformanceData):
"""FMP Losers Data."""
__alias_dict__ = {
"percent_change": "changesPercentage",
}
exchange: str = Field(
description="Stock exchange where the security is listed.",
)
@field_validator("percent_change", mode="before", check_fields=False)
@classmethod
def _normalize_percent(cls, v):
"""Normalize percent change by removing % sign and converting to float."""
return v / 100 if v else None
class FMPLosersFetcher(Fetcher[FMPLosersQueryParams, list[FMPLosersData]]):
"""FMP Losers Fetcher."""
@staticmethod
def transform_query(params: dict[str, Any]) -> FMPLosersQueryParams:
"""Transform query params."""
return FMPLosersQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPLosersQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> list:
"""Get the raw data from the FMP API."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
url = (
f"https://financialmodelingprep.com/stable/biggest-losers?apikey={api_key}"
)
return await get_data_many(url)
@staticmethod
def transform_data(
query: EquityPerformanceQueryParams,
data: list[dict],
**kwargs: Any,
) -> list[FMPLosersData]:
"""Transform data."""
return [
FMPLosersData.model_validate(d)
for d in sorted(
data,
key=lambda x: x["changesPercentage"],
reverse=query.sort == "desc",
)
]

View File

@@ -0,0 +1,79 @@
"""FMP Equity Most Active Model."""
# pylint: disable=unused-argument
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_performance import (
EquityPerformanceData,
EquityPerformanceQueryParams,
)
from pydantic import Field, field_validator
class FMPEquityActiveQueryParams(EquityPerformanceQueryParams):
"""FMP Most Active Query.
Source: https://site.financialmodelingprep.com/developer/docs#most-active
"""
class FMPEquityActiveData(EquityPerformanceData):
"""FMP Most Active Data."""
__alias_dict__ = {
"percent_change": "changesPercentage",
}
exchange: str = Field(
description="Stock exchange where the security is listed.",
)
@field_validator("percent_change", mode="before", check_fields=False)
@classmethod
def _normalize_percent(cls, v):
"""Normalize percent change by removing % sign and converting to float."""
return v / 100 if v else None
class FMPEquityActiveFetcher(
Fetcher[FMPEquityActiveQueryParams, list[FMPEquityActiveData]]
):
"""FMP EquityActive Fetcher."""
@staticmethod
def transform_query(params: dict[str, Any]) -> FMPEquityActiveQueryParams:
"""Transform query params."""
return FMPEquityActiveQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPEquityActiveQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> list:
"""Get the raw data from the FMP API."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
url = f"https://financialmodelingprep.com/stable/most-actives?apikey={api_key}"
return await get_data_many(url)
@staticmethod
def transform_data(
query: EquityPerformanceQueryParams,
data: list[dict],
**kwargs: Any,
) -> list[FMPEquityActiveData]:
"""Transform data."""
return [
FMPEquityActiveData.model_validate(d)
for d in sorted(
data,
key=lambda x: x["changesPercentage"],
reverse=query.sort == "desc",
)
]

View File

@@ -1,77 +1,255 @@
"""FMP Equity Ownership Model."""
from datetime import (
date as dateType,
datetime,
)
from typing import Any, Dict, List, Optional
# pylint: disable=unused-argument
from datetime import date as dateType
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_ownership import (
EquityOwnershipData,
EquityOwnershipQueryParams,
)
from openbb_fmp.utils.helpers import create_url, get_data_many, most_recent_quarter
from pydantic import field_validator
from pydantic import Field, field_validator
class FMPEquityOwnershipQueryParams(EquityOwnershipQueryParams):
"""FMP Equity Ownership Query.
Source: https://site.financialmodelingprep.com/developer/docs/#Stock-Ownership-by-Holders
Source: https://site.financialmodelingprep.com/developer/docs#filings-extract-with-analytics-by-holder
"""
@field_validator("date", mode="before", check_fields=True)
@classmethod
def time_validate(cls, v: str):
"""Validate the date."""
if v is None:
v = dateType.today()
if isinstance(v, str):
base = datetime.strptime(v, "%Y-%m-%d").date()
return most_recent_quarter(base)
return most_recent_quarter(v)
year: Optional[int] = Field(
default=None,
description="Calendar year for the data. If not provided, the latest year is used.",
)
quarter: Optional[int] = Field(
default=None,
description="Calendar quarter for the data. Valid values are 1, 2, 3, or 4."
+ " If not provided, the quarter previous to the current quarter is used.",
ge=1,
le=4,
)
page: Optional[int] = Field(
default=None,
description="Page number, used in conjunction with the limit. The default is 0.",
)
limit: Optional[int] = Field(
default=None,
description="Number of items to return per page. The default is 100, which is the maximum.",
)
class FMPEquityOwnershipData(EquityOwnershipData):
"""FMP Equity Ownership Data."""
__alias_dict__ = {
"security_type": "typeOfSecurity",
"sic_industry": "industryTitle",
"weight_previous": "lastWeight",
"weight_change": "changeInWeight",
"weight_change_percent": "changeInWeightPercentage",
"market_value_previous": "lastMarketValue",
"market_value_change": "changeInMarketValue",
"market_value_change_percent": "changeInMarketValuePercentage",
"shares_previous": "lastSharesNumber",
"shares_change": "changeInSharesNumber",
"shares_change_percent": "changeInSharesNumberPercentage",
"ownership_previous": "lastOwnership",
"ownership_change": "changeInOwnership",
"ownership_change_percent": "changeInOwnershipPercentage",
"performance_percent": "performancePercentage",
"performance_previous": "lastPerformance",
"performance_change": "changeInPerformance",
}
security_name: str = Field(
description="Security name.",
)
security_type: str = Field(
description="Type or class of security.",
)
security_cusip: str = Field(
description="CUSIP of the security.",
)
shares_type: str = Field(description="Shares type.")
put_call_share: str = Field(
description="Whether the share represents a put, call, or share.",
)
investment_discretion: str = Field(
description="Investment discretion of reporting entity.",
)
sic_industry: str = Field(
description="SIC classification industry.",
)
weight: float = Field(
description="Weight relative to the total reported portfolio.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
weight_previous: float = Field(
description="Weight from the previous quarter.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
weight_change: float = Field(
description="Change in the weight from the previous quarter.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
weight_change_percent: float = Field(
description="Change in weight as a percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
market_value: int = Field(description="Market value of the stock ownership.")
market_value_previous: int = Field(
description="Market value from the previous quarter.",
)
market_value_change: int = Field(
description="Change in market value from the previous quarter.",
)
market_value_change_percent: float = Field(
description="Change in market value from the previous quarter, as a percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
shares_number: int = Field(
description="Number of controlled shares.",
)
shares_previous: int = Field(
description="Number of controlled shares from the previous quarter.",
)
shares_change: float = Field(
description="Change in shares number from the previous quarter.",
)
shares_change_percent: float = Field(
description="Change in shares number from the previous quarter, as a percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
quarter_end_price: float = Field(
description="Market price of the security at the end of the quarter.",
)
avg_price_paid: float = Field(
description="Average price paid for the shares.",
)
is_new: bool = Field(description="If the security was newly added this quarter.")
is_sold_out: bool = Field(description="If the security was sold out this quarter.")
ownership: Optional[float] = Field(
default=None,
description="Ownership stake in the security, as a percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
ownership_previous: Optional[float] = Field(
default=None,
description="Ownership stake in the security from the previous quarter, as a percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
ownership_change: Optional[float] = Field(
default=None,
description="Change in ownership stake from the previous quarter.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
ownership_change_percent: Optional[float] = Field(
default=None,
description="Change in ownership stake from the previous quarter, as a percent.",
)
holding_period: int = Field(
description="Holding period of the security.",
)
first_added: dateType = Field(
description="When the security was first reported as held.",
)
performance: Optional[float] = Field(
default=None, description="Performance value of the security holding."
)
performance_percent: Optional[float] = Field(
default=None,
description="Performance of the security holding, as a percent.",
json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
performance_previous: Optional[float] = Field(
default=None,
description="Performance value of the security holding from the previous quarter.",
)
performance_change: Optional[float] = Field(
default=None,
description="Change in value of the security holding's performance.",
)
is_counted_for_performance: bool = Field(
description="If the security is counted for the performance measurement.",
)
@field_validator(
"weight",
"weight_previous",
"weight_change",
"weight_change_percent",
"market_value_change_percent",
"shares_change_percent",
"ownership",
"ownership_previous",
"ownership_change",
"ownership_change_percent",
"performance_percent",
mode="before",
check_fields=False,
)
@classmethod
def _normalize_percent(cls, v):
"""Normalize percent values."""
return v / 100 if v else None
class FMPEquityOwnershipFetcher(
Fetcher[
FMPEquityOwnershipQueryParams,
List[FMPEquityOwnershipData],
list[FMPEquityOwnershipData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
"""FMP Equity Ownership Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPEquityOwnershipQueryParams:
def transform_query(params: dict[str, Any]) -> FMPEquityOwnershipQueryParams:
"""Transform the query params."""
return FMPEquityOwnershipQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPEquityOwnershipQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import get_data_many
from pandas import Timestamp, offsets
api_key = credentials.get("fmp_api_key") if credentials else ""
url = create_url(
4,
"institutional-ownership/institutional-holders/symbol-ownership-percent",
api_key,
query,
year = query.year if query.year else None
quarter = query.quarter if query.quarter else None
if year is None and quarter is None:
current = (Timestamp("now") + offsets.QuarterEnd()) - offsets.QuarterEnd()
quarter = current.quarter
year = current.year
elif year is None and quarter is not None:
year = Timestamp("now").year
elif year is not None and quarter is None:
current = Timestamp("now")
quarter = (
4
if year < current.year
else current.quarter - 1 if current.quarter > 1 else 1
)
url = (
"https://financialmodelingprep.com/stable/institutional-ownership/extract-analytics/holder"
+ f"?symbol={query.symbol}&year={year}&quarter={quarter}&page={query.page or 0}"
+ f"&limit={query.limit or 100}&apikey={api_key}"
)
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPEquityOwnershipQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPEquityOwnershipData]:
query: FMPEquityOwnershipQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPEquityOwnershipData]:
"""Return the transformed data."""
own = [FMPEquityOwnershipData.model_validate(d) for d in data]
own.sort(key=lambda x: x.filing_date, reverse=True)
return own
return [FMPEquityOwnershipData.model_validate(d) for d in data]

View File

@@ -9,23 +9,38 @@ from openbb_core.provider.standard_models.equity_peers import (
EquityPeersData,
EquityPeersQueryParams,
)
from pydantic import Field
class FMPEquityPeersQueryParams(EquityPeersQueryParams):
"""FMP Equity Peers Query.
Source: https://site.financialmodelingprep.com/developer/docs/#Stock-Peers
Source: https://site.financialmodelingprep.com/developer/docs#peers
"""
class FMPEquityPeersData(EquityPeersData):
"""FMP Equity Peers Data."""
__alias_dict__ = {"name": "companyName", "market_cap": "mktCap"}
name: str = Field(
description="The name of the company.",
)
price: Optional[float] = Field(
default=None,
description="The current stock price of the company.",
)
market_cap: Optional[int] = Field(
default=None,
description="The market capitalization of the company.",
)
class FMPEquityPeersFetcher(
Fetcher[
FMPEquityPeersQueryParams,
FMPEquityPeersData,
list[FMPEquityPeersData],
]
):
"""FMP Equity Peers Fetcher."""
@@ -40,23 +55,25 @@ class FMPEquityPeersFetcher(
query: FMPEquityPeersQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> dict:
) -> list:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from openbb_fmp.utils.helpers import create_url, get_data_one
from openbb_fmp.utils.helpers import get_data_many
api_key = credentials.get("fmp_api_key") if credentials else ""
url = create_url(4, "stock_peers", api_key, query)
url = (
"https://financialmodelingprep.com/stable/stock-peers?"
f"symbol={query.symbol}&apikey={api_key}"
)
return await get_data_one(url, **kwargs)
return await get_data_many(url, **kwargs)
@staticmethod
def transform_data(
query: FMPEquityPeersQueryParams, data: dict, **kwargs: Any
) -> FMPEquityPeersData:
query: FMPEquityPeersQueryParams, data: list, **kwargs: Any
) -> list[FMPEquityPeersData]:
"""Return the transformed data."""
_ = data.pop("symbol", None)
peers: list = [d for d in data.get("peersList", []) if d]
data["peersList"] = peers
return FMPEquityPeersData.model_validate(data)
return [
FMPEquityPeersData.model_validate(d)
for d in sorted(data, key=lambda x: x["mktCap"], reverse=True)
]

View File

@@ -2,12 +2,10 @@
# pylint: disable=unused-argument
import asyncio
from datetime import (
date as dateType,
)
from typing import Any, Dict, List, Optional
from warnings import warn
from typing import Any, Optional
from openbb_core.provider.abstract.data import ForceInt
from openbb_core.provider.abstract.fetcher import Fetcher
@@ -16,15 +14,13 @@ from openbb_core.provider.standard_models.equity_info import (
EquityInfoQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request
from openbb_fmp.utils.helpers import response_callback
from pydantic import Field, field_validator, model_validator
from pydantic import ConfigDict, Field, field_validator, model_validator
class FMPEquityProfileQueryParams(EquityInfoQueryParams):
"""FMP Equity Profile Query.
Source: https://site.financialmodelingprep.com/developer/docs/companies-key-stats-free-api/
Source: https://site.financialmodelingprep.com/developer/docs#profile-symbol
"""
__json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
@@ -47,12 +43,12 @@ class FMPEquityProfileData(EquityInfoData):
"employees": "fullTimeEmployees",
"long_description": "description",
"first_stock_price_date": "ipoDate",
"market_cap": "mktCap",
"last_price": "price",
"volume_avg": "volAvg",
"annualized_dividend_amount": "lastDiv",
"volume_avg": "averageVolume",
"annualized_dividend_amount": "lastDividend",
}
__json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
model_config = ConfigDict(extra="ignore")
is_etf: bool = Field(description="If the symbol is an ETF.")
is_actively_trading: bool = Field(description="If the company is actively trading.")
@@ -110,37 +106,43 @@ class FMPEquityProfileData(EquityInfoData):
class FMPEquityProfileFetcher(
Fetcher[
FMPEquityProfileQueryParams,
List[FMPEquityProfileData],
list[FMPEquityProfileData],
]
):
"""FMP Equity Profile Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPEquityProfileQueryParams:
def transform_query(params: dict[str, Any]) -> FMPEquityProfileQueryParams:
"""Transform the query params."""
return FMPEquityProfileQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPEquityProfileQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
import asyncio # noqa
import warnings
from openbb_core.provider.utils.helpers import amake_request
from openbb_fmp.utils.helpers import response_callback
api_key = credentials.get("fmp_api_key") if credentials else ""
symbols = query.symbol.split(",")
base_url = "https://financialmodelingprep.com/api/v3"
base_url = "https://financialmodelingprep.com/stable/"
results = []
results: list = []
async def get_one(symbol):
"""Get data for one symbol."""
url = f"{base_url}/profile/{symbol}?apikey={api_key}"
url = f"{base_url}profile?symbol={symbol}&apikey={api_key}"
result = await amake_request(
url, response_callback=response_callback, **kwargs
)
if not result:
warn(f"Symbol Error: No data found for {symbol}")
warnings.warn(f"Symbol Error: No data found for {symbol}")
if result:
results.append(result[0])
@@ -158,27 +160,16 @@ class FMPEquityProfileFetcher(
@staticmethod
def transform_data(
query: FMPEquityProfileQueryParams,
data: List[Dict],
data: list[dict],
**kwargs: Any,
) -> List[FMPEquityProfileData]:
) -> list[FMPEquityProfileData]:
"""Return the transformed data."""
results: List[FMPEquityProfileData] = []
results: list[FMPEquityProfileData] = []
for d in data:
d["year_low"], d["year_high"] = (
d.get("range", "-").split("-") if d.get("range") else (None, None)
d.pop("range", "-").split("-") if d.get("range") else (None, None)
)
# Clear out fields that don't belong and can be had elsewhere.
entries_to_remove = (
"exchangeShortName",
"defaultImage",
"dcf",
"dcfDiff",
"changes",
"range",
)
for key in entries_to_remove:
d.pop(key, None)
results.append(FMPEquityProfileData.model_validate(d))
return results

View File

@@ -2,31 +2,22 @@
# pylint: disable=unused-argument
import asyncio
from datetime import (
date as dateType,
datetime,
timezone,
)
from typing import Any, Dict, List, Optional, Union
from warnings import warn
from datetime import date as dateType
from typing import Any, Optional, Union
from openbb_core.provider.abstract.data import ForceInt
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_quote import (
EquityQuoteData,
EquityQuoteQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request, safe_fromtimestamp
from openbb_fmp.utils.helpers import get_querystring, response_callback
from pydantic import Field, field_validator
class FMPEquityQuoteQueryParams(EquityQuoteQueryParams):
"""FMP Equity Quote Query.
Source: https://financialmodelingprep.com/developer/docs/#Stock-Historical-Price
Source: https://site.financialmodelingprep.com/developer/docs#quote
"""
__json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
@@ -36,60 +27,41 @@ class FMPEquityQuoteData(EquityQuoteData):
"""FMP Equity Quote Data."""
__alias_dict__ = {
"price_avg50": "priceAvg50",
"price_avg200": "priceAvg200",
"ma50": "priceAvg50",
"ma200": "priceAvg200",
"last_timestamp": "timestamp",
"high": "dayHigh",
"low": "dayLow",
"last_price": "price",
"change_percent": "changesPercentage",
"change_percent": "changePercentage",
"prev_close": "previousClose",
}
price_avg50: Optional[float] = Field(
ma50: Optional[float] = Field(
default=None, description="50 day moving average price."
)
price_avg200: Optional[float] = Field(
ma200: Optional[float] = Field(
default=None, description="200 day moving average price."
)
avg_volume: Optional[ForceInt] = Field(
default=None,
description="Average volume over the last 10 trading days.",
)
market_cap: Optional[float] = Field(
default=None, description="Market cap of the company."
)
shares_outstanding: Optional[ForceInt] = Field(
default=None, description="Number of shares outstanding."
)
eps: Optional[float] = Field(default=None, description="Earnings per share.")
pe: Optional[float] = Field(default=None, description="Price earnings ratio.")
earnings_announcement: Optional[datetime] = Field(
default=None, description="Upcoming earnings announcement date."
)
@field_validator("last_timestamp", mode="before", check_fields=False)
@classmethod
def validate_last_timestamp(cls, v: Union[str, int]) -> Optional[dateType]:
"""Return the date as a datetime object."""
# pylint: disable=import-outside-toplevel
from datetime import timezone # noqa
from openbb_core.provider.utils.helpers import safe_fromtimestamp
if v:
v = int(v) if isinstance(v, str) else v
return safe_fromtimestamp(v, tz=timezone.utc)
return None
@field_validator("earnings_announcement", mode="before", check_fields=False)
@classmethod
def timestamp_validate(cls, v: str) -> Optional[dateType]:
"""Return the datetime string as a datetime object."""
if v:
dt = datetime.strptime(v, "%Y-%m-%dT%H:%M:%S.%f%z")
dt = dt.replace(microsecond=0)
timestamp = dt.timestamp()
return safe_fromtimestamp(timestamp, tz=timezone.utc)
return safe_fromtimestamp(v, tz=timezone.utc).replace(tzinfo=None)
return None
@field_validator("change_percent", mode="after", check_fields=False)
@classmethod
def normalize_percent(cls, v): # pylint: disable=E0213
def _normalize_percent(cls, v): # pylint: disable=E0213
"""Return the percent value as a normalized value."""
return float(v) / 100 if v else None
@@ -97,40 +69,42 @@ class FMPEquityQuoteData(EquityQuoteData):
class FMPEquityQuoteFetcher(
Fetcher[
FMPEquityQuoteQueryParams,
List[FMPEquityQuoteData],
list[FMPEquityQuoteData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
"""FMP Equity Quote Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPEquityQuoteQueryParams:
def transform_query(params: dict[str, Any]) -> FMPEquityQuoteQueryParams:
"""Transform the query params."""
return FMPEquityQuoteQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPEquityQuoteQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
import asyncio # noqa
import warnings
from openbb_core.provider.utils.helpers import amake_request
from openbb_fmp.utils.helpers import response_callback
api_key = credentials.get("fmp_api_key") if credentials else ""
base_url = "https://financialmodelingprep.com/api/v3"
query_str = get_querystring(query.model_dump(), ["symbol"])
base_url = "https://financialmodelingprep.com/stable/quote?"
symbols = query.symbol.split(",")
results: list = []
async def get_one(symbol):
"""Get data for one symbol."""
url = f"{base_url}/quote/{symbol}?{query_str}&apikey={api_key}"
url = f"{base_url}symbol={symbol}&apikey={api_key}"
result = await amake_request(
url, response_callback=response_callback, **kwargs
)
if not result or len(result) == 0:
warn(f"Symbol Error: No data found for {symbol}")
warnings.warn(f"Symbol Error: No data found for {symbol}")
if result and len(result) > 0:
results.extend(result)
@@ -146,7 +120,7 @@ class FMPEquityQuoteFetcher(
@staticmethod
def transform_data(
query: FMPEquityQuoteQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPEquityQuoteData]:
query: FMPEquityQuoteQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPEquityQuoteData]:
"""Return the transformed data."""
return [FMPEquityQuoteData.model_validate(d) for d in data]

View File

@@ -2,7 +2,7 @@
# pylint: disable=unused-argument
from typing import Any, Dict, List, Literal, Optional
from typing import Any, Optional
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_screener import (
@@ -10,13 +10,50 @@ from openbb_core.provider.standard_models.equity_screener import (
EquityScreenerQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_fmp.utils.definitions import EXCHANGES, SECTORS, Exchanges, Sectors
from pydantic import Field
from openbb_fmp.utils.definitions import (
Countries,
Exchanges,
IndustryChoices,
Sectors,
)
from pydantic import Field, field_validator
class FMPEquityScreenerQueryParams(EquityScreenerQueryParams):
"""FMP Equity Screener Query."""
__json_schema_extra__ = {
"industry": {
"x-widget_config": {
"options": IndustryChoices,
},
},
"sector": {
"x-widget_config": {
"options": [
{"label": sector.replace("_", " ").title(), "value": sector}
for sector in Sectors.__args__
],
},
},
"exchange": {
"x-widget_config": {
"options": [
{"label": exchange.upper(), "value": exchange}
for exchange in Exchanges.__args__
],
},
},
"country": {
"x-widget_config": {
"options": [
{"label": country.upper(), "value": country}
for country in Countries.__args__
],
},
},
}
__alias_dict__ = {
"mktcap_min": "marketCapMoreThan",
"mktcap_max": "marketCapLowerThan",
@@ -30,25 +67,19 @@ class FMPEquityScreenerQueryParams(EquityScreenerQueryParams):
"dividend_max": "dividendLowerThan",
"is_active": "isActivelyTrading",
"is_etf": "isEtf",
}
__json_schema_extra__ = {
"exchange": {
"multiple_items_allowed": False,
"choices": EXCHANGES,
},
"sector": {
"multiple_items_allowed": False,
"choices": SECTORS,
},
"is_fund": "isFund",
"all_share_classes": "includeAllShareClasses",
}
mktcap_min: Optional[int] = Field(
default=None, description="Filter by market cap greater than this value."
default=None,
description="Filter by market cap greater than this value.",
title="Mkt Cap Min",
)
mktcap_max: Optional[int] = Field(
default=None,
description="Filter by market cap less than this value.",
title="Mkt Cap Max",
)
price_min: Optional[float] = Field(
default=None,
@@ -82,26 +113,49 @@ class FMPEquityScreenerQueryParams(EquityScreenerQueryParams):
default=None,
description="Filter by dividend amount less than this value.",
)
is_etf: Optional[bool] = Field(
default=False,
description="If true, returns only ETFs.",
sector: Optional[Sectors] = Field(
default=None,
description="Filter by sector.",
)
is_active: Optional[bool] = Field(
default=True,
description="If false, returns only inactive tickers.",
industry: Optional[str] = Field(
default=None,
description="Filter by industry.",
)
sector: Optional[Sectors] = Field(default=None, description="Filter by sector.")
industry: Optional[str] = Field(default=None, description="Filter by industry.")
country: Optional[str] = Field(
country: Optional[Countries] = Field(
default=None, description="Filter by country, as a two-letter country code."
)
exchange: Optional[Exchanges] = Field(
default=None, description="Filter by exchange."
)
is_etf: Optional[bool] = Field(
default=None,
description="If true, includes ETFs.",
)
is_active: Optional[bool] = Field(
default=None,
description="If false, returns only inactive tickers.",
)
is_fund: Optional[bool] = Field(
default=None,
description="If true, includes funds.",
)
all_share_classes: Optional[bool] = Field(
default=None,
description="If true, includes all share classes of a equity.",
)
limit: Optional[int] = Field(
default=50000, description="Limit the number of results to return."
)
@field_validator("industry")
@classmethod
def _validate_industry(cls, v):
"""Validate industry."""
industries = [v["value"] for v in IndustryChoices]
if v and v not in industries + ["all"]:
raise ValueError(f"Industry must be one of {', '.join(industries)}")
return v
class FMPEquityScreenerData(EquityScreenerData):
"""FMP Equity Screener Data."""
@@ -120,7 +174,8 @@ class FMPEquityScreenerData(EquityScreenerData):
description="The market cap of ticker.", default=None
)
sector: Optional[str] = Field(
description="The sector the ticker belongs to.", default=None
description="The sector the ticker belongs to.",
default=None,
)
industry: Optional[str] = Field(
description="The industry ticker belongs to.", default=None
@@ -146,10 +201,13 @@ class FMPEquityScreenerData(EquityScreenerData):
description="The two-letter country abbreviation where the head office is located.",
default=None,
)
is_etf: Optional[Literal[True, False]] = Field(
is_etf: Optional[bool] = Field(
description="Whether the ticker is an ETF.", default=None
)
actively_trading: Optional[Literal[True, False]] = Field(
is_fund: Optional[bool] = Field(
description="Whether the ticker is a fund.", default=None
)
actively_trading: Optional[bool] = Field(
description="Whether the ETF is actively trading.",
default=None,
)
@@ -158,64 +216,82 @@ class FMPEquityScreenerData(EquityScreenerData):
class FMPEquityScreenerFetcher(
Fetcher[
FMPEquityScreenerQueryParams,
List[FMPEquityScreenerData],
list[FMPEquityScreenerData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
"""FMP Equity Screener Fetcher."""
@staticmethod
def transform_query(params: Dict[str, Any]) -> FMPEquityScreenerQueryParams:
def transform_query(params: dict[str, Any]) -> FMPEquityScreenerQueryParams:
"""Transform the query."""
return FMPEquityScreenerQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPEquityScreenerQueryParams,
credentials: Optional[Dict[str, str]],
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
) -> list[dict]:
"""Return the raw data from the FMP endpoint."""
# pylint: disable=import-outside-toplevel
from copy import deepcopy # noqa
from openbb_fmp.utils.helpers import create_url, get_data # noqa
from openbb_core.provider.utils.helpers import get_querystring
from openbb_fmp.utils.helpers import get_data
api_key = credentials.get("fmp_api_key") if credentials else ""
_query = deepcopy(query)
if _query.sector is not None:
_query.sector = _query.sector.replace("_", " ").title()
url = create_url(
version=3,
endpoint="stock-screener",
api_key=api_key,
query=_query,
exclude=["query", "is_symbol", "industry"],
).replace(" ", "%20")
sector: str = (
query.sector.replace("_", " ").title().replace(" ", "%20")
if query.sector
else ""
)
industry_map = {v["value"]: v["label"] for v in IndustryChoices}
industry: str = (
industry_map.get(query.industry, query.industry) if query.industry else ""
)
industry = (
industry.replace(" & ", "%20%26%20")
.replace(" ", "%20")
.replace("/", "%2F")
.replace("-", "%2D")
.replace(",", "%2C")
)
exchange: str = query.exchange.upper() if query.exchange else ""
country: str = query.country.upper() if query.country else ""
query.is_active = True if query.is_active is None else query.is_active
query.is_etf = False if query.is_etf is None else query.is_etf
query.is_fund = False if query.is_fund is None else query.is_fund
query.all_share_classes = (
False if query.all_share_classes is None else query.all_share_classes
)
query_dict = query.model_dump(exclude_none=True, by_alias=True)
if sector:
query_dict["sector"] = sector
if industry:
query_dict["industry"] = industry
if exchange:
query_dict["exchange"] = exchange
if country:
query_dict["country"] = country
query_str = (
get_querystring(query_dict, exclude=["query"])
.replace("True", "true")
.replace("False", "false")
)
base_url = "https://financialmodelingprep.com/stable/company-screener"
url = f"{base_url}?{query_str}&apikey={api_key}"
return await get_data(url, **kwargs) # type: ignore
@staticmethod
def transform_data(
query: FMPEquityScreenerQueryParams, data: List[Dict], **kwargs: Any
) -> List[FMPEquityScreenerData]:
query: FMPEquityScreenerQueryParams, data: list[dict], **kwargs: Any
) -> list[FMPEquityScreenerData]:
"""Return the transformed data."""
# pylint: disable=import-outside-toplevel
from pandas import DataFrame
if not data:
raise EmptyDataError("The request was returned empty.")
results = DataFrame(data)
if query.industry:
results = results[
results["sector"].str.contains(query.industry, case=False)
| results["industry"].str.contains(query.industry, case=False)
]
results["companyName"] = results["companyName"].fillna("-").replace("-", "")
for col in results:
if results[col].dtype in ("int", "float"):
results[col] = results[col].fillna(0).replace(0, None)
return [
FMPEquityScreenerData.model_validate(d)
for d in results.sort_values(by="marketCap", ascending=False).to_dict(
"records"
)
for d in sorted(data, key=lambda x: x["marketCap"], reverse=True)
]

View File

@@ -1,152 +0,0 @@
"""FMP Equity Valuation Multiples Model."""
# pylint: disable=unused-argument
import asyncio
from typing import Any, Dict, List, Optional
from warnings import warn
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.equity_valuation_multiples import (
EquityValuationMultiplesData,
EquityValuationMultiplesQueryParams,
)
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_core.provider.utils.helpers import amake_request
from openbb_fmp.utils.helpers import create_url, response_callback
class FMPEquityValuationMultiplesQueryParams(EquityValuationMultiplesQueryParams):
"""FMP Equity Valuation Multiples Query.
Source: https://site.financialmodelingprep.com/developer/docs/#Company-Key-Metrics
"""
__json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
class FMPEquityValuationMultiplesData(EquityValuationMultiplesData):
"""FMP Equity Valuation Multiples Data."""
__alias_dict__ = {
"revenue_per_share_ttm": "revenuePerShareTTM",
"net_income_per_share_ttm": "netIncomePerShareTTM",
"operating_cash_flow_per_share_ttm": "operatingCashFlowPerShareTTM",
"free_cash_flow_per_share_ttm": "freeCashFlowPerShareTTM",
"cash_per_share_ttm": "cashPerShareTTM",
"book_value_per_share_ttm": "bookValuePerShareTTM",
"tangible_book_value_per_share_ttm": "tangibleBookValuePerShareTTM",
"shareholders_equity_per_share_ttm": "shareholdersEquityPerShareTTM",
"interest_debt_per_share_ttm": "interestDebtPerShareTTM",
"market_cap_ttm": "marketCapTTM",
"enterprise_value_ttm": "enterpriseValueTTM",
"pe_ratio_ttm": "peRatioTTM",
"price_to_sales_ratio_ttm": "priceToSalesRatioTTM",
"pocf_ratio_ttm": "pocfratioTTM",
"pfcf_ratio_ttm": "pfcfRatioTTM",
"pb_ratio_ttm": "pbRatioTTM",
"ptb_ratio_ttm": "ptbRatioTTM",
"ev_to_sales_ttm": "evToSalesTTM",
"enterprise_value_over_ebitda_ttm": "enterpriseValueOverEBITDATTM",
"ev_to_operating_cash_flow_ttm": "evToOperatingCashFlowTTM",
"ev_to_free_cash_flow_ttm": "evToFreeCashFlowTTM",
"earnings_yield_ttm": "earningsYieldTTM",
"free_cash_flow_yield_ttm": "freeCashFlowYieldTTM",
"debt_to_equity_ttm": "debtToEquityTTM",
"debt_to_assets_ttm": "debtToAssetsTTM",
"debt_to_market_cap_ttm": "debtToMarketCapTTM",
"net_debt_to_ebitda_ttm": "netDebtToEBITDATTM",
"current_ratio_ttm": "currentRatioTTM",
"interest_coverage_ttm": "interestCoverageTTM",
"income_quality_ttm": "incomeQualityTTM",
"dividend_yield_ttm": "dividendYieldTTM",
"payout_ratio_ttm": "payoutRatioTTM",
"sales_general_and_administrative_to_revenue_ttm": "salesGeneralAndAdministrativeToRevenueTTM",
"research_and_development_to_revenue_ttm": "researchAndDevelopementToRevenueTTM",
"intangibles_to_total_assets_ttm": "intangiblesToTotalAssetsTTM",
"capex_to_operating_cash_flow_ttm": "capexToOperatingCashFlowTTM",
"capex_to_revenue_ttm": "capexToRevenueTTM",
"capex_to_depreciation_ttm": "capexToDepreciationTTM",
"stock_based_compensation_to_revenue_ttm": "stockBasedCompensationToRevenueTTM",
"graham_number_ttm": "grahamNumberTTM",
"roic_ttm": "roicTTM",
"return_on_tangible_assets_ttm": "returnOnTangibleAssetsTTM",
"graham_net_net_ttm": "grahamNetNetTTM",
"working_capital_ttm": "workingCapitalTTM",
"tangible_asset_value_ttm": "tangibleAssetValueTTM",
"net_current_asset_value_ttm": "netCurrentAssetValueTTM",
"invested_capital_ttm": "investedCapitalTTM",
"average_receivables_ttm": "averageReceivablesTTM",
"average_payables_ttm": "averagePayablesTTM",
"average_inventory_ttm": "averageInventoryTTM",
"days_sales_outstanding_ttm": "daysSalesOutstandingTTM",
"days_payables_outstanding_ttm": "daysPayablesOutstandingTTM",
"days_of_inventory_on_hand_ttm": "daysOfInventoryOnHandTTM",
"receivables_turnover_ttm": "receivablesTurnoverTTM",
"payables_turnover_ttm": "payablesTurnoverTTM",
"inventory_turnover_ttm": "inventoryTurnoverTTM",
"roe_ttm": "roeTTM",
"capex_per_share_ttm": "capexPerShareTTM",
"dividend_yield_percentage_ttm": "dividendYieldPercentageTTM",
"dividend_to_market_cap_ttm": "debtToMarketCapTTM",
"dividend_per_share_ttm": "dividendPerShareTTM",
}
class FMPEquityValuationMultiplesFetcher(
Fetcher[
FMPEquityValuationMultiplesQueryParams,
List[FMPEquityValuationMultiplesData],
]
):
"""Transform the query, extract and transform the data from the FMP endpoints."""
@staticmethod
def transform_query(
params: Dict[str, Any]
) -> FMPEquityValuationMultiplesQueryParams:
"""Transform the query params."""
return FMPEquityValuationMultiplesQueryParams(**params)
@staticmethod
async def aextract_data(
query: FMPEquityValuationMultiplesQueryParams,
credentials: Optional[Dict[str, str]],
**kwargs: Any,
) -> List[Dict]:
"""Return the raw data from the FMP endpoint."""
api_key = credentials.get("fmp_api_key") if credentials else ""
symbols = query.symbol.split(",")
results = []
async def get_one(symbol):
"""Get data for one symbol."""
url = create_url(
3, f"key-metrics-ttm/{symbol}", api_key, query, exclude=["symbol"]
)
result = await amake_request(
url, response_callback=response_callback, **kwargs
)
if not result:
warn(f"Symbol Error: No data found for {symbol}.")
if result:
data = [{**d, "symbol": symbol} for d in result]
results.extend(data)
await asyncio.gather(*[get_one(symbol) for symbol in symbols])
if not results:
raise EmptyDataError("No data found for given symbols.")
return results
@staticmethod
def transform_data(
query: FMPEquityValuationMultiplesQueryParams,
data: List[Dict],
**kwargs: Any,
) -> List[FMPEquityValuationMultiplesData]:
"""Return the transformed data."""
return [FMPEquityValuationMultiplesData.model_validate(d) for d in data]

Some files were not shown because too many files have changed in this diff Show More