mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
[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:
3
.codespell.skip
vendored
3
.codespell.skip
vendored
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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)])
|
||||
@@ -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", "")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -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.",
|
||||
)
|
||||
|
||||
@@ -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", ""))
|
||||
|
||||
@@ -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", ""),
|
||||
)
|
||||
|
||||
@@ -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)."
|
||||
|
||||
@@ -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", "")
|
||||
)
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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},
|
||||
)
|
||||
|
||||
@@ -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.",
|
||||
)
|
||||
|
||||
@@ -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.",
|
||||
)
|
||||
|
||||
@@ -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"))
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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},
|
||||
)
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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", "")
|
||||
)
|
||||
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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", "")
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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},
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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()))
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
),
|
||||
|
||||
@@ -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()))
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
),
|
||||
(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
),
|
||||
(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'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'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'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'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. It said that “sales had not been as expected”.</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 <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> 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, staff in the
|
||||
company’s wealth management division 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 <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 & 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'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's
|
||||
net worth has continued to climb since the COVID-19 pandemic, 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> On
|
||||
Tuesday, KPMG chief economist <strong>Diane Swonk</strong> shared a <a href=\\\"https://twitter.com/DianeSwonk/status/1706663509331718197\\\">chart</a> on
|
||||
X showing household net worth grew across all incomes from the fourth
|
||||
quarter of 2019 to the second quarter of this year. </p>\\r\\n\\r\\n<p>After
|
||||
adjusting for inflation, the top 1% saw the highest net worth increase
|
||||
of 22%. The net worth of those in the 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%. </p>\\r\\n\\r\\n<p><a
|
||||
href=\\\"https://www.federalreserve.gov/releases/z1/\\\">Data</a> from the
|
||||
Federal Reserve released earlier this month showed household wealth hit
|
||||
record highs of $154.28 trillion last quarter, fueled by 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. </p>\\r\\n\\r\\n<p>Households
|
||||
have now fully regained the losses driven by declining real estate values
|
||||
and 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. </p>\\r\\n\\r\\n<p><em>Related
|
||||
Link: <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> SPDR S&P 500 ETF Trust</strong>
|
||||
(NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/SPY#NYSE\\\">SPY</a>)
|
||||
was 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 <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%. </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 & 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 & 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 Oct. 16, according to a
|
||||
recent communication to its users.</p>\\r\\n\\r\\n<p>The bank stated, "If
|
||||
we think you're making a payment related to crypto assets, we'll decline
|
||||
it," suggesting 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&utm_source=onecryptofeed&utm_campaign=headlines\\\">reported</a>.</p>\\r\\n\\r\\n<p>This
|
||||
move by Chase comes ahead of the much-anticipated Benzinga'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.'s
|
||||
financial regulatory body, had to mediate dialogues between banks and
|
||||
cryptocurrency entities due to the banking sector's hesitancy in serving
|
||||
the crypto industry.</p>\\r\\n\\r\\n<p><em>Also Read: <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's
|
||||
decision, as detailed in the communication reviewed by CoinDesk, stemmed 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, “We’re committed to helping
|
||||
keep our customers’ money safe and secure. We’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.”</p>\\r\\n\\r\\n<p><em>Read
|
||||
Next: <a href=\\\"https://www.benzinga.com/markets/cryptocurrency/23/09/34900667/taiwan-regulators-unveil-guiding-principles-to-regulate-digital-assets-crypto\\\">Taiwan
|
||||
Regulators Unveil 'Guiding Principles' To Regulate Digital Assets,
|
||||
Crypto</a></em></p>\\r\\n\\r\\n<p><em>Meet and engage with transformative </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> business
|
||||
leaders and investors at Benzinga's exclusive event: </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: </em><em><a href=\\\"https://www.benzinga.com/go/crypto-conference-article-link\\\"
|
||||
rel=\\\"noopener noreferrer\\\" target=\\\"_blank\\\">Get yours</a></em><em>! </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 & 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's
|
||||
Sporting Goods Inc\_(NYSE: DKS) disclosed its intentions to onboard 8,600
|
||||
seasonal teammates across the nation in\_\",\"body\":\"<p><strong>Dick's
|
||||
Sporting Goods Inc</strong> (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 <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 <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> interim staff for the festive rush.</p>\\r\\n\\r\\n<p>The
|
||||
reveal from Dick's follows similar conservative seasonal employment goals
|
||||
recently unveiled by giants like <strong>Target Corporation</strong> (NYSE:<a
|
||||
class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/TGT#NYSE\\\">TGT</a>)
|
||||
and <strong>Macy's Inc</strong> (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 & 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, <strong>Amazon.Com,
|
||||
Inc.</strong> (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>: </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'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's Sporting Goods, <strong>Nike,
|
||||
Inc.</strong>(NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/NKE#NYSE\\\">NKE</a>),
|
||||
and <strong>Foot Locker, Inc.</strong> (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> (NASDAQ:<a
|
||||
class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/BLNK#NASDAQ\\\">BLNK</a>)
|
||||
disclosed an agreement with Parkopedia to offer increased accessibility <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. </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>"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's excellence in serving drivers through convenience in parking
|
||||
accessibility and Blink'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." said <strong>Jim
|
||||
Nemec</strong>, chief revenue officer for Blink Charging. </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 <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: </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 on the
|
||||
last check Tuesday. </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.’s
|
||||
short-acting psychedelic therapies developer <strong>Small Pharma</strong>’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 “native”
|
||||
DMT compound, SPL026 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. 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 no
|
||||
drug-related serious adverse events 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 non-SSRI group) the majority
|
||||
of them resolved during the dosing visit.</p>\\r\\n\\t</li>\\r\\n\\t<li>\\r\\n\\t<p>Results
|
||||
reaffirmed SPL026’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 “marked,”
|
||||
greater antidepressant effects in the SSRI cohort, suggesting a “potentially
|
||||
enhanced efficacy effect” 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 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 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 “very pleased” to find patients may not
|
||||
need to withdraw from their SSRIs, they were not expecting to see
|
||||
“such a marked difference in efficacy” when administering SPL026
|
||||
in combination with SSRIs compared to SPL026 alone. </p>\\r\\n\\r\\n<p>“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,” Routledge explained.</p>\\r\\n\\r\\n<p><strong>CEO George
|
||||
Tziras</strong> added that these safety and tolerability results “further
|
||||
support” Small Pharma’s DMT programs’ patient access strategy,
|
||||
and further stated the team looks forward to exploring these findings “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> (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/CYBN#NYSE\\\">CYBN</a>),
|
||||
subject to completion of the proposed arrangement transaction.”</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’s best, you’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 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 & 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 & 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.'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.'s </strong>(OTC:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/QABSY#OTC\\\">QABSY</a>)
|
||||
Chairman <strong>Richard Goyder</strong> is facing calls for his resignation
|
||||
from a pilots union over downbeat company morale.</p>\\r\\n\\r\\n<p>The
|
||||
Australian and International Pilots Association (AIPA), which represents most
|
||||
of Qantas' roughly 3,000 pilots, wrote to the <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's new CEO, Vanessa Hudson</a>, demanding
|
||||
that Goyder resign, according to a Reuters report.</p>\\r\\n\\r\\n<p>"We
|
||||
have totally lost confidence in Goyder and his board," 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> </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' 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'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 "customer pain points"
|
||||
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> Shares
|
||||
of QABSY were down 1.22% to $16.40 on Tuesday. </p>\\r\\n\\r\\n<p><em>Also
|
||||
Read: </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 – don'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'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 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 <a href=\\\"\\\">Benzinga Cannabis Capital
|
||||
Conference</a> is happening this week in Chicago September 27-28. <a
|
||||
href=\\\"\\\">Get your tickets today</a> 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 <a href=\\\"https://unsplash.com/@jaimespaniol?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText\\\">Jaime
|
||||
Spaniol</a> on <a href=\\\"https://unsplash.com/photos/-L0N74GWsq8?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText\\\">Unsplash</a>. </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's note:
|
||||
This story has been updated to reflect the nature of the lawsuit against Amazon
|
||||
and its intent.\_\",\"body\":\"<p><em><strong>Editor's note:</strong>
|
||||
This story has been updated to reflect the nature of the lawsuit against Amazon
|
||||
and its intent. </em></p>\\r\\n\\r\\n<p><strong>Amazon.com, Inc</strong> (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, <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> monopolistic power
|
||||
against rivals. </p>\\r\\n\\r\\n<p>Amazon'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. </p>\\r\\n\\r\\n<p>The
|
||||
complaint alleges that Amazon's schemes affect billions of dollars in
|
||||
retail sales, impacting businesses and <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. </p>\\r\\n\\r\\n<p>The
|
||||
lawsuit aims to hold Amazon accountable and restore fair competition, according
|
||||
to the FTC. </p>\\r\\n\\r\\n<p><strong>John Newman</strong>, deputy director
|
||||
of the FTC's Bureau of Competition, said Amazon's monopolistic behavior
|
||||
results in higher consumer prices and high fees for online sellers. </p>\\r\\n\\r\\n<p>The
|
||||
complaint focuses on what the FTC said is Amazon'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' "Prime" eligibility on costly fulfillment
|
||||
services, limiting competitors' ability to compete effectively.</p>\\r\\n\\r\\n<p>Amazon'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. </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. </p>\\r\\n\\r\\n<p>The
|
||||
FTC and state partners seek a permanent injunction to stop what they say is
|
||||
Amazon'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, "Amazon
|
||||
now takes one of every $2 that a seller makes," 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. </p>\\r\\n\\r\\n<p><strong>David Zapolsky</strong>,
|
||||
Amazon's Senior Vice President of Global Public Policy and General Counsel,
|
||||
said the FTC is straying from its consumer and competition protection mission. </p>\\r\\n\\r\\n<p>He
|
||||
argued that the practices challenged by the FTC have encouraged competition
|
||||
and innovation in the retail industry. </p>\\r\\n\\r\\n<p>Zapolsky warned
|
||||
that if the FTC prevailed, it could result in fewer product choices, higher
|
||||
prices, slower deliveries and limited options for small <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. </p>\\r\\n\\r\\n<p>However, <strong>Wedbush </strong>found
|
||||
the possibility of a <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. </p>\\r\\n\\r\\n<p><strong>Price Action:</strong> 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> 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 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 and
|
||||
construction management.</p>\\r\\n\\r\\n<p>“Our diversification playbook
|
||||
continues to drive strong growth,” 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 'Beyond Buds: How
|
||||
and Why Ancillary Companies are Diversifying'. 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 “will be predominantly realized over the next six quarters,”
|
||||
<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>"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,” 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 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 <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 <a href=\\\"https://www.benzinga.com/go/cannabis-conference-article-link\\\"
|
||||
target=\\\"_blank\\\"><strong>Benzinga Cannabis Capital Conference</strong></a> in <strong>Chicago
|
||||
on Sept 27-28</strong>. <a href=\\\"http://www.benzinga.com/go/cannabis-conference-article-link\\\"
|
||||
target=\\\"_blank\\\">Get your tickets today</a> 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'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's (NASDAQ: AVGO) division Symantec announced a collaboration with\_Alphabet
|
||||
Inc.\_(NASDAQ: GOOGL) (NASDAQ: GOOG) Google C\",\"body\":\"<p><strong>Broadcom
|
||||
Inc</strong>'s (NASDAQ:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/AVGO#NASDAQ\\\">AVGO</a>)
|
||||
division Symantec announced a collaboration with <strong>Alphabet Inc.</strong> (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 <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. </p>\\r\\n\\r\\n<p><strong><em>Related</em></strong><em>: </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's Power Move: Broadcom Ditched in Favor
|
||||
of Marvell Amid AI Chip Wars</em></a></p>\\r\\n\\r\\n<p>"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," said Adam Bromwich, CTO and Head of
|
||||
Engineering, Symantec Enterprise Division.</p>\\r\\n\\r\\n<p><strong><em>Also
|
||||
Read</em></strong><em>: </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's Shift, Analyst
|
||||
Sees Strong Ties and Momentum in Broadcom'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 </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 <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. </p>\\r\\n\\r\\n<p>"This collection
|
||||
is a big step for Lee as it is our first fully unisex collection and one that's
|
||||
available globally," said Brigid Stevens, global vice president of marketing,
|
||||
Lee.</p>\\r\\n\\r\\n<p><strong><em>Also Read</em></strong><em>: </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's essence
|
||||
is deeply rooted in the vibrant '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'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
|
||||
|
||||
@@ -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&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&P 500, foreseeing it reaching 15,000 by 2030
|
||||
riding the AI wave.</p>\\n\\n\\n\\n<p><strong>What Happened</strong>: Lee’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>“This
|
||||
will be the 3rd time that stocks entered a cycle where annual returns compound
|
||||
at high teens,” Lee said in an interview with CNBC’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>'s
|
||||
Tom Lee says the S&P 500 will hit 15,000 by 2030. "This will be the
|
||||
3rd time that stocks entered a cycle where annual returns compound at high
|
||||
teens." <a href=\\\"https://twitter.com/search?q=%24SPX&src=ctag&ref_src=twsrc%5Etfw\\\">$SPX</a>
|
||||
<a href=\\\"https://t.co/31vzaEaGpo\\\">pic.twitter.com/31vzaEaGpo</a></p>—
|
||||
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&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’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&P 500 ETF</strong> (NYSE:<a class=\\\"ticker\\\" href=\\\"https://www.benzinga.com/stock/VOO#NYSE\\\">VOO</a>),
|
||||
and<strong> iShares Core S&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: ‘It’s A Waste Of
|
||||
Time To Try To Refute Mr. Trump’s Arguments’</a></em></p>\\n\\n\\n\\n<p><strong>Why
|
||||
It Matters</strong>: Lee’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&P 500</a> for the remainder of 2024, suggesting that
|
||||
the stock market rally had peaked. However, Lee’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’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
|
||||
“crash of a lifetime”</a> due to the current “everything”
|
||||
bubble, which he believes has yet to burst. Despite this, Lee’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 ‘Thousands Of Hard Working Americans’ 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> (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 <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™, 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 </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's technologies; first incorporating
|
||||
LanzaTech's novel gas fermentation technology to convert nearly any waste
|
||||
resource into CarbonSmart™ ethanol and then second, executing LanzaJet'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 85% and can also produce carbon negative results,
|
||||
depending on the feedstock.</p>\\n\\n<p>SAF is estimated to account for 65–70% 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)
|
||||
("GNL" or the "Company") 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>) ("GNL"
|
||||
or the "Company") 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 $170 million, at a 7.88% 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's ongoing strategic disposition initiative and aligns
|
||||
with the Company's 2024 full-year guidance, which projected a disposition
|
||||
cash cap rate range of 7% to 8%. GNL plans to use the net proceeds
|
||||
from this sale to reduce outstanding debt and further lower the Company's
|
||||
leverage. The sale of this portfolio, which GNL acquired for $153.4 million,
|
||||
is part of the previously announced $567 million1 of closed and
|
||||
pipeline dispositions at a cash cap rate of 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), ("Entero Therapeutics" or the "Company"),
|
||||
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>),
|
||||
("Entero Therapeutics" or the "Company"), a clinical-stage
|
||||
biopharmaceutical company specializing in the development of targeted, non-systemic
|
||||
therapies for gastrointestinal (GI) diseases, today announced the publication
|
||||
of "<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>"
|
||||
in <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>), one
|
||||
of North America’s largest recyclers and a global producer
|
||||
of packaging, pulp and other fiber-based products. This news triggered a nearly
|
||||
15% drop in International Paper’s shares during pre-market trading on
|
||||
Thursday.</p>\\n\\n\\n\\n<p>Suzano’s decision was prompted by International
|
||||
Paper’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’s shares were trading at $39.67, a significant drop
|
||||
from Wednesday’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\\\">‘Dogecoin Killer’
|
||||
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’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 “private, confidential and amicable terms,”
|
||||
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’s plan to acquire DS Smith Plc, a deal that is expected to close
|
||||
in the fourth quarter. If Suzano’s acquisition had been successful,
|
||||
the world’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’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 25.1% of the outstanding shares
|
||||
of RH common stock, based on 18.44 million shares outstanding as of June 7,
|
||||
2024, as reported in RH's first quarter fiscal 2024 Form 10-Q.
|
||||
This represents an increase in Friedman's beneficial ownership by 0.2%. </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’s</strong>
|
||||
GPT-4, will be rolled out to the firm’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’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’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’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’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’</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’s remaining interest.</div>\\n\\n<div> </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’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’s energy transition strategy.</p>\\n\\n\\n\\n<p>These moves
|
||||
are part of CEO Murray Auchincloss’ 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’
|
||||
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’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’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 “to deliver as a simpler,
|
||||
more focused and higher value company.”</p>\\n\\n\\n\\n<p>“The
|
||||
actions we are taking are part of delivering this – and of course are
|
||||
all in service of our aim of growing the value of BP.”</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 <strong>Direxion Hydrogen ETF </strong>(ARCA: HJEN) and <strong>First
|
||||
Trust Exchange-Traded Fund IV FT Energy Income Partners Strategy ETF</strong>
|
||||
(ARCA: 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)"
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"])
|
||||
]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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?"
|
||||
)
|
||||
|
||||
134
openbb_platform/providers/fmp/openbb_fmp/models/calendar_ipo.py
Normal file
134
openbb_platform/providers/fmp/openbb_fmp/models/calendar_ipo.py
Normal 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)
|
||||
]
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -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")
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
@@ -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,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
]
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user