[Feature] Add Fama-French Data Library (#7163)

* add fama-french data library

* lint

* lint

* lint

* more lint and test function names

* test shuffle

* test params

* use apply_date in get_breakpoint_data

* unused import

* not that import

* ok fine, do it this way
This commit is contained in:
Danglewood
2025-07-12 15:26:56 -07:00
committed by GitHub
parent 7b520e5f8a
commit 8a603e2020
35 changed files with 66331 additions and 0 deletions

View File

@@ -58,6 +58,7 @@ openbb-biztoc = { path = "./providers/biztoc", optional = true, develop = true }
openbb-cboe = { path = "./providers/cboe", optional = true, develop = true }
openbb-deribit = { path = "./providers/deribit", optional = true, develop = true }
openbb-ecb = { path = "./providers/ecb", optional = true, develop = true }
openbb-famafrench = { path = "./providers/famafrench", optional = true, develop = true }
openbb-finra = { path = "./providers/finra", optional = true, develop = true }
openbb-finviz = { path = "./providers/finviz", optional = true, develop = true }
openbb-government-us = { path = "./providers/government_us", optional = true, develop = true }

View File

@@ -0,0 +1 @@
"""OpenBB FamaFrench Integration Tests module."""

View File

@@ -0,0 +1,243 @@
"""Test Fama-French API endpoints."""
import base64
import pytest
import requests
from openbb_core.env import Env
from openbb_core.provider.utils.helpers import get_querystring
# pylint: disable=redefined-outer-name
@pytest.fixture(scope="session")
def headers():
"""Get the headers for the API request."""
userpass = f"{Env().API_USERNAME}:{Env().API_PASSWORD}"
userpass_bytes = userpass.encode("ascii")
base64_bytes = base64.b64encode(userpass_bytes)
return {"Authorization": f"Basic {base64_bytes.decode('ascii')}"}
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"region": "america",
"factor": "momentum",
"frequency": "monthly",
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_factors(params, headers):
"""Test the Fama-French factors 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/famafrench/factors?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"portfolio": "5_industry_portfolios",
"measure": "equal",
"frequency": "annual",
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_us_portfolio_returns(params, headers):
"""Test the US portfolio returns 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/famafrench/us_portfolio_returns?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"portfolio": "developed_ex_us_6_portfolios_me_op",
"measure": "equal",
"frequency": "annual",
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_regional_portfolio_returns(params, headers):
"""Test the regional portfolio returns 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/famafrench/regional_portfolio_returns?{query_str}"
)
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"country": "japan",
"measure": "ratios",
"frequency": None,
"start_date": None,
"end_date": None,
"dividends": True,
"all_data_items_required": True,
}
),
],
)
@pytest.mark.integration
def test_famafrench_country_portfolio_returns(params, headers):
"""Test the country portfolio returns 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/famafrench/country_portfolio_returns?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"index": "asia_pacific",
"measure": "local",
"frequency": "annual",
"start_date": None,
"end_date": None,
"dividends": True,
"all_data_items_required": True,
}
),
],
)
@pytest.mark.integration
def test_famafrench_international_index_returns(params, headers):
"""Test the international index returns 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/famafrench/international_index_returns?{query_str}"
)
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"breakpoint_type": "op",
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_breakpoints(params, headers):
"""Test Fama-French breakpoints 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/famafrench/breakpoints?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"region": "america",
"factor": "Momentum",
"is_portfolio": None,
"portfolio": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_factor_choices(params, headers):
"""Test Fama-French available factors 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/famafrench/factor_choices?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200

View File

@@ -0,0 +1,227 @@
"""Test Fama-French Python Interface."""
import pytest
from openbb_core.app.model.obbject import OBBject
# pylint: disable=redefined-outer-name
@pytest.fixture(scope="session")
def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
"""Fixture to setup obb."""
if pytestconfig.getoption("markexpr") != "not integration":
import openbb # pylint: disable=import-outside-toplevel
return openbb.obb
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"region": "america",
"factor": "momentum",
"frequency": "monthly",
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_factors(params, obb):
"""Test the Fama-French factors endpoint."""
params = {p: v for p, v in params.items() if v}
result = obb.famafrench.factors(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"portfolio": "5_industry_portfolios",
"measure": "equal",
"frequency": "annual",
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_us_portfolio_returns(params, obb):
"""Test the US portfolio returns endpoint."""
params = {p: v for p, v in params.items() if v}
result = obb.famafrench.us_portfolio_returns(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"portfolio": "developed_ex_us_6_portfolios_me_op",
"measure": "equal",
"frequency": None,
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_regional_portfolio_returns(params, obb):
"""Test the regional portfolio returns endpoint."""
params = {p: v for p, v in params.items() if v}
result = obb.famafrench.regional_portfolio_returns(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"country": "japan",
"measure": "ratios",
"frequency": None,
"start_date": None,
"end_date": None,
"dividends": True,
"all_data_items_required": True,
}
),
],
)
@pytest.mark.integration
def test_famafrench_country_portfolio_returns(params, obb):
"""Test the country portfolio returns endpoint."""
params = {p: v for p, v in params.items() if v}
result = obb.famafrench.country_portfolio_returns(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"index": "asia_pacific",
"measure": "local",
"frequency": "annual",
"start_date": None,
"end_date": None,
"dividends": True,
"all_data_items_required": True,
}
),
],
)
@pytest.mark.integration
def test_famafrench_international_index_returns(params, obb):
"""Test the international index returns endpoint."""
params = {p: v for p, v in params.items() if v}
result = obb.famafrench.international_index_returns(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "famafrench",
}
),
(
{
"provider": "famafrench",
"breakpoint_type": "op",
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_breakpoints(params, obb):
"""Test the Fama-French breakpoints endpoint."""
params = {p: v for p, v in params.items() if v}
result = obb.famafrench.breakpoints(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"region": "america",
"factor": "Momentum",
"is_portfolio": None,
"portfolio": None,
}
),
],
)
@pytest.mark.integration
def test_famafrench_factor_choices(params, obb):
"""Test Fama-French available factors endpoint."""
params = {p: v for p, v in params.items() if v}
result = obb.famafrench.factor_choices(**params)
assert result
assert isinstance(result, list)
assert len(result) > 0

View File

@@ -0,0 +1,61 @@
# OpenBB Fama-French Extension
This extension implements the Ken French data library (Source: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html)
as an OpenBB Platform Provider and Router extension.
## Installation
Install this extension with:
```sh
pip install openbb-famafrench
```
## Coverage
The extension provides six API endpoints:
```python
In [1]: from openbb import obb
In [2]: obb.famafrench
Out[2]:
/famafrench
breakpoints
country_portfolio_returns
factor_choices <-- Utility function serving choices to the OpenBB Workspace widget
factors
international_index_returns
regional_portfolio_returns
us_portfolio_returns
```
## Usage
The most common use will be for retrieving the 3 and 5-factor models, default state is 3-factors, at a monthly interval, for the United States.
```python
from openbb import obb
factors = obb.famafrench.factors()
```
Or, by region and factor:
```python
momentum = obb.famafrench.factors(factor="momentum", region="europe")
```
Metadata corresponding to the file downloaded is available under, `extra["results_metadata"]`, of the results object.
```python
factors.extra["results_metadata"]
{
'description': '### \n\nThis file was created using the 202504 CRSP database. The 1-month TBill rate data until 202405 are from Ibbotson Associates. Starting from 202406, the 1-month TBill rate is from ICE BofA US 1-Month Treasury Bill Index.\n\n',
'frequency': 'monthly',
'formations': ['Mkt-RF', 'SMB', 'HML', 'RF']
}
```
Refer to the endpoint's docstring for detailed descriptions.

View File

@@ -0,0 +1,36 @@
"""OpenBB Fama-French Provider module."""
from openbb_core.provider.abstract.provider import Provider
from openbb_famafrench.models.breakpoints import FamaFrenchBreakpointFetcher
from openbb_famafrench.models.country_portfolio_returns import (
FamaFrenchCountryPortfolioReturnsFetcher,
)
from openbb_famafrench.models.factors import FamaFrenchFactorsFetcher
from openbb_famafrench.models.international_index_returns import (
FamaFrenchInternationalIndexReturnsFetcher,
)
from openbb_famafrench.models.regional_portfolio_returns import (
FamaFrenchRegionalPortfolioReturnsFetcher,
)
from openbb_famafrench.models.us_portfolio_returns import (
FamaFrenchUSPortfolioReturnsFetcher,
)
famafrench_provider = Provider(
name="famafrench",
website="https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html",
description="""
This provider implements the Fama-French research portfolios and factors data library,
maintained and hosted by Kenneth R. French at Dartmouth College.
""",
fetcher_dict={
"FamaFrenchBreakpoints": FamaFrenchBreakpointFetcher,
"FamaFrenchCountryPortfolioReturns": FamaFrenchCountryPortfolioReturnsFetcher,
"FamaFrenchFactors": FamaFrenchFactorsFetcher,
"FamaFrenchInternationalIndexReturns": FamaFrenchInternationalIndexReturnsFetcher,
"FamaFrenchRegionalPortfolioReturns": FamaFrenchRegionalPortfolioReturnsFetcher,
"FamaFrenchUSPortfolioReturns": FamaFrenchUSPortfolioReturnsFetcher,
},
repr_name="Fama-French Research Portfolios and Factors",
)

View File

@@ -0,0 +1,518 @@
# pylint: disable=import-outside-toplevel,unused-argument
"""Fama-French Router."""
from typing import Annotated, Optional
from fastapi import Query as FastAPIQuery
from openbb_core.app.model.command_context import CommandContext
from openbb_core.app.model.example import APIEx
from openbb_core.app.model.obbject import OBBject
from openbb_core.app.provider_interface import (
ExtraParams,
ProviderChoices,
StandardParams,
)
from openbb_core.app.query import Query as OpenBBQuery
from openbb_core.app.router import Router
router = Router(prefix="", description="Fama-French research factors and portfolios.")
@router.command(
model="FamaFrenchFactors",
methods=["GET"],
examples=[
APIEx(
description="Fama-French 3-factors data for America, default is monthly.",
parameters={
"provider": "famafrench",
},
),
APIEx(
description="Fama-French 5-factors data for Europe.",
parameters={
"provider": "famafrench",
"region": "europe",
"factor": "5_factors",
},
),
],
)
async def factors(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Fama-French factors.
Metadata for the selected dataset are returned in the
`extra['results_metadata']` field of the response.
Source
------
https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
All returns are in U.S. dollars, include dividends and capital gains,
and are not continuously compounded.
Market is the return on a region's value-weight market portfolio
minus the U.S. one month T-bill rate.
The Fama/French 5 factors (2x3) are constructed using the 6,
value-weight, portfolios formed on size and book-to-market, the 6,
value-weight, portfolios formed on size and operating profitability,
and the 6, value-weight, portfolios formed on size and investment.
To construct the SMB, HML, RMW, and CMA factors, we sort stocks in a
region into two market cap and three respective book-to-market equity (B/M),
operating profitability (OP), and investment (INV) groups at the end of each June.
Big stocks are those in the top 90% of June market cap for the region,
and small stocks are those in the bottom 10%.
The B/M, OP, and INV breakpoints for a region are the 30th and 70th percentiles
of respective ratios for the big stocks of the region.
Rm–Rf for July of year t to June of t+1 include all stocks
for which we have market equity data for June of t.
SMB, HML, RMW, and CMA for July of year t to June of t+1 include all stocks
for which we have market equity data for December of t-1
and June of t, (positive) book equity data for t-1 (for SMB, HML, and RMW),
non-missing revenues and at least one of the following: cost of goods sold,
selling, general and administrative expenses, or interest expense for
t-1 (for SMB and RMW), and total assets data for t-2 and t-1 (for SMB and CMA).
The momentum and short term reversal portfolios are reconstituted monthly
and the other research portfolios are reconstituted annually.
We reconstruct the full history of returns each month when we update the portfolios.
"""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
methods=["GET"],
include_in_schema=False,
openapi_extra={"widget_config": {"exclude": True}},
examples=[
APIEx(
description="Get region choices.",
parameters={
"provider": "famafrench",
},
),
APIEx(
description="Get factor choices by region.",
parameters={
"provider": "famafrench",
"region": "europe",
},
),
APIEx(
description="Get interval choices for factors by region.",
parameters={
"provider": "famafrench",
"region": "europe",
"factor": "5_Factors",
},
),
APIEx(
description="Get regional choices by portfolio.",
parameters={
"provider": "famafrench",
"portfolio": "6 Portfolios ME BE-ME",
},
),
],
)
async def factor_choices(
region: Annotated[
Optional[str],
FastAPIQuery(description="Region to get factor choices for."),
] = None,
factor: Annotated[
Optional[str],
FastAPIQuery(
description="Factor to get interval choices for.",
),
] = None,
is_portfolio: Annotated[
Optional[bool],
FastAPIQuery(
description="When True, returns portfolio choices by region.",
),
] = None,
portfolio: Annotated[
Optional[str],
FastAPIQuery(
description="When supplied, returns regional choices for the portfolio.",
),
] = None,
) -> list:
"""
Endpoint (optionsEndpoint) for providing dynamic menu choices to OpenBB Workspace widgets.
Intended to be referenced in 'widgets.json', and returns a list of {'value': str, 'label': str}.
"""
# pylint: disable=import-outside-toplevel
from openbb_famafrench.utils.helpers import get_factor_choices
return await get_factor_choices(
region=region,
factor=factor,
is_portfolio=is_portfolio is True,
portfolio=portfolio,
)
@router.command(
model="FamaFrenchUSPortfolioReturns",
methods=["GET"],
examples=[
APIEx(
description="Get model US portfolio returns "
+ "used for constructing the Fama-French Factor models.",
parameters={
"provider": "famafrench",
},
),
APIEx(
description="If the portfolio name does not have a frequency - i.e."
+ " '_daily' - use the frequency parameter to specify the interval.",
parameters={
"provider": "famafrench",
"portfolio": "5_industry_portfolios_wout_div",
"frequency": "annual",
"measure": "equal",
},
),
],
)
async def us_portfolio_returns(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""US Portfolio returns.
Metadata for the selected dataset are returned in the
`extra['results_metadata']` field of the response.
Source
------
https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
All returns are in U.S. dollars, include dividends (unless the portfolio is 'wout_div')
and capital gains, and are not continuously compounded.
The momentum and short term reversal portfolios are reconstituted monthly
and the other research portfolios are reconstituted annually.
We reconstruct the full history of returns each month when we update the portfolios.
Size and Book-to-Market Portfolios
----------------------------------
- Small Value
- Small Neutral
- Small Growth
- Big Value
- Big Neutral
- Big Growth
BE < 0; bottom 30%, middle 40%, top 30%; quintiles; deciles.
Firms with negative book equity are in only the BE < 0 portfolio.
Size and Operating Profitability Portfolios
-------------------------------------------
- Small Robust
- Small Neutral
- Small Weak
- Big Robust
- Big Neutral
- Big Weak
Operating Profitability bottom 30%, middle 40%, top 30%; quintiles; deciles.
Size and Investment Portfolios
------------------------------
- Small Conservative
- Small Neutral
- Small Aggressive
- Big Conservative
- Big Neutral
- Big Aggressive
ME < 0 (not used); bottom 30%, middle 40%, top 30%; quintiles; deciles.
Investment bottom 30%, middle 40%, top 30%; quintiles; deciles.
Definitions
-----------
ME : Market Equity
Market equity (size) is price times shares outstanding.
Price and shares outstanding are from CRSP.
BE : Book Equity
Book equity is constructed from Compustat data or collected from the
Moody’s Industrial, Financial, and Utilities manuals.
BE is the book value of stockholders’ equity, plus balance sheet deferred taxes
and investment tax credit (if available), minus the book value of preferred stock.
Depending on availability, we use the redemption, liquidation, or par value (in that order)
to estimate the book value of preferred stock. Stockholders’ equity is the value reported
by Moody’s or Compustat, if it is available. If not, we measure stockholders’ equity as
the book value of common equity plus the par value of preferred stock,
or the book value of assets minus total liabilities (in that order).
See Davis, Fama, and French, 2000,
“Characteristics, Covariances, and Average Returns: 1929-1997”
Journal of Finance, for more details.
BE/ME : Book-to-Market
The book-to-market ratio used to form portfolios in June of year t is book equity
for the fiscal year ending in calendar year t-1,
divided by market equity at the end of December of t-1.
OP : Operating Profitability
The operating profitability ratio used to form portfolios in June of year t is annual revenues
minus cost of goods sold, interest expense, and selling, general, and administrative expense
divided by the sum of book equity and minority interest for the last fiscal year ending in t-1.
INV : Investment
The investment ratio used to form portfolios in June of year t is the change in total assets
from the fiscal year ending in year t-2 to the fiscal year ending in t-1,
divided by t-2 total assets.
E/P : Earnings/Price
Earnings is total earnings before extraordinary items, from Compustat.
The earnings/price ratio used to form portfolios in June of year t is earnings
for the fiscal year ending in calendar year t-1,
divided by market equity at the end of December of t-1.
CF/P : Cashflow/Price
Cashflow is total earnings before extraordinary items, plus equity’s share of depreciation,
plus deferred taxes (if available), from Compustat. Equity’s share is defined as market equity
divided by assets minus book equity plus market equity.
The cashflow/price ratio used to form portfolios in June of year t is the cashflow for the
fiscal year ending in calendar year t-1, divided by market equity at the end of December of t-1.
D/P : Dividend Yield
The dividend yield used to form portfolios in June of year t is the total dividends paid
from July of t-1 to June of t per dollar of equity in June of t.
The dividend yield is computed using the with and without dividend returns from CRSP,
as described in Fama and French, 1988, “Dividend yields and expected stock returns,”
Journal of Financial Economics 25.
"""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
model="FamaFrenchRegionalPortfolioReturns",
methods=["GET"],
examples=[
APIEx(
description="Get model Regional portfolio returns "
+ "used for constructing the Fama-French Factor models.",
parameters={
"provider": "famafrench",
},
),
APIEx(
description="Get portfolios formed by equal-weight, with annual returns.",
parameters={
"provider": "famafrench",
"frequency": "annual",
"measure": "equal",
},
),
],
)
async def regional_portfolio_returns(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Regional portfolio returns.
Metadata for the selected dataset are returned in the
`extra['results_metadata']` field of the response.
See the `us_portfolio_returns` endpoint for more details.
"""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
model="FamaFrenchCountryPortfolioReturns",
methods=["GET"],
examples=[
APIEx(
description="Get model Country portfolio returns "
+ "used for constructing the Fama-French Factor models.",
parameters={
"provider": "famafrench",
},
),
APIEx(
description="Parameters are different than the regional and US portfolio returns.",
parameters={
"provider": "famafrench",
"frequency": "annual",
"measure": "local",
"country": "japan",
"dividends": False,
"all_data_items_required": False,
},
),
],
)
async def country_portfolio_returns(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Country portfolio returns.
Metadata for the selected dataset are returned in the
`extra['results_metadata']` field of the response.
Source
------
https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
We form value and growth portfolios in each country using four valuation ratios:
- book-to-market (B/M)
- earnings-price (E/P)
- cash earnings to price (CE/P)
- dividend yield (D/P)
We form the portfolios at the end of December each year by sorting on one of the four ratios and
then compute value-weighted returns for the following 12 months.
The value portfolios (High) contain firms in the top 30% of a ratio
and the growth portfolios (Low) contain firms in the bottom 30%.
There are two sets of portfolios.
In one, firms are included only if we have data on all four ratios.
In the other, a firm is included in a sort variable's portfolios
if we have data for that variable.
The market return (Mkt) for the first set is the value weighted average of the returns
for only firms with all four ratios.
The market return for the second set includes all firms with book-to-market data,
and Firms is the number of firms with B/M data.
"""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
model="FamaFrenchInternationalIndexReturns",
methods=["GET"],
examples=[
APIEx(
description="Get benchmark index returns "
+ "used for constructing the international Fama-French Factor models.",
parameters={
"provider": "famafrench",
},
),
APIEx(
description="Parameters are similar to the country portfolio returns.",
parameters={
"provider": "famafrench",
"frequency": "annual",
"measure": "ratios",
"index": "europe_ex_uk",
"dividends": False,
"all_data_items_required": False,
},
),
],
)
async def international_index_returns(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""International index returns.
Metadata for the selected dataset are returned in the
`extra['results_metadata']` field of the response.
See the `country_portfolio_returns` endpoint for more details.
Source
------
https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
The returns on the index portfolios are constructed by
averaging the returns on the country portfolios.
We weight countries in the index portfolios in proportion to their EAFE + Canada weights.
Each country is added to the index portfolios when the return data for the country begin;
the country start dates can be inferred from the country return files.
"""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
model="FamaFrenchBreakpoints",
methods=["GET"],
examples=[
APIEx(
description="Get US breakpoints "
+ "used for constructing the research portfolios.",
parameters={
"provider": "famafrench",
},
),
APIEx(
description="See the parameters description for details on the breakpoints.",
parameters={
"provider": "famafrench",
"breakpoint": "op",
"start_date": "1998-01-01",
},
),
],
)
async def breakpoints(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Fama-French breakpoints.
Metadata for the selected dataset are returned in the
`extra['results_metadata']` field of the response.
Source
------
https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html#Breakpoints
"""
return await OBBject.from_query(OpenBBQuery(**locals()))

View File

@@ -0,0 +1 @@
"""OpenBB Fama-French Models."""

View File

@@ -0,0 +1,306 @@
"""Fama-French Breakpoints Model."""
# pylint: disable=unused-argument
from datetime import date as dateType
from typing import Any, Optional
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.abstract.query_params import QueryParams
from pydantic import Field
from openbb_famafrench.utils.constants import (
BREAKPOINT_CHOICES,
BreakpointChoices,
)
class FamaFrenchBreakpointQueryParams(QueryParams):
"""Fama-French Breakpoints Query."""
__json_schema_extra__ = {
"breakpoint_type": {
"choices": BREAKPOINT_CHOICES,
}
}
breakpoint_type: BreakpointChoices = Field(
default="me",
description="""Type of breakpoint to fetch.
The breakpoints for month t use all NYSE stocks that have a CRSP share code of 10 or 11
and have good shares and price data. We exclude closed end funds and REITs.
Breakpoints are computed either monthly or annually, see the description of each breakpoint type below.
Data contains every fifth percentile, from 5% to 100%.
ME
--
Market Equity. Market equity (size) is price times shares outstanding.
Price and shares outstanding are from CRSP.
ME breakpoints are computed for each month.
It is price times shares outstanding (divided by 1,000,000) at month end.
BE/ME
-----
BE/ME breakpoints are computed at the end of each June.
The BE used in June of year t is the book equity for the last fiscal year end in t-1.
ME is price times shares outstanding at the end of December of t-1.
The breakpoints for year t use all NYSE stocks for which we have ME for December of t-1
and (positive) BE for the last fiscal year end in t-1.
Operating Profitability
-----------------------
Operating Profitability breakpoints are computed at the end of each June.
OP for June of year t is annual revenues minus
- cost of goods sold
- interest expense
- selling, general, and administrative expenses
divided by book equity for the last fiscal year end in t-1.
Please be aware that some of the value-weight averages of operating profitability for deciles 1 and 10 are extreme.
These are driven by extraordinary values of OP for individual firms.
We have spot checked the accounting data that produce the extraordinary values
and all the numbers we examined accurately reflect the data in the firm's accounting statements.
The breakpoints for year t use all NYSE stocks for which we have (positive) book equity data for t-1,
non-missing revenues data for t-1, and non-missing data for at least one of the following:
- cost of goods sold
- selling, general and administrative expenses
- interest expense for t-1.
Investment
----------
Investment breakpoints are computed at the end of each June.
Inv used in June of year t is the change in total assets from the fiscal year ending in year t-2
to the fiscal year ending in t-1, divided by t-2 total assets.
The breakpoints for year t use all NYSE stocks for which we have total assets data for t-2 and t-1.
E/P
---
E/P (in percent) breakpoints are computed at the end of each June.
The E used in June of year t is the earnings for the last fiscal year end in t-1.
P (actually ME) is price times shares outstanding at the end of December of t-1.
The breakpoints for year t use all NYSE stocks for which we have ME for December of t-1
and (positive) earnings for the last fiscal year end in t-1.
CF/P
----
CF/P (in percent) breakpoints is computed at the end of each June.
The CF used in June of year t is the cash flow for the last fiscal year end in t-1.
P (actually ME) is price times shares outstanding at the end of December of t-1.
The breakpoints for year t use all NYSE stocks for which we have ME for December of t-1
and (positive) cash flow for the last fiscal year end in t-1.
D/P
---
D/P (in percent) breakpoints are computed at the end of each June.
The dividend yield in June of year t is the total dividends paid from July of t-1
to June of t per dollar of equity in June of t.
The breakpoints for year t use NYSE stocks for which we have at least
seven months (to compute the dividend yield) from July of t-1 to June of t.
(Only six monthly returns are required in June 1926.)
We do not include stocks that pay no dividends from July of t-1 to June of t.
Prior 2-12
----------
Prior return breakpoints are computed for each month.
The prior return at the end of month t is the cumulative return from month t-11 to month t-1.
The breakpoints for month t use NYSE stocks.
To be included, a stock must have a price for the end of month t-12 and a good return for t-1.
In addition, any missing returns from t-11 to t-2 must be -99.0, CRSP's code for a missing price.
""",
)
start_date: Optional[dateType] = Field(
default=None,
description="Start date for the data.",
)
end_date: Optional[dateType] = Field(
default=None,
description="End date for the data.",
)
class FamaFrenchBreakpointData(Data):
"""Fama-French Breakpoints Data."""
date: dateType = Field(
description="Date of the data.",
)
num_firms: Optional[int] = Field(
default=None,
description="Number of firms in the sample. Not returned for BE/ME breakpoints.",
)
num_firms_less_than_0: Optional[int] = Field(
default=None,
description="Number of firms with ratio less than or equal to 0."
+ " This is only applicable for ratio breakpoints.",
)
num_firms_greater_than_0: Optional[int] = Field(
default=None,
description="Number of firms with ratio greater than 0."
+ " This is only applicable for ratio breakpoints.",
)
percentile_5: float = Field(
description="Fifth percentile of the sample.",
title="5th Percentile",
)
percentile_10: float = Field(
description="Tenth percentile of the sample.",
title="10th Percentile",
)
percentile_15: float = Field(
description="Fifteenth percentile of the sample.",
title="15th Percentile",
)
percentile_20: float = Field(
description="Twentieth percentile of the sample.",
title="20th Percentile",
)
percentile_25: float = Field(
description="Twenty-fifth percentile of the sample.",
title="25th Percentile",
)
percentile_30: float = Field(
description="Thirtieth percentile of the sample.",
title="30th Percentile",
)
percentile_35: float = Field(
description="Thirty-fifth percentile of the sample.",
title="35th Percentile",
)
percentile_40: float = Field(
description="Fortieth percentile of the sample.",
title="40th Percentile",
)
percentile_45: float = Field(
description="Forty-fifth percentile of the sample.",
title="45th Percentile",
)
percentile_50: float = Field(
description="Fiftieth percentile of the sample.",
title="50th Percentile",
)
percentile_55: float = Field(
description="Fifty-fifth percentile of the sample.",
title="55th Percentile",
)
percentile_60: float = Field(
description="Sixtieth percentile of the sample.",
title="60th Percentile",
)
percentile_65: float = Field(
description="Sixty-fifth percentile of the sample.",
title="65th Percentile",
)
percentile_70: float = Field(
description="Seventieth percentile of the sample.",
title="70th Percentile",
)
percentile_75: float = Field(
description="Seventy-fifth percentile of the sample.",
title="75th Percentile",
)
percentile_80: float = Field(
description="Eightieth percentile of the sample.",
title="80th Percentile",
)
percentile_85: float = Field(
description="Eighty-fifth percentile of the sample.",
title="85th Percentile",
)
percentile_90: float = Field(
description="Ninetieth percentile of the sample.",
title="90th Percentile",
)
percentile_95: float = Field(
description="Ninety-fifth percentile of the sample.",
title="95th Percentile",
)
percentile_100: float = Field(
description="Hundredth percentile of the sample.",
title="100th Percentile",
)
class FamaFrenchBreakpointFetcher(
Fetcher[FamaFrenchBreakpointQueryParams, list[FamaFrenchBreakpointData]]
):
"""Fama-French Breakpoints Fetcher."""
@staticmethod
def transform_query(
params: dict[str, Any],
) -> FamaFrenchBreakpointQueryParams:
"""Transform the query parameters into a QueryParams object."""
return FamaFrenchBreakpointQueryParams(**params)
@staticmethod
async def aextract_data(
query: FamaFrenchBreakpointQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> tuple:
"""Extract data from the Fama-French FTP."""
# pylint: disable=import-outside-toplevel
from openbb_famafrench.utils.helpers import get_breakpoint_data
try:
return get_breakpoint_data(
breakpoint_type=query.breakpoint_type,
)
except Exception as e:
raise OpenBBError(e) from e
@staticmethod
def transform_data(
query: FamaFrenchBreakpointQueryParams,
data: tuple,
**kwargs: Any,
) -> AnnotatedResult[list[FamaFrenchBreakpointData]]:
"""Transform the extracted data."""
dfs, meta = data
if not dfs:
raise OpenBBError(
f"The data is unexpectedly empty. -> {query.breakpoint_type}"
)
df = dfs[0]
metadata = meta[0]
if query.start_date:
df = df[df["date"] >= query.start_date.strftime("%Y-%m-%d")]
if query.end_date:
df = df[df["date"] <= query.end_date.strftime("%Y-%m-%d")]
return AnnotatedResult(
result=[
FamaFrenchBreakpointData(**d) for d in df.to_dict(orient="records")
],
metadata={"description": metadata},
)

View File

@@ -0,0 +1,319 @@
"""Fama-French Factors Fetcher Model."""
# pylint: disable=unused-argument
from datetime import date as dateType
from typing import Any, Literal, Optional
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.abstract.query_params import QueryParams
from pydantic import Field
from openbb_famafrench.utils.constants import CountryPortfolios
class FamaFrenchCountryPortfolioReturnsQueryParams(QueryParams):
"""Country Portfolio Returns Query parameters."""
__json_schema_extra__ = {
"country": {
"x-widget_config": {
"options": [
{"value": d, "label": d.replace("_", " ").title()}
for d in getattr(CountryPortfolios, "__args__", [])
]
}
},
}
country: CountryPortfolios = Field(
default="united_kingdom",
description="Country to fetch the portfolio returns for.",
)
measure: Literal[
"usd",
"local",
"ratios",
] = Field(
default="usd",
description="The measure to fetch for the portfolio."
+ " Only 'annual' frequency is supported for 'ratios'.",
)
frequency: Literal["monthly", "annual"] = Field(
default="monthly",
description="The frequency of the data to fetch."
+ " Ignored when `measure` is set to 'ratios'.",
)
dividends: Optional[bool] = Field(
default=None, description="When False, portoflios exclude dividends."
)
all_data_items_required: Optional[bool] = Field(
default=None,
description="If True (default), includes firms with data for all four ratios."
+ " When False, includes only firms with Book-to-Market (B/M) data.",
)
start_date: Optional[dateType] = Field(
default=None,
description="The start date for the data. Defaults to the earliest available date.",
)
end_date: Optional[dateType] = Field(
default=None,
description="The end date for the data. Defaults to the latest available date.",
)
class FamaFrenchCountryPortfolioReturnsData(Data):
"""US Portfolio Returns Data."""
__alias_dict__ = {
"date": "Date",
"mkt": "Mkt",
"firms": "Firms",
"bm": "B/M",
"be_me_high": "BE/ME High",
"be_me_low": "BE/ME Low",
"ep": "E/P",
"e_p_high": "E/P High",
"e_p_low": "E/P Low",
"ce_p": "CE/P",
"ce_p_high": "CE/P High",
"ce_p_low": "CE/P Low",
"yld": "Yld",
"yld_high": "Yld High",
"yld_low": "Yld Low",
"yld_zero": "Yld Zero",
}
date: dateType = Field(
description="The date of the data.",
)
mkt: Optional[float] = Field(
default=None,
description="""
The market return (Mkt) for the first set is the value weighted average
of the returns for only firms with all four ratios.
The market return for the second set includes all firms with book-to-market data,
and Firms is the number of firms with B/M data.
Not returned if `measure` is set to 'ratios'.
""",
)
firms: Optional[int] = Field(
default=None,
description="The number of firms, relative to `all_data_items_required` parameter."
+ " Only returned when `measure` is set to 'ratios'.",
)
bm: Optional[float] = Field(
default=None,
description="Book to Market equity ratio."
+ " Not returned if `measure` is set to 'ratios'.",
title="B/M",
)
be_me_high: Optional[float] = Field(
default=None,
description="Book Equity to Market Equity returns for the value portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="BE/ME High",
)
be_me_low: Optional[float] = Field(
default=None,
description="Book Equity to Market Equity returns for the growth portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="BE/ME Low",
)
ep: Optional[float] = Field(
default=None,
description="Earnings to Price ratio."
+ " Only returned when `measure` is set to 'ratios'.",
title="E/P",
)
e_p_high: Optional[float] = Field(
default=None,
description="Earnings to Price returns for the value portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="E/P High",
)
e_p_low: Optional[float] = Field(
default=None,
description="Earnings to Price returns for the growth portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="E/P Low",
)
ce_p: Optional[float] = Field(
default=None,
description="Cash Earnings to Price ratio."
+ " Only returned when `measure` is set to 'ratios'.",
title="CE/P",
)
ce_p_high: Optional[float] = Field(
default=None,
description="Cash Earnings to Price returns for the value portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="CE/P High",
)
ce_p_low: Optional[float] = Field(
default=None,
description="Cash Earnings to Price returns for the growth portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="CE/P Low",
)
yld: Optional[float] = Field(
default=None,
description="Dividend Yield ratio."
+ " Only returned when `measure` is set to 'ratios'.",
title="Yld",
)
yld_high: Optional[float] = Field(
default=None,
description="Dividend Yield returns for the value portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="Yld High",
)
yld_low: Optional[float] = Field(
default=None,
description="Dividend Yield returns for the growth portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="Yld Low",
)
yld_zero: Optional[float] = Field(
default=None,
description="Dividend Yield returns for firms not paying dividends."
+ " Not returned if `measure` is set to 'ratios'.",
title="Yld Zero",
)
class FamaFrenchCountryPortfolioReturnsFetcher(
Fetcher[
FamaFrenchCountryPortfolioReturnsQueryParams,
list[FamaFrenchCountryPortfolioReturnsData],
]
):
"""Country Portfolio Returns Fetcher."""
@staticmethod
def transform_query(
params: dict[str, Any],
) -> FamaFrenchCountryPortfolioReturnsQueryParams:
"""Transform the query parameters into a QueryParams object."""
return FamaFrenchCountryPortfolioReturnsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FamaFrenchCountryPortfolioReturnsQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> tuple:
"""Extract data from the Fama-French FTP."""
# pylint: disable=import-outside-toplevel
from openbb_famafrench.utils.helpers import get_international_portfolio # noqa
from warnings import warn
if query.measure == "ratios" and query.frequency != "annual":
warn("Only 'annual' frequency is supported for 'ratios' measure.")
query.frequency = "annual"
try:
return get_international_portfolio(
country=query.country,
measure=query.measure,
frequency=query.frequency,
dividends=True if query.dividends is None else query.dividends,
all_data_items_required=(
True
if query.all_data_items_required is None
else query.all_data_items_required
),
)
except Exception as e: # pylint: disable=broad-except
raise OpenBBError(e) from e
@staticmethod
def transform_data(
query: FamaFrenchCountryPortfolioReturnsQueryParams,
data: tuple,
**kwargs: Any,
) -> AnnotatedResult[list[FamaFrenchCountryPortfolioReturnsData]]:
"""Transform the extracted data."""
# pylint: disable=import-outside-toplevel
from pandas import MultiIndex
dfs, meta = data
if not dfs:
raise OpenBBError(
"The request was returned empty."
+ " This may be due to an invalid, or incorrectly mapped, portfolio choice."
)
returns_data = dfs[0] if isinstance(dfs, list) else dfs
# Values of -99.99 or -999 indicate no data,
# Drop columns that have no data.
for col in returns_data.columns:
if all(returns_data[col].values == "-99.99") or all(
returns_data[col].values == "-999"
):
returns_data = returns_data.drop(columns=[col])
else:
returns_data[col] = (
returns_data[col].astype(int)
if query.measure == "ratios" and col == "firms"
else returns_data[col].astype(float)
)
if query.start_date:
returns_data = returns_data[
returns_data.index >= query.start_date.strftime("%Y-%m-%d")
]
if query.end_date:
returns_data = returns_data[
returns_data.index <= query.end_date.strftime("%Y-%m-%d")
]
if isinstance(returns_data.columns, MultiIndex):
returns_data.columns = [
" ".join([str(level) for level in col if str(level) != ""])
for col in returns_data.columns.values
]
metadata = meta[0] if isinstance(meta, list) else meta
description = metadata.pop("description", "")
new_description = """
We form value and growth portfolios in each country using four ratios:
- book-to-market (B/M)
- earnings-price (E/P)
- cash earnings to price (CE/P)
- dividend yield (D/P)
We form the portfolios at the end of December each year by sorting on one of the four ratios
and then compute value-weighted returns for the following 12 months.
The value portfolios (High) contain firms in the top 30% of a ratio
and the growth portfolios (Low) contain firms in the bottom 30%.
There are two sets of portfolios. In one, firms are included only if we have data on all four ratios.
In the other, a firm is included in a sort variable's portfolios if we have data for that variable.
The market return (Mkt) for the first set is the value weighted average of the returns
for only firms with all four ratios.
The market return for the second set includes all firms with book-to-market data,
and Firms is the number of firms with B/M data.
The raw data are from Morgan Stanley Capital International for 1975 to 2006 and from
Bloomberg for 2007 to present.
"""
metadata["description"] = "### " + description + "\n\n" + new_description
return AnnotatedResult(
result=[
FamaFrenchCountryPortfolioReturnsData(**d)
for d in returns_data.reset_index().to_dict(orient="records")
],
metadata=meta[0] if isinstance(meta, list) else meta,
)

View File

@@ -0,0 +1,314 @@
"""Fama-French Factors Fetcher Model."""
# pylint: disable=unused-argument
from datetime import date as dateType
from typing import Any, Literal, Optional
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.app.service.system_service import SystemService
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.abstract.query_params import QueryParams
from pydantic import Field, model_validator
from openbb_famafrench.utils.constants import FACTOR_REGION_MAP, REGIONS_MAP
api_prefix = SystemService().system_settings.api_settings.prefix
factors_dict = {
"5_factors": "5_Factors",
"3_factors": "3_Factors",
"momentum": "Momentum",
"st_reversal": "ST_Reversal",
"lt_reversal": "LT_Reversal",
}
class FamaFrenchFactorsQueryParams(QueryParams):
"""Query parameters for Fama-French factors.
Source: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
"""
region: Literal[
"america",
"north_america",
"europe",
"japan",
"asia_pacific_ex_japan",
"developed",
"developed_ex_us",
"emerging",
] = Field(
description="Region of focus. Default is America.",
default="america",
json_schema_extra={
"x-widget_config": {
"options": [
{"label": k.replace("_", " ").title(), "value": v}
for k, v in REGIONS_MAP.items()
]
}
},
)
factor: Literal[
"5_factors", "3_factors", "momentum", "st_reversal", "lt_reversal"
] = Field(
default="3_factors",
description="Factor to fetch. "
+ "Default is the 3-Factor Model."
+ "Short/long-term reversals are available only for America.",
json_schema_extra={
"x-widget_config": {
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/famafrench/factor_choices",
"optionsParams": {"region": "$region", "is_portfolio": False},
},
},
)
frequency: Literal[
"daily",
"weekly",
"monthly",
"annual",
] = Field(
default="monthly",
description="Frequency of the factor data."
+ "Not all are available for all regions, "
+ "and intervals depend on the factor selected."
+ " Weekly is only available for the US 3-Factor Model.",
json_schema_extra={
"x-widget_config": {
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/famafrench/get_factor_choices",
"optionsParams": {
"region": "$region",
"factor": "$factor",
"is_portfolio": False,
},
},
},
)
start_date: Optional[dateType] = Field(
default=None,
description="Start date of the data. Defaults to the complete data range.",
json_schema_extra={
"x-widget_config": {
"value": "$currentDate-5y",
"description": "Start date of the factor data.",
}
},
)
end_date: Optional[dateType] = Field(
default=None,
description="End date of the data. Defaults to the complete data range.",
json_schema_extra={
"x-widget_config": {
"value": "$currentDate",
"description": "End date of the factor data.",
}
},
)
@model_validator(mode="before")
@classmethod
def validate_region_and_factor(cls, values):
"""Validate region and factor combination."""
region = values.get("region", "america")
factor = factors_dict.get(values.get("factor", "3_factors"), "")
frequency = values.get("frequency", "")
if factor and factor in ["st_reversal", "lt_reversal"] and region != "america":
raise ValueError(
f"Invalid region, '{region}', for factor '{factor}'. Only 'america' is supported."
)
if region and region not in list(FACTOR_REGION_MAP):
raise ValueError(
f"Invalid region: '{region}'. "
+ "Valid regions are: "
+ ", ".join(FACTOR_REGION_MAP.keys())
)
regional_factors = FACTOR_REGION_MAP[region]
if factor not in regional_factors.get("factors", {}):
raise ValueError(
f"Invalid factor: '{factor}' for region: '{region}'. "
+ "Valid factors are: "
+ ", ".join(regional_factors.get("factors", {}).keys())
)
if frequency:
intervals = regional_factors.get("intervals", {}).get(factor, {})
if frequency not in list(intervals):
raise ValueError(
f"Invalid frequency: '{frequency}' for factor: '{factor}'"
+ f" in region: '{region}'. "
+ "Valid frequencies are: "
+ ", ".join(intervals.keys())
)
return values
class FamaFrenchFactorsData(Data):
"""Fama-French Factors Data.
Source: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
"""
__alias_dict__ = {
"date": "Date",
"mkt_rf": "Mkt-RF",
"smb": "SMB",
"hml": "HML",
"rmw": "RMW",
"cma": "CMA",
"rf": "RF",
"mom": "Mom",
"lt_rev": "LT_Rev",
"st_rev": "ST_Rev",
"wml": "WML",
}
date: dateType = Field(
description="Date of the factor data.",
)
mkt_rf: Optional[float] = Field(
default=None,
description="Excess return on the market, value-weighted return of all firms,"
+ " minus the one-month Treasury bill rate."
+ " Not returned for momentum or reversal factors.",
title="Mkt-RF",
)
smb: Optional[float] = Field(
default=None,
description="Small minus big (SMB) factor returns."
+ " Average return of small minus big stock portfolios."
+ " Not returned for momentum or reversal factors.",
title="SMB",
)
hml: Optional[float] = Field(
default=None,
description="High minus low (HML) factor returns."
+ " Average return on value minus average return on growth portfolios."
+ " Not returned for momentum or reversal factors.",
title="HML",
)
rmw: Optional[float] = Field(
default=None,
description="Robust minus weak (RMW) factor returns."
+ " Average return on robust operating profitability portfolios,"
" minus average return on weak operating profitability portfolios."
+ " Only returned when 5 Factor model is selected.",
title="RMW",
)
cma: Optional[float] = Field(
default=None,
description="Conservative minus aggressive (CMA) factor returns."
+ " Average return on conservative investment portfolios,"
" minus average return on aggressive investment portfolios."
+ " Only returned when 5 Factor model is selected.",
title="CMA",
)
rf: Optional[float] = Field(
default=None,
description="Risk-free rate (RF) returns."
+ " The one-month US Treasury bill rate."
+ " Not returned when momentum or reversal factors are selected.",
title="RF",
)
mom: Optional[float] = Field(
default=None,
description="Momentum (Mom) factor returns."
+ " Returned only when the momentum factor is selected and the region is 'america'.",
title="Mom",
)
wml: Optional[float] = Field(
default=None,
description="Winners minus losers (WML) factor returns."
+ " Equal-weight average of the returns for the winner portfolios"
+ " minus the average of the returns for the loser portfolios."
+ " Returned only when the momentum factor is selected, and the region is not 'america'.",
title="WML",
)
lt_rev: Optional[float] = Field(
default=None,
description="Long-term reversal (LT_Rev) factor returns."
+ " Returned only when the long-term reversal factor is selected,"
+ " and the region is 'america'.",
title="LT_Rev",
)
st_rev: Optional[float] = Field(
default=None,
description="Short-term reversal (ST_Rev) factor returns."
+ " Returned only when the short-term reversal factor is selected,"
+ " and the region is 'america'.",
title="ST_Rev",
)
class FamaFrenchFactorsFetcher(
Fetcher[FamaFrenchFactorsQueryParams, list[FamaFrenchFactorsData]]
):
"""Fama-French Factors Fetcher."""
@staticmethod
def transform_query(params: dict[str, Any]) -> FamaFrenchFactorsQueryParams:
"""Transform query parameters to FamaFrenchFactorsQueryParams."""
return FamaFrenchFactorsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FamaFrenchFactorsQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> tuple:
"""Extract the data from the FTP."""
# pylint: disable=import-outside-toplevel, broad-except
from openbb_famafrench.utils.helpers import get_portfolio_data
factors = FACTOR_REGION_MAP.get(query.region, {})
factor = factors_dict.get(query.factor, "")
portfolio = factors.get("factors", {}).get(factor, "")
interval = factors.get("intervals", {}).get(factor, {}).get(query.frequency, "")
dataset = portfolio + interval
if not dataset:
raise OpenBBError(f"Invalid dataset, {dataset}")
try:
return get_portfolio_data(
dataset=dataset,
frequency=query.frequency,
)
except Exception as e:
raise OpenBBError(original=e) from e
@staticmethod
def transform_data(
query: FamaFrenchFactorsQueryParams,
data: tuple,
**kwargs: Any,
) -> AnnotatedResult[list[FamaFrenchFactorsData]]:
"""Transform the raw data and insert metadata."""
# pylint: disable=import-outside-toplevel
from pandas import to_datetime
table = data[0][0].reset_index()
if query.start_date:
table = table[to_datetime(table.Date) >= to_datetime(query.start_date)]
if query.end_date:
table = table[to_datetime(table.Date) <= to_datetime(query.end_date)]
records = table.to_dict(orient="records")
metadata = data[1][0]
return AnnotatedResult(
result=[FamaFrenchFactorsData(**r) for r in records],
metadata=metadata,
)

View File

@@ -0,0 +1,314 @@
"""Fama-French Factors Fetcher Model."""
# pylint: disable=unused-argument
from datetime import date as dateType
from typing import Any, Literal, Optional
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.abstract.query_params import QueryParams
from pydantic import Field
from openbb_famafrench.utils.constants import InternationalIndexPortfolios
class FamaFrenchInternationalIndexReturnsQueryParams(QueryParams):
"""International Index Returns Query parameters."""
__json_schema_extra__ = {
"country": {
"x-widget_config": {
"options": [
{
"value": d,
"label": d.replace("_", " ").title().replace("Ex ", "Ex-"),
}
for d in getattr(InternationalIndexPortfolios, "__args__", [])
]
}
},
}
index: InternationalIndexPortfolios = Field(
default="all",
description="International index to fetch the portfolio returns for. Defaults to 'all'.",
)
measure: Literal[
"usd",
"local",
"ratios",
] = Field(
default="usd",
description="The measure to fetch for the portfolio."
+ " Only 'annual' frequency is supported for 'ratios'.",
)
frequency: Literal["monthly", "annual"] = Field(
default="monthly",
description="The frequency of the data to fetch."
+ " Ignored when `measure` is set to 'ratios'.",
)
dividends: Optional[bool] = Field(
default=None, description="When False, portoflios exclude dividends."
)
all_data_items_required: Optional[bool] = Field(
default=None,
description="If True (default), includes firms with data for all four ratios."
+ " When False, includes only firms with Book-to-Market (B/M) data.",
)
start_date: Optional[dateType] = Field(
default=None,
description="The start date for the data. Defaults to the earliest available date.",
)
end_date: Optional[dateType] = Field(
default=None,
description="The end date for the data. Defaults to the latest available date.",
)
class FamaFrenchInternationalIndexReturnsData(Data):
"""International Index Portfolio Returns Data."""
__alias_dict__ = {
"date": "Date",
"mkt": "Mkt",
"firms": "Firms",
"bm": "B/M",
"be_me_high": "BE/ME High",
"be_me_low": "BE/ME Low",
"ep": "E/P",
"e_p_high": "E/P High",
"e_p_low": "E/P Low",
"ce_p": "CE/P",
"ce_p_high": "CE/P High",
"ce_p_low": "CE/P Low",
"yld": "Yld",
"yld_high": "Yld High",
"yld_low": "Yld Low",
"yld_zero": "Yld Zero",
}
date: dateType = Field(
description="The date of the data.",
)
mkt: Optional[float] = Field(
default=None,
description="""
The market return (Mkt) for the first set is the value weighted average
of the returns for only firms with all four ratios.
The market return for the second set includes all firms with book-to-market data,
and Firms is the number of firms with B/M data.
Not returned if `measure` is set to 'ratios'.
""",
)
firms: Optional[int] = Field(
default=None,
description="The number of firms, relative to `all_data_items_required` parameter."
+ " Only returned when `measure` is set to 'ratios'.",
)
bm: Optional[float] = Field(
default=None,
description="Book to Market equity ratio."
+ " Not returned if `measure` is set to 'ratios'.",
title="B/M",
)
be_me_high: Optional[float] = Field(
default=None,
description="Book Equity to Market Equity returns for the value portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="BE/ME High",
)
be_me_low: Optional[float] = Field(
default=None,
description="Book Equity to Market Equity returns for the growth portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="BE/ME Low",
)
ep: Optional[float] = Field(
default=None,
description="Earnings to Price ratio."
+ " Only returned when `measure` is set to 'ratios'.",
title="E/P",
)
e_p_high: Optional[float] = Field(
default=None,
description="Earnings to Price returns for the value portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="E/P High",
)
e_p_low: Optional[float] = Field(
default=None,
description="Earnings to Price returns for the growth portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="E/P Low",
)
ce_p: Optional[float] = Field(
default=None,
description="Cash Earnings to Price ratio."
+ " Only returned when `measure` is set to 'ratios'.",
title="CE/P",
)
ce_p_high: Optional[float] = Field(
default=None,
description="Cash Earnings to Price returns for the value portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="CE/P High",
)
ce_p_low: Optional[float] = Field(
default=None,
description="Cash Earnings to Price returns for the growth portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="CE/P Low",
)
yld: Optional[float] = Field(
default=None,
description="Dividend Yield ratio."
+ " Only returned when `measure` is set to 'ratios'.",
title="Yld",
)
yld_high: Optional[float] = Field(
default=None,
description="Dividend Yield returns for the value portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="Yld High",
)
yld_low: Optional[float] = Field(
default=None,
description="Dividend Yield returns for the growth portfolio."
+ " Not returned if `measure` is set to 'ratios'.",
title="Yld Low",
)
yld_zero: Optional[float] = Field(
default=None,
description="Dividend Yield returns for firms not paying dividends."
+ " Not returned if `measure` is set to 'ratios'.",
title="Yld Zero",
)
class FamaFrenchInternationalIndexReturnsFetcher(
Fetcher[
FamaFrenchInternationalIndexReturnsQueryParams,
list[FamaFrenchInternationalIndexReturnsData],
]
):
"""International Index Returns Fetcher."""
@staticmethod
def transform_query(
params: dict[str, Any],
) -> FamaFrenchInternationalIndexReturnsQueryParams:
"""Transform the query parameters into a QueryParams object."""
return FamaFrenchInternationalIndexReturnsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FamaFrenchInternationalIndexReturnsQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> tuple:
"""Extract data from the Fama-French FTP."""
# pylint: disable=import-outside-toplevel
from openbb_famafrench.utils.helpers import get_international_portfolio # noqa
from warnings import warn
if query.measure == "ratios" and query.frequency != "annual":
warn("Only 'annual' frequency is supported for 'ratios' measure.")
query.frequency = "annual"
try:
return get_international_portfolio(
index=query.index,
measure=query.measure,
frequency=query.frequency,
dividends=True if query.dividends is None else query.dividends,
all_data_items_required=(
True
if query.all_data_items_required is None
else query.all_data_items_required
),
)
except Exception as e: # pylint: disable=broad-except
raise OpenBBError(e) from e
@staticmethod
def transform_data(
query: FamaFrenchInternationalIndexReturnsQueryParams,
data: tuple,
**kwargs: Any,
) -> AnnotatedResult[list[FamaFrenchInternationalIndexReturnsData]]:
"""Transform the extracted data."""
# pylint: disable=import-outside-toplevel
from pandas import MultiIndex
dfs, meta = data
if not dfs:
raise OpenBBError(
"The request was returned empty."
+ " This may be due to an invalid, or incorrectly mapped, portfolio choice."
)
returns_data = dfs[0] if isinstance(dfs, list) else dfs
# Values of -99.99 or -999 indicate no data,
# Drop columns that have no data.
for col in returns_data.columns:
if all(returns_data[col].values == "-99.99") or all(
returns_data[col].values == "-999"
):
returns_data = returns_data.drop(columns=[col])
else:
returns_data[col] = (
returns_data[col].astype(int)
if query.measure == "ratios" and col == "firms"
else returns_data[col].astype(float)
)
if query.start_date:
returns_data = returns_data[
returns_data.index >= query.start_date.strftime("%Y-%m-%d")
]
if query.end_date:
returns_data = returns_data[
returns_data.index <= query.end_date.strftime("%Y-%m-%d")
]
if isinstance(returns_data.columns, MultiIndex):
returns_data.columns = [
" ".join([str(level) for level in col if str(level) != ""])
for col in returns_data.columns.values
]
metadata = meta[0] if isinstance(meta, list) else meta
description = metadata.pop("description", "")
new_description = """
We form value and growth portfolios in each country using four ratios:
- book-to-market (B/M)
- earnings-price (E/P)
- cash earnings to price (CE/P)
- dividend yield (D/P)
The returns on the index portfolios are constructed by averaging the returns on the country portfolios.
Each country is added to the index portfolios when the return data for the country begin;
the country start dates can be inferred from the country return files.
We weight countries in the index portfolios in proportion to their EAFE + Canada weights.
The raw data are from Morgan Stanley Capital International for 1975 to 2006 and from
Bloomberg for 2007 to present.
"""
metadata["description"] = "### " + description + "\n" + new_description
return AnnotatedResult(
result=[
FamaFrenchInternationalIndexReturnsData(**d)
for d in returns_data.reset_index().to_dict(orient="records")
],
metadata=meta[0] if isinstance(meta, list) else meta,
)

View File

@@ -0,0 +1,188 @@
"""Fama-French Factors Fetcher Model."""
# pylint: disable=unused-argument
from datetime import date as dateType
from typing import Any, Literal, Optional, Union
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.abstract.query_params import QueryParams
from pydantic import Field
from openbb_famafrench.utils.constants import RegionalPortfolios, portfolio_choices
class FamaFrenchRegionalPortfolioReturnsQueryParams(QueryParams):
"""Regional Portfolio Returns Query parameters."""
__json_schema_extra__ = {
"portfolio": {
"x-widget_config": {
"options": [
{"value": d, "label": d}
for d in portfolio_choices
if d.get("value") in getattr(RegionalPortfolios, "__args__", [])
]
}
},
}
portfolio: RegionalPortfolios = Field(
default="developed_ex_us_6_portfolios_me_op",
description="The specific portfolio file to fetch.",
)
measure: Literal[
"value",
"equal",
"number_of_firms",
"firm_size",
] = Field(
default="value",
description="The measure to fetch for the portfolio.",
)
frequency: Literal["monthly", "annual"] = Field(
default="monthly",
description="The frequency of the data to fetch."
+ " Ignored when the portfolio ends with 'daily'.",
)
start_date: Optional[dateType] = Field(
default=None,
description="The start date for the data. Defaults to the earliest available date.",
)
end_date: Optional[dateType] = Field(
default=None,
description="The end date for the data. Defaults to the latest available date.",
)
class FamaFrenchRegionalPortfolioReturnsData(Data):
"""Regional Portfolio Returns Data."""
__alias_dict__ = {
"date": "Date",
}
date: dateType = Field(
description="The date of the data.",
)
portfolio: str = Field(
description="The individual portfolio formation within the portfolio file.",
)
measure: Literal[
"value",
"equal",
"number_of_firms",
"firm_size",
] = Field(
description="The measure of the portfolio.",
)
value: Union[int, float] = Field(
description="The value represented by the 'measure'."
+ " Missing data are indicated by -99.99 or -999",
)
class FamaFrenchRegionalPortfolioReturnsFetcher(
Fetcher[
FamaFrenchRegionalPortfolioReturnsQueryParams,
list[FamaFrenchRegionalPortfolioReturnsData],
]
):
"""Regional Portfolio Returns Fetcher."""
@staticmethod
def transform_query(
params: dict[str, Any],
) -> FamaFrenchRegionalPortfolioReturnsQueryParams:
"""Transform the query parameters into a QueryParams object."""
return FamaFrenchRegionalPortfolioReturnsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FamaFrenchRegionalPortfolioReturnsQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> tuple:
"""Extract data from the Fama-French FTP."""
# pylint: disable=import-outside-toplevel
from openbb_famafrench.utils.helpers import get_portfolio_data
dataset = ""
for item in portfolio_choices:
if item.get("label") == query.portfolio:
dataset = item["value"]
break
try:
return get_portfolio_data(
dataset=dataset,
measure=query.measure,
frequency=(None if "daily" in dataset.lower() else query.frequency),
)
except Exception as e: # pylint: disable=broad-except
raise OpenBBError(e) from e
@staticmethod
def transform_data(
query: FamaFrenchRegionalPortfolioReturnsQueryParams,
data: tuple,
**kwargs: Any,
) -> AnnotatedResult[list[FamaFrenchRegionalPortfolioReturnsData]]:
"""Transform the extracted data."""
dfs, meta = data
if not dfs:
raise OpenBBError(
"The request was returned empty."
+ " This may be due to an invalid, or incorrectly mapped, portfolio choice."
)
returns_data = dfs[0] if isinstance(dfs, list) else dfs
# Values of -99.99 or -999 indicate no data,
# Drop columns that have no data.
for col in returns_data.columns:
if all(returns_data[col].values == "-99.99") or all(
returns_data[col].values == "-999"
):
returns_data = returns_data.drop(columns=[col])
else:
returns_data[col] = (
returns_data[col].astype(int)
if query.measure == "number_of_firms"
else returns_data[col].astype(float)
)
if query.start_date:
returns_data = returns_data[
returns_data.index >= query.start_date.strftime("%Y-%m-%d")
]
if query.end_date:
returns_data = returns_data[
returns_data.index <= query.end_date.strftime("%Y-%m-%d")
]
# Flatten the DataFrame to conform to the Data model
# This avoids having undefined fields.
flattened_data = (
returns_data.reset_index()
.melt(
id_vars=["Date"],
var_name="portfolio",
value_name="value",
)
.copy()
)
flattened_data.loc[:, "measure"] = query.measure
return AnnotatedResult(
result=[
FamaFrenchRegionalPortfolioReturnsData(**d)
for d in flattened_data.to_dict(orient="records")
],
metadata=meta[0] if isinstance(meta, list) else meta,
)

View File

@@ -0,0 +1,192 @@
"""Fama-French Factors Fetcher Model."""
# pylint: disable=unused-argument
from datetime import date as dateType
from typing import Any, Literal, Optional, Union
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.abstract.query_params import QueryParams
from pydantic import Field
from openbb_famafrench.utils.constants import USPortfolios, portfolio_choices
class FamaFrenchUSPortfolioReturnsQueryParams(QueryParams):
"""US Portfolio Returns Query parameters."""
__json_schema_extra__ = {
"portfolio": {
"x-widget_config": {
"options": [
{"label": d, "value": d}
for d in portfolio_choices
if d.get("value") in getattr(USPortfolios, "__args__", [])
]
}
},
}
portfolio: USPortfolios = Field(
default="portfolios_formed_on_me",
description="The specific portfolio file to fetch.",
)
measure: Literal[
"value",
"equal",
"number_of_firms",
"firm_size",
] = Field(
default="value",
description="The measure to fetch for the portfolio.",
)
frequency: Literal["monthly", "annual"] = Field(
default="monthly",
description="The frequency of the data to fetch."
+ " Ignored if the portfolio ends with 'daily' or 'weekly'.",
)
start_date: Optional[dateType] = Field(
default=None,
description="The start date for the data. Defaults to the earliest available date.",
)
end_date: Optional[dateType] = Field(
default=None,
description="The end date for the data. Defaults to the latest available date.",
)
class FamaFrenchUSPortfolioReturnsData(Data):
"""US Portfolio Returns Data."""
__alias_dict__ = {
"date": "Date",
}
date: dateType = Field(
description="The date of the data.",
)
portfolio: str = Field(
description="The individual portfolio formation within the portfolio file.",
)
measure: Literal[
"value",
"equal",
"number_of_firms",
"firm_size",
] = Field(
description="The measure of the portfolio.",
)
value: Union[int, float] = Field(
description="The value represented by the 'measure'."
+ " Missing data are indicated by -99.99 or -999",
)
class FamaFrenchUSPortfolioReturnsFetcher(
Fetcher[
FamaFrenchUSPortfolioReturnsQueryParams, list[FamaFrenchUSPortfolioReturnsData]
]
):
"""US Portfolio Returns Fetcher."""
@staticmethod
def transform_query(
params: dict[str, Any],
) -> FamaFrenchUSPortfolioReturnsQueryParams:
"""Transform the query parameters into a QueryParams object."""
return FamaFrenchUSPortfolioReturnsQueryParams(**params)
@staticmethod
async def aextract_data(
query: FamaFrenchUSPortfolioReturnsQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> tuple:
"""Extract data from the Fama-French FTP."""
# pylint: disable=import-outside-toplevel
from openbb_famafrench.utils.helpers import get_portfolio_data
dataset = ""
for item in portfolio_choices:
if item.get("label") == query.portfolio:
dataset = item["value"]
break
try:
return get_portfolio_data(
dataset=dataset,
measure=query.measure,
frequency=(
None
if "daily" in dataset.lower() or "weekly" in dataset.lower()
else query.frequency
),
)
except Exception as e: # pylint: disable=broad-except
raise OpenBBError(e) from e
@staticmethod
def transform_data(
query: FamaFrenchUSPortfolioReturnsQueryParams,
data: tuple,
**kwargs: Any,
) -> AnnotatedResult[list[FamaFrenchUSPortfolioReturnsData]]:
"""Transform the extracted data."""
dfs, meta = data
if not dfs:
raise OpenBBError(
"The request was returned empty."
+ " This may be due to an invalid, or incorrectly mapped, portfolio choice."
)
returns_data = dfs[0] if isinstance(dfs, list) else dfs
# Values of -99.99 or -999 indicate no data,
# Drop columns that have no data.
for col in returns_data.columns:
if all(returns_data[col].values == "-99.99") or all(
returns_data[col].values == "-999"
):
returns_data = returns_data.drop(columns=[col])
else:
returns_data[col] = (
returns_data[col].astype(int)
if query.measure == "number_of_firms"
else returns_data[col].astype(float)
)
if query.start_date:
returns_data = returns_data[
returns_data.index >= query.start_date.strftime("%Y-%m-%d")
]
if query.end_date:
returns_data = returns_data[
returns_data.index <= query.end_date.strftime("%Y-%m-%d")
]
# Flatten the DataFrame to conform to the Data model
# This avoids having undefined fields.
flattened_data = (
returns_data.reset_index()
.melt(
id_vars=["Date"],
var_name="portfolio",
value_name="value",
)
.copy()
)
flattened_data.loc[:, "measure"] = query.measure
return AnnotatedResult(
result=[
FamaFrenchUSPortfolioReturnsData(**d)
for d in flattened_data.to_dict(orient="records")
],
metadata=meta[0] if isinstance(meta, list) else meta,
)

View File

@@ -0,0 +1 @@
"""Fama-French Utilities Module."""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,969 @@
"""Fama-French utilities for handling US and International portfolios and factors."""
# pylint: disable=R0912,R0913,R0914,R0917,R1702,W0612,W0613
from functools import lru_cache
from typing import Optional
from openbb_famafrench.utils.constants import (
BASE_URL,
BREAKPOINT_FILES,
COUNTRY_PORTFOLIO_FILES,
COUNTRY_PORTFOLIOS_URLS,
DATASET_CHOICES,
FACTOR_REGION_MAP,
INTERNATIONAL_INDEX_PORTFOLIO_FILES,
INTERNATIONAL_INDEX_PORTFOLIOS_URLS,
REGIONS_MAP,
)
async def get_factor_choices(
region: Optional[str] = None,
factor: Optional[str] = None,
is_portfolio: bool = False,
portfolio: Optional[str] = None,
):
"""Get the list of choices for factors based on combinations of parameters.
This function is for generating choices for Workspace dropdown menus.
It is not intended to be used directly.
Parameters
----------
region : Optional[str]
The region for which to get factor choices. If None, all regions are considered.
factor : Optional[str]
The specific factor for which to get choices. If None, all factors for the region are
considered.
is_portfolio : bool
If True, the function will return portfolio choices instead of factor choices.
portfolio : Optional[str]
A specific portfolio.
Returns
-------
list
A list of dictionaries representing the choices for factors or portfolios.
Each dictionary contains a 'label' and a 'value' key.
"""
if region and not factor and not is_portfolio:
factors = FACTOR_REGION_MAP.get(region, {}).get("factors", {})
return [
{
"label": k.replace("_", " ")
.title()
.replace("Lt", "LT")
.replace("St", "ST"),
"value": k,
}
for k in list(factors)
]
if region and factor and not is_portfolio:
intervals = (
FACTOR_REGION_MAP.get(region, {}).get("intervals", {}).get(factor, {})
)
intervals = [
{"label": k.replace("_", " ").title(), "value": k} for k in list(intervals)
]
return intervals
if not is_portfolio:
return [
{"label": k.replace("_", " ").title(), "value": k}
for k in list(FACTOR_REGION_MAP)
]
region = region or ""
mapped_region = REGIONS_MAP.get(region, "").replace("_", " ")
if is_portfolio and region and not portfolio:
portfolios = (
[
{
"label": (
d["label"].replace(mapped_region, "").strip()
if region != "america"
else d["label"]
),
"value": d["value"],
}
for d in DATASET_CHOICES
if "Portfolio" in d.get("value", "")
and d["value"].startswith(REGIONS_MAP.get(region, region))
and "daily" not in d.get("value", "").lower()
]
if region != "america"
else [
d
for d in DATASET_CHOICES
if "Portfolio" in d.get("value", "")
and all(
not d.get("value", "").startswith(reg)
for reg in REGIONS_MAP.values()
if reg
)
and "daily" not in d.get("value", "").lower()
]
)
if "ex_" not in region:
portfolios = [d for d in portfolios if "ex_" not in d.get("value", "")]
return portfolios
if is_portfolio and region and portfolio:
portfolios = (
[
d
for d in DATASET_CHOICES
if "Portfolio" in d.get("value", "")
and d["value"].startswith(REGIONS_MAP.get(region, region))
and portfolio in d.get("value", "")
]
if region != "america"
else [
d
for d in DATASET_CHOICES
if "Portfolio" in d.get("value", "")
and all(
not d.get("value", "").startswith(reg)
for reg in REGIONS_MAP.values()
if reg
)
and portfolio in d.get("value", "")
]
)
has_daily = False
frequencies = ["monthly", "annual"]
for d in portfolios:
if "daily" in d.get("value", "").lower():
has_daily = True
break
frequencies = ["daily"] + frequencies if has_daily else frequencies
return [{"label": k.title(), "value": k} for k in frequencies]
return [{"label": "No choices found. Try a new parameter.", "value": None}]
@lru_cache(maxsize=64)
def download_file(dataset) -> str:
"""Download the specified dataset file from the Ken French data library.
Note: This function is not intended for direct use, it is called by `get_portfolio_data`.
"""
# pylint: disable=import-outside-toplevel
import zipfile
from io import BytesIO
from openbb_core.provider.utils.helpers import get_requests_session
url_map = {item["label"]: item["value"] for item in DATASET_CHOICES}
if dataset.replace("_", " ") not in list(url_map) and dataset not in list(
url_map.values()
):
raise ValueError(
f"Dataset {dataset} not found in available datasets: {list(url_map)}"
)
url = (
BASE_URL + dataset
if dataset.endswith(".zip")
else BASE_URL + url_map[dataset.replace("_", " ")]
)
with get_requests_session() as session:
response = session.get(url)
response.raise_for_status()
data = ""
with zipfile.ZipFile(BytesIO(response.content)) as f:
with f.open(f.namelist()[0]) as file: # type: ignore
data = file.read() # type: ignore
try:
data = data.decode("utf-8") # type: ignore
except UnicodeDecodeError:
data = data.decode("latin-1") # type: ignore
return data
@lru_cache(maxsize=64)
def download_international_portfolios(url):
"""Download the international index portfolios file.
Note: This function is not intended for direct use,
it is called by `get_international_portfolio`.
"""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import get_requests_session
with get_requests_session() as session:
response = session.get(url)
response.raise_for_status()
return response
def apply_date(x):
"""Pandas Apply helper to convert various date formats to a standard YYYY-MM-DD format."""
# pylint: disable=import-outside-toplevel
from pandas import to_datetime
date = str(x).replace(" ", "")
if len(date) == 6:
date = date[:4] + "-" + date[4:]
date = to_datetime(date, format="%Y-%m")
date = date.date().strftime("%Y-%m-%d")
elif len(date) == 8:
date = date[:4] + "-" + date[4:6] + "-" + date[6:]
elif len(date) == 4:
date = date + "-12-31"
return date
def read_csv_file(data: str):
"""Parse the raw data from a .csv file into a list of dictionaries representing tables.
Note: This function is not intended for direct use, it is called by `get_portfolio_data`.
"""
# pylint: disable=import-outside-toplevel
import re
lines = data.splitlines()
tables: list = []
general_description: list = []
# Extract general description from the top of the file
description_end_idx = 0
for idx, line in enumerate(lines):
if line.strip().startswith(",") or re.match(r"^\s*\d{4,6}", line):
description_end_idx = idx
break
if line.strip():
general_description.append(line.strip())
# Extract initial table metadata from the last line of general description
table_metadata: str = ""
if general_description and (
"Monthly" in general_description[-1]
or "Annual" in general_description[-1]
or "Returns" in general_description[-1]
):
table_metadata = general_description[-1]
general_description = general_description[:-1]
general_desc_text = "\n".join(general_description)
# Process tables in the file
i = description_end_idx
while i < len(lines):
# Skip empty lines
while i < len(lines) and not lines[i].strip():
i += 1
if i >= len(lines):
break
# Check if this is a table header (starts with comma)
if lines[i].strip().startswith(","):
# Look for metadata line before this header
metadata = table_metadata # Default to initial metadata
j = i - 1
while j >= description_end_idx:
if lines[j].strip():
metadata = lines[j].strip()
break
j -= 1
# Parse headers
header_line = lines[i].strip()
headers = ["Date"] + header_line.split(",")[1:]
# Move past header row
i += 1
# Collect data rows
data_rows = []
# Look for data rows until we hit the next header or end of file
while i < len(lines):
line = lines[i].strip()
if not line:
break
values = line.split(",")
if values:
data_rows.append([d.strip() for d in values])
i += 1
# Add table if it has data
if data_rows:
tables.append(
{
"meta": metadata,
"headers": headers,
"rows": data_rows,
"is_annual": "Annual" in metadata,
}
)
# Check for standalone metadata
elif "--" in lines[i] and any(
d in lines[i] for d in ["Daily", "Monthly", "Annual", "Weekly"]
):
# Update metadata for next table
table_metadata = lines[i].strip()
i += 1
else:
# Skip other lines
i += 1
return tables, general_desc_text
def process_csv_tables(tables, general_description="") -> tuple:
"""Convert parsed table dictionaries from CSV files to pandas DataFrames.
Note: This function is not intended for direct use, it is called by `get_portfolio_data`.
"""
# pylint: disable=import-outside-toplevel
import warnings # noqa
from pandas import DataFrame
dataframes: list = []
metadata: list = []
for table_idx, table in enumerate(tables):
# Skip empty tables
if not table["rows"]:
continue
# Create DataFrame from rows
rows_data = table.get("rows", [])
headers = table["headers"]
# Check if we have enough headers
max_cols = max(len(row) for row in rows_data)
if len(headers) < max_cols:
headers.extend([f"Column_{i}" for i in range(len(headers), max_cols)])
# Create dataframe with proper dimensions and headers
df = DataFrame(rows_data)
if df.empty or df.shape[1] == 0:
continue
# Set column names
df.columns = headers[: df.shape[1]]
# Convert Date column to datetime
try:
# Convert YYYYMM format to datetime
df["Date"] = df.Date.apply(apply_date)
except Exception as e: # pylint: disable=W0718
warnings.warn(f"Error parsing dates: {e}")
df["Date"] = df["Date"].astype(str)
# Set Date as index
df = df.set_index("Date")
df = df.sort_index()
dataframes.append(df)
# Get metadata from the table
table_meta_desc = table["meta"].strip()
# Determine frequency from the table's metadata
frequency = "monthly"
if "Annual" in table_meta_desc:
frequency = "annual"
# Create metadata entry
table_meta = {
"description": f"### {table_meta_desc}\n\n"
+ general_description.replace("\n", " ")
+ "\n\n",
"frequency": frequency,
"formations": headers[1:],
}
metadata.append(table_meta)
return dataframes, metadata
def read_dat_file(data: str) -> list:
"""Parse the raw data from a .dat file into a list of dictionaries representing tables.
Note: This function is not intended for direct use,
it is called by `get_international_portfolio`.
"""
# pylint: disable=import-outside-toplevel
import re
lines = data.splitlines()
tables: list = []
i = 0
current_table: dict = {}
while i < len(lines):
# Check for table separator or new table metadata indicator
if re.match(r"\s*,", lines[i]) or (
i > 1
and "Data" in lines[i]
and "current_table" in locals()
and current_table["rows"]
):
# Add current table if it exists and has rows
if "current_table" in locals() and current_table["rows"]:
tables.append(current_table)
# If this is a separator line, skip it
if re.match(r"\s*,", lines[i]):
i += 1
continue
# Start a new table
current_table = {"meta": "", "spanners": "", "headers": [], "rows": []}
meta_lines = []
# Process metadata (which may span multiple lines)
while i < len(lines):
line = lines[i].strip()
# Check if this line looks like the start of data or spanner rows
if (
"--" in line # Spanner line
or "Firms" in line.split()
and any(c.isdigit() for c in lines[i + 1])
if i + 1 < len(lines)
else False # Firms header
or line in ["", " "] # Empty separator
or (line and line[0].isdigit() and len(line.split()) > 2) # Data row
):
break
if line: # Only add non-empty lines
meta_lines.append(line)
i += 1
# Join all metadata lines
current_table["meta"] = "\n".join(meta_lines)
# Process spanners if we have a line with dashes
if i < len(lines) and "--" in lines[i]:
current_table["spanners"] = lines[i]
i += 1
else:
# No spanners line found
current_table["spanners"] = "" # Empty spanners for tables like "Firms"
# Process headers - handle special case for "Firms" tables
if i < len(lines):
header_line = lines[i].strip()
# Check if this is a "Firms" table with its specific format
if (
"Firms" in header_line.split()
or header_line
and not header_line[0].isdigit()
):
current_table["headers"] = ["Date"] + header_line.split()
i += 1
elif "Firms" in current_table["meta"]:
# Default headers for Firms tables if header row is missing
current_table["headers"] = [
"Date",
"Firms",
"B/M",
"E/P",
"CE/P",
"Yld",
]
else:
# Skip this table - malformed
while i < len(lines) and not (
re.match(r"\s*,", lines[i]) or "Data" in lines[i]
):
i += 1
continue
# Process rows until next separator or next table start
row_count = 0
while i < len(lines) and not (
re.match(r"\s*,", lines[i]) or "Data" in lines[i]
):
# Skip copyright lines, empty lines, and other non-data lines
if (
lines[i].strip()
and not lines[i].strip().startswith("Copyright")
and "©" not in lines[i]
and any(c.isdigit() for c in lines[i])
): # Ensure line has at least one digit (likely a date)
current_table["rows"].append(lines[i].split())
row_count += 1
i += 1
# Add the last table if it has rows
if "current_table" in locals() and current_table["rows"]:
tables.append(current_table)
return tables
def get_international_portfolio_data(
index: Optional[str] = None,
country: Optional[str] = None,
dividends: bool = True,
) -> str:
"""Download and extract the international index or country portfolio data.
Note: Not intended for direct use, this function is called by `get_international_portfolio`.
"""
# pylint: disable=import-outside-toplevel
import zipfile
from io import BytesIO
url: str = ""
data: str = ""
if not index and not country:
raise ValueError("Please provide either an index or a country.")
if index and country:
raise ValueError(
"Please provide either an index or a country, not both at the same time."
)
if index:
index = INTERNATIONAL_INDEX_PORTFOLIO_FILES.get(index)
if not index:
raise ValueError(
f"Index {index} not found in available indexes: "
+ f"{INTERNATIONAL_INDEX_PORTFOLIO_FILES}"
)
url = (
BASE_URL
+ INTERNATIONAL_INDEX_PORTFOLIOS_URLS["dividends" if dividends else "ex"]
)
if country:
country = country.title().replace("_", " ")
if country not in list(COUNTRY_PORTFOLIO_FILES):
raise ValueError(
f"Country {country} not found in available countries: "
+ f"{COUNTRY_PORTFOLIO_FILES}"
)
url = BASE_URL + COUNTRY_PORTFOLIOS_URLS["dividends" if dividends else "ex"]
index = COUNTRY_PORTFOLIO_FILES[country]
response = download_international_portfolios(url)
with zipfile.ZipFile(BytesIO(response.content)) as f:
filenames = f.namelist()
if index in filenames:
try:
with f.open(index) as file:
data = file.read().decode("utf-8")
except UnicodeDecodeError:
# Fallback to latin-1 encoding if utf-8 fails
with f.open(index) as file:
data = file.read().decode("latin-1")
else:
raise ValueError(
f"Index {index} not found in available indexes: {filenames}"
)
return data
def process_international_portfolio_data(tables: list, dividends: bool = True) -> tuple:
"""Convert parsed table dictionaries to pandas DataFrames with proper multi-index columns.
Note: Not intended for direct use, this function is called by `get_international_portfolio`.
"""
# pylint: disable=import-outside-toplevel
import re # noqa
import warnings
from pandas import DataFrame, MultiIndex
dataframes: list = []
metadata: list = []
for table_idx, table in enumerate(tables):
# Extract spanner groups
spanner_groups = table["spanners"].replace("-", "").split()
# Create DataFrame from rows
rows_data = table.get("rows", [])
df = DataFrame(rows_data)
if df.empty:
continue
# Set column names based on headers
headers = table["headers"]
if len(headers) == len(df.columns):
df.columns = headers
# Check if this is a special case table with "Firms" column
has_firms_column = "Firms" in df.columns
# Parse and set Date column
try:
df["Date"] = df["Date"].apply(apply_date)
except Exception as e: # pylint: disable=W0718
warnings.warn(f"Error parsing dates. Using string conversion. -> {e}")
df["Date"] = df["Date"].astype(str)
# Set Date as index (or Date and Mkt if applicable)
df = (
df.set_index(["Date", "Mkt"])
if "Mkt" in df.columns and not has_firms_column
else df.set_index("Date")
)
# Create multi-index columns only for regular tables (not those with Firms)
if not has_firms_column and spanner_groups and len(df.columns) > 0:
# Create multi-index columns
remaining_headers = list(df.columns)
bottom_level = remaining_headers
# Calculate columns per group
cols_per_group = len(remaining_headers) // len(spanner_groups)
# Create top level for multi-index
top_level = []
for group in spanner_groups:
top_level.extend([group] * cols_per_group)
# Handle Zero column specially
if "Zero" in remaining_headers:
zero_idx = remaining_headers.index("Zero")
# Find Yld group
for group in spanner_groups:
if group.lower() == "yld":
# Ensure top_level has enough elements
while len(top_level) <= zero_idx:
top_level.append("")
top_level[zero_idx] = group
break
# Create the multi-index columns
if len(top_level) == len(bottom_level):
df.columns = MultiIndex.from_arrays([top_level, bottom_level])
dataframes.append(df)
# Format metadata for description
meta_text = table["meta"].strip().replace("\n", " - ")
if dividends is False:
meta_text += " - Ex-Dividends"
# Format metadata nicely, replacing multiple spaces with a single space
meta_text = re.sub(r"\s{2,}", " ", meta_text)
is_annual = (
df.index[0][0][-2:] in ["31", 31]
if isinstance(df.index, MultiIndex)
else df.index[0][-2:] in ["31", 31]
)
table_meta = {
"description": meta_text,
"frequency": "annual" if is_annual else "monthly",
"formations": (
[d for d in df.columns.tolist() if d != "Firms"]
if has_firms_column
else spanner_groups
),
}
metadata.append(table_meta)
return dataframes, metadata
@lru_cache(maxsize=64)
def get_international_portfolio(
index: Optional[str] = None,
country: Optional[str] = None,
dividends: bool = True,
frequency: Optional[str] = None,
measure: Optional[str] = None,
all_data_items_required: Optional[bool] = None,
) -> tuple:
"""Get the international portfolio data for a given index or country.
Parameters
----------
index : Optional[str]
The index for which to get the portfolio data. If None, country must be provided.
country : Optional[str]
The country for which to get the portfolio data. If None, index must be provided.
dividends : bool
When False, returns data with dividends excluded. Defaults to True.
frequency : Optional[str]
The frequency of the data to return. Can be 'monthly', or 'annual'.
If None, defaults to 'monthly'.
measure : Optional[str]
The measure of the data to return. Can be 'usd', 'local', or 'ratios'.
If None, defaults to 'usd'.
all_data_items_required : Optional[bool]
Default is True.
If True, returns only data for firms with all 4 ratios of B/M, E/P, CE/P, and Yld.
When False, returns data for firms with B/M data only.
Returns
-------
tuple
A tuple containing a list of pandas DataFrames and a list of metadata dictionaries.
In most scenarios, there will only be 1 DataFrame and 1 metadata dictionary.
Raises
------
ValueError
When an invalid combination of parameters or unsupported values are supplied.
"""
measure = measure.lower() if measure is not None else "usd"
data = get_international_portfolio_data(index, country, dividends)
tables = read_dat_file(data)
dataframes, metadata = process_international_portfolio_data(tables, dividends)
if measure and measure not in ["usd", "local", "ratios"]:
raise ValueError(
f"Measure {measure} not supported. Choose from 'usd', 'local', or 'ratios'."
)
if frequency == "monthly" and measure == "ratios":
raise ValueError("Only annual frequency is available for 'ratios' measure.")
if frequency:
dfs = [
df
for df, meta in zip(dataframes, metadata)
if meta["frequency"] == frequency
]
dfs_meta = [meta for meta in metadata if meta["frequency"] == frequency]
else:
dfs = dataframes
dfs_meta = metadata
if measure == "local":
dfs = [df for df, meta in zip(dfs, dfs_meta) if "Local" in meta["description"]]
dfs_meta = [meta for meta in dfs_meta if "Local" in meta["description"]]
elif measure == "usd":
dfs = [df for df, meta in zip(dfs, dfs_meta) if "Dollar" in meta["description"]]
dfs_meta = [meta for meta in dfs_meta if "Dollar" in meta["description"]]
elif measure == "ratios":
dfs = [df for df, meta in zip(dfs, dfs_meta) if "Ratios" in meta["description"]]
dfs_meta = [meta for meta in dfs_meta if "Ratios" in meta["description"]]
if all_data_items_required is False and measure != "ratios":
dfs = [
df
for df, meta in zip(dfs, dfs_meta)
if "Not Reqd" not in meta["description"]
]
dfs_meta = [
meta
for meta in dfs_meta
if meta.get("description", "").endswith("Not Reqd")
]
elif all_data_items_required is True:
dfs = [
df for df, meta in zip(dfs, dfs_meta) if "Required" in meta["description"]
]
dfs_meta = [meta for meta in dfs_meta if "Required" in meta["description"]]
return dfs, dfs_meta
@lru_cache(maxsize=64)
def get_portfolio_data(
dataset: str, frequency: Optional[str] = None, measure: Optional[str] = None
) -> tuple:
"""Get US portfolio data for a given dataset.
Parameters
----------
dataset : str
The dataset to retrieve. Must be one of the available datasets in DATASET_CHOICES.
frequency : Optional[str]
The frequency of the data to return. Can be 'monthly', 'annual', or 'daily'.
If None, defaults to 'monthly'.
measure : Optional[str]
The measure of the data to return.
Can be 'value', 'equal', 'number_of_firms', or 'firm_size'.
If None, defaults to 'value'.
Returns
-------
tuple
A tuple containing a list of pandas DataFrames and a list of metadata dictionaries.
In most scenarios, there will only be 1 DataFrame and 1 metadata dictionary.
Raises
------
ValueError
When an invalid combination of parameters or unsupported values are supplied.
"""
if frequency and frequency.lower() not in ["monthly", "annual", "daily"]:
raise ValueError(
f"Frequency {frequency} not supported. Choose from 'monthly', 'annual', or 'daily'."
)
if measure and measure not in ["value", "equal", "number_of_firms", "firm_size"]:
raise ValueError(
f"Measure {measure} not supported. "
+ "Choose from 'value', 'equal', 'number_of_firms', or 'firm_size'."
)
if measure in ["number_of_firms", "firm_size"] and frequency == "annual":
raise ValueError(
f"Measure '{measure}' is only available for monthly frequency."
)
if "Factor" in dataset:
measure = None
file = download_file(dataset)
table, desc = read_csv_file(file)
dfs, metadata = process_csv_tables(table, desc)
if frequency:
out_dfs = [
df
for df, meta in zip(dfs, metadata)
if meta["frequency"].lower() == frequency.lower()
]
out_metadata = [
meta for meta in metadata if meta["frequency"].lower() == frequency.lower()
]
else:
out_dfs = dfs
out_metadata = metadata
if measure is not None:
if measure in ["value", "equal"]:
out_dfs = [
df
for df, meta in zip(out_dfs, out_metadata)
if "--" in meta["description"]
and measure.lower() in meta["description"].split(" -- ")[0].lower()
]
out_metadata = [
meta
for meta in out_metadata
if "--" in meta["description"]
and measure.lower() in meta["description"].split(" -- ")[0].lower()
]
elif measure == "number_of_firms":
out_dfs = [
df
for df, meta in zip(out_dfs, out_metadata)
if "Number of Firms" in meta["description"]
]
out_metadata = [
meta
for meta in out_metadata
if "Number of Firms" in meta["description"]
]
elif measure == "firm_size":
out_dfs = [
df
for df, meta in zip(out_dfs, out_metadata)
if "Average Firm Size" in meta["description"]
]
out_metadata = [
meta
for meta in out_metadata
if "Average Firm Size" in meta["description"]
]
return out_dfs, out_metadata
@lru_cache(maxsize=8)
def get_breakpoint_data(
breakpoint_type: str,
) -> tuple:
"""Get US breakpoint data for a given dataset.
Parameters
----------
breakpoint_type : str
The breakpoint to retrieve. Must be one of the available breakpoints in BREAKPOINT_FILES.
Returns
-------
tuple
A tuple containing a pandas DataFrames a metadata dictionary.
"""
# pylint: disable=import-outside-toplevel
from io import StringIO # noqa
from pandas import offsets, read_csv, to_datetime
col_names = [
"num_firms",
"percentile_5",
"percentile_10",
"percentile_15",
"percentile_20",
"percentile_25",
"percentile_30",
"percentile_35",
"percentile_40",
"percentile_45",
"percentile_50",
"percentile_55",
"percentile_60",
"percentile_65",
"percentile_70",
"percentile_75",
"percentile_80",
"percentile_85",
"percentile_90",
"percentile_95",
"percentile_100",
]
breakpoint_file = BREAKPOINT_FILES.get(breakpoint_type)
file = download_file(breakpoint_file)
metadata = ""
for line in file.splitlines()[:3]:
if not line.strip() or line.strip().startswith("19"):
break
metadata += line.strip() + " "
metadata = metadata.strip()
ratio_breakpoints = [
"be-me",
"e-p",
"cf-p",
"d-p",
]
if breakpoint_type in ratio_breakpoints:
col_names = ["num_firms_less_than_0", "num_firms_greater_than_0"] + col_names[
1:
]
breakpoint_data = StringIO(file)
df = read_csv(
breakpoint_data,
skiprows=1 if breakpoint_type == "me" else 3,
index_col=None,
header=0,
names=col_names,
skipfooter=1,
engine="python",
).reset_index()
df = df.rename(columns={"index": "date"})
df.date = df.date.apply(
lambda x: (
(to_datetime(x, format="%Y%m") + offsets.MonthEnd(0)).strftime("%Y-%m-%d")
if len(str(x)) == 6
else str(x) + "-12-31"
)
)
return [df], [metadata]

1741
openbb_platform/providers/famafrench/poetry.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
[tool.poetry]
name = "openbb-famafrench"
version = "1.0.0"
description = "Fama-French data integration for OpenBB Platform."
authors = ["OpenBB Team <hello@openbb.co>"]
license = "AGPL-3.0-only"
readme = "README.md"
packages = [{ include = "openbb_famafrench" }]
[tool.poetry.dependencies]
python = ">=3.9.21,<3.13"
openbb-core = "^1.4.7"
[tool.poetry.scripts]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.plugins."openbb_provider_extension"]
famafranch = "openbb_famafrench:famafrench_provider"
[tool.poetry.plugins."openbb_core_extension"]
famafrench = "openbb_famafrench.famafrench_router:router"

View File

@@ -0,0 +1 @@
"""OpenBB Fama-French Tests Module."""

View File

@@ -0,0 +1,105 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/OP_Breakpoints_CSV.zip
response:
body:
string: !!binary |
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
AGQAAAAnDwAAAAA=
headers:
accept-ranges:
- bytes
content-length:
- '4001'
content-type:
- application/x-zip-compressed
date:
- Mon, 07 Jul 2025 04:22:15 GMT
etag:
- '"804fcbe2cb71db1:0"'
last-modified:
- Tue, 28 Jan 2025 21:30:43 GMT
server:
- Microsoft-IIS/10.0
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,105 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate, br, zstd
Connection:
- keep-alive
method: GET
uri: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/OP_Breakpoints_CSV.zip
response:
body:
string: !!binary |
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
AGQAAAAnDwAAAAA=
headers:
accept-ranges:
- bytes
content-length:
- '4001'
content-type:
- application/x-zip-compressed
date:
- Mon, 07 Jul 2025 04:50:30 GMT
etag:
- '"804fcbe2cb71db1:0"'
last-modified:
- Tue, 28 Jan 2025 21:30:43 GMT
server:
- Microsoft-IIS/10.0
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,251 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip
response:
body:
string: !!binary |
UEsDBBQAAAAIAAdQw1ofcX3rei8AANeUAAAdAAAARi1GX1Jlc2VhcmNoX0RhdGFfRmFjdG9ycy5j
c3ZslV2OUzEMhd+R2MNdQBrFdn4fAYFAYiTEwAIKdJiKoZXajoDd03Ni4D7wEl3dL3bsE9v5cL8/
L3f7h93yY3tePp9228vuy/J43h++Lpf73aJJS8rLi/e375Yv28v20/a8i0+ffLgi2Xw/Hi73y4fn
+4eH5XQ15I7l8XDZP8Awp7JsT7vl7nT8vrz59Ol4OR8Py7Pz+fh5f919jsvtZXu64ChuoUkNy+X/
zhEoPb14uTw/3j1bPt5et93MbdfAz4+nXwu3vzl82f28Rvn0Sbj5dtm8fxVub56H1zdvw/tXT5/I
0Jpa0NhH2GgsBauNkKKq036lNYeNRMnBYhfA4hA7rQNavX6mAmgTSgobi9rCJsE0xa7YrU4laCyZ
ULBWARWnilMV/1ODY3jXTtiS8H/BahpyLJ0xOcUfgRETawpWnVlALIi3NaYKS0tOEWSGJRWoZe21
hBJzCQKNcuxpbVf/uipC37o+s4UWtboWkNHWtj1IHAOptIY9vawzHSH7/5qwpzRQmRQC56gjaEyG
z0xanEqosSgk6NSrr03xn7c3OmNSQCXsSfyoHDUxMqFfpwqDynpJFbhCNzPHFnpsTMimaxoPpxlO
oSCLScv6WKrLgFk5iRqrw4oEB0GbxQAqTpF4Na9DBJ/Xtj3UPwFnrGJrCv06SiXjwA5LbZNJChIN
larTEDCLQwkiKOoNV50FY92pYivlG8aQlHVMOpKEHCuLwgocKKhlp0r5MlcLEmsFrU7prhcKItCs
rW1zkJibXyoOX5miZyrvpcTMNigMMzuuYUB8Zoq1Id+iTlEQuSNmylV5rDnsodNoxEYZZvoOBw8s
QTypxrlRJhUciG5lQiW0eXu5OkZL4YaYdQ9lThlrjjVI1A5bOJqjxqmlJKFgsljMA+n6AHCoGEFp
FpMvlpxZaGgQizb4mwE7ozY+f+bqbUkMILxc9ZAoo1bHVyA1JgpZBusO2COmyoIC8HHRSJPTGSNd
z0JHdGk4HVMqAc8C0ZPPcnJE030caKIea3MRtl1GSCxNznqxSRFTi519WBt81/FPTEkSKvrZUHw9
agYrzjQI785mEVTDqdmhsSYLutA4JRNPdYoqTTHNUadYjdbdOcQ2DMECDE/ClBzXcKXDKAX7taW1
dYNFNbQM54OSVqfdW1h5W+K25hQGPbZMHSmO5hWXFJpPdkXm7EZJDgWdIgUdlRCcUMvmVJlUQRWw
8IdzJdckbF32o4Th9WUOlS8Vn3Puy21NMSQFJYQx+DdlqY6RTYsjz8mhMTVQcQqvfORyVMFdpE69
HFd/xqhaib0AqsMWzGIVf7Ks+EA0xz1Yg0ZiKD7D07PGvJ0xh3CB1tZWWBIFk5n1YMnktjpd2Awd
j223uXessULK30yXW67DMAhEN2RFBmyw97+xqxlz1fmJ2p5ACB4edVQHiqs70DTSgHDe2VaMRHjC
cFT7c8aXwSjf2hJ9Q4zAnOQEry641WyNOKiygIAc8qBGmu7h9q1Arm4OO1+mGFPasDhfvo7DmL0p
VZMLgE+PqfQMwzCk8kDnHhLyS2BuNphDwZSJOftIT9pZ+Lyu5MSgyVssxsLsLLX1Yb0JJoK7cg4L
mXYkygICs2wV7MbeqsYMgJtUW7YF9tOWgUImokAr2NoC1JpSUc73CSrlTsUJM6P2Dq5nKy1ki9qE
gPmlUm/AzNq8wM5K2esPbxRPXN0EG3/KhP5eD5tKbZwvIMnsma2wUxyfc9KYhLQnOz2FfJOZKaXO
gwEN9rV1QL0p81fPwwsq1HiNy73Q8M7ri1LI+ZDd5XFSCnPs7wbPLmjvSustBo5SSfgWdt72ld+b
QlfZBXMu7lwS1KmxJIPLDHf4UGhjofsuuDY0SskCOwbPkxWtckoGce7YOBdIYyt0vDdtFtITyvCF
eU9uKyrhhISxYlGeb1gdxXv0FDl4472VJbcMpp1na3QcTQsKQmucBUuNiP2dJcolqq56vWD1P9ru
Ejsm1qjK2HBqYmhw5Yn/CU/ymh/4QqqDqW9NT7KaMAwOTVayaKAmjpjhJvKquam3MXv2w+qbSy1Z
uZGUtb/CAI7Gm1OYLvL+cDZOTrQAuIaruVrXOPgd6w8upuzAFL93y/KtQV/OqUQF9v6zydfjyO6F
RGjNchFrM7ZohrWTO6BSf2MYrt+4WvXL85kGpwfyX/QcCv1/tC8DvKUQ5fsSsThKg0MRye4b1rAF
3wlBMnSBm605ub5Wd0ZxnsMD8l79d6kU1qhuhnewNvSpbNvJ62LW64htlzBoyqLnj1LLhwuNjS3a
ATPevti0NvU1+eBs7tBwAfDot7zPnW/UgAJAQP6ibu4jUDksPgcUFlz1Z/BlNg7ba6jxwjdDWSdB
TH30RouC18NTKHWdCCvYgt8/Nz+KCz3N+WL43UL9Hi7Uh2q9fY4a1R3G3og1srDfVIpvY/J20IRO
6op3Mwpqc215y/5U7MgSeyj1bT/bPzLNJLeCEAaiF2KBbcb7XyyqskmX9DctJa8NtPGcjN6rKWIt
nHWqUm+Gjtir6I6lMJqz4KhE7VvhSOeKV+u7Qri1w3uhFX8RBrZVL6w3mshJ1jkqzdiKzWpAVL5C
yJznVpfcaX1Cb+McL6rPsyHrWm+RSTuC2U0ZDXrhYy9vdyn0Sk1eHeoRDRs0nDYBUZRYciLLomJk
Yl+NsorjzV76zXpUGHPeWDwZdDJMRXkzsf8nUcv0UAt+G3jDftgGw2OPJt0b2aFmsWod2roe6QIc
vnT59C3YOt2Ggaxvdryyb+r4Tjxzn9UVe6YFCB/4oqbWgeaPA5TNXrrXubyg08pXs2pMbapo0A47
SufUlykdDNoEdIV+lU5UZ1W6MVJu0Ci62oudznwWeqjNOEaFzgYnVEn2ZB2Qo6sIgVW8cbhHgxZm
HSHModyLbLKV5eCCFueZn5Uig1nFbLrOB6MbKhcOMTnHCGGsPG0xL3K4qixQfgZWxmNcZYOpnyqF
yS1h1OtmTZPFurLFXukzsj2V7kxkDB6Gp4dgKjZ6uQVTvNIaPPEVaEmY9Wc5aSvrKMyklX68mbSu
YqgoGLg6onH/2OiWZumZz/wqq4T+BtVnKoxWRr3QQASZFxtvHE0XZ7srolQvVFEVbihbbeYwwwyK
vsqYlXmL81B7rvRw7AqTzPeGQq7jLKEAvctx6a0c3dUz9LRmrVptVs1xlHkbOZzzYNz6Vp2dtmWv
o9ILmzRa5xSZU45QuSpbq8vklSoebVeBwAmW6bIzL7OqwxjK2DpeiLAW1zWhWM+SqKreHSp6cFp6
NUve0QXWn0O4MSfEwowtA9MeYpENZQaP7FmxDLEfMOxlkDOGa90wOzkfSO5Mt0eZc6RE16rItxXz
kt64FratkGoJ2g8/RNnEdpziHxytK8uxbDaHM2slxdTtIr4GMIWyNqs0ejqdWymkUEtQ65GVvXDr
/4ZuzMFTYSZkzw+lTSj1NmqxXMQ+uHO4NqHdmpUr9DekzpLchsIAXIdP0GVCmc2YWCL3PlMplXc3
qIOGK11tvmuLDKBK2ZdyZowIsJeyU/0AJH8OfHljI13RyvVXQoZLhujpEDRlVpMyRjo26kodBZNV
sR2lwUN4ulEBkZWtVUjZBXOeLi3PVBrt5Dly2OFgt9ho8Y0dMVXRPWfbqdCz0y8UMupV3wSnOkPX
3XAqFkV5VDOVPa2GZdl1+BXRGhHf3PEbao3E1qt+gV8xqivkOD7Sl7OMnYqdA4sc/fEqhN5ePQUj
d00zrBdzHiroc/srmW/haANK9/T1MBUdn9DNqY2r6OTKB7hqgKvSiz9wx5UlvdKNzDde7RN6JjYW
k2tRH1NhFuc0QQh6CLPOgA8WzJgqmBnW+DGDzw56ilZF5pmq97fs7Fk1YO1g8JnKPMPkiz37Kgzc
xnptR82LrBdlVOeWrIJCJSeyBK/Ogu2xCmZJnsnCclfFO7uRdPSAvwo8bbD8q+o8tsIL6JC8gNYF
5lXapYWuFzzMixoC0QA8TLvc1Ao6Lrui8qY3PoaebUK52VjVl0QxKreXIXjBXjC+v0GuHIEtXXfw
Q8vbAlAWpukGZTNgDgf2wgt4BUA2HPcI5rjycok+eX2yNBXMsHBnNnAKK49YXsHSTa2/uXNZtWX3
W9QYMb6E0EmtqLfg7/1N8ue3stN+x6Si5jNXmwWd38r+IF9xFQ0UbFT+3Sz8vl3ZrlVaHdlKh4rO
1/ungroDRsHVokZ7i04y9EgcOBg9mM9wpadmJ/Y8UeH7Hwvk7O9eV1Lrz1gqQpSeRmFjgOby3qxO
3At689d0MEjeb+E/Ls4kSXIcBoIf0oEA9/9/rIwRrhStLmNt7c0NIrHn1BK0S1Qb11w3dCld4tMf
ixeFVns1+jeaZkkWCyzZL9maoRLCPXOn4Yc5U3AC7WZt/1eHLffE7jc5VIk6Bneo82eDkKjiEjC1
RM5mu7RQu3YV5WnynWmFQVQVGrK4okW0Sw1D0zeFmTk5Y1sJB7c6Cg5gBCx/eca40iETWo+3MCwF
CdyrQh0jKLM+P1d5Q7tiSVupZGSHDXL+uDXusIHNp1u1F9qNrnHrV5gY6J+bKp1gfSA9sa7tUJGb
uJt4hhMYz9ajUTWqsdkBTKcFVETszLrEeonXHc92/kG5mdw7ClisWjas8g1RLs5+RjGVicPTOkNn
v2F/XlOcdobHoQ06nkHBbMhy30Nl4ny1kw/zQftorimM36IDuH/ft/ryx0WDvNJRkr66ogsaLtzQ
soB/PYGyVftIQN/2HjnsAxduLn2ImYYS71yEA++uY0NVUKBpTBR9J+rcr8byJTwWqjx16sv5PWrd
Ch1PpU+rf/nFLND5K74MK/d579kV3XPcJTqRhahz9tEcQ/PtX1loFee4I7+JO9SpB7QhyfkswHwq
Ft9mK/qhTXRayH0+9TzGV2lOYJIJVQ4LM3CNrQ+eVrVd0NgKlHogOfH1s3QoFWVSqekblQNqcyJF
jY28l9Ur1j8hyaF7CVxUGQpdjq3fy27KsKKUeFnVfZn4j4Tb6+ANjifl6qNktna8gCmHx32DfrqX
pJb7BrudnGaPOBcsuUycOO0PAOvjSJf0AN0SwCavngpnoNQWUJmofPD26BcK4MAuE+pWh+DAeba4
7UBXgtYJU99rwY3A/W6w7eZIuVlUqIKtRlGJ2mr9jQNjAcORcL7Qri4wn06OB0PxvSjHcJNGHEpt
GTBVt3Hd2G9sWLUGwMPjgySwyexdCSLRAlUCzW60fNJyDx1v9VP6ENXRgUqv2hvtemU31EqVuFDN
nfd+t7IigdngqdUw1T50mp7e14EFGCiUt5nZTc7ApGmw/Oq9v57UodrbcAotvkbZClQCbtgAvlmn
3IbkePAWFYVobAe3g92e7B1zWFHpBveHbpXerJKA43FbHxocKTao20EmjbuIIip0PbQnSzkTKzDx
EfEg06XFh+gwpSHUzXP7pycTquethlNC+mtVZWGmfWC88G9i+i9J61XC/Q3LB9WMK6odNWB1+3+1
1cY6FmDTx6CZntc2Yd0+rD8Z6ZoF0xWxRiY62de0mmw5wmui92BnICafltL1t1/XH109o4p17coK
Yh3qv9fMGxj2ygKDkg5tgPmV/NbE+r1D0/ZtkXzutLU3YJLdQD0gQ2BFAxD8WPxQhXHD1gkvyyoE
qsq+GsUPSO5wg45De5F5Uo/Pva4C5Zy85kqT8gTqMSr4+3JLWQ1l3QT4wFWrhihxnHs1dj+0HDqg
8YR7kHC2+rUlvQdJ0sKt16pVtm03VDjKssPevXDQ6PfA+rx3retY5YbtaTTCpsIFQaa1DiaKoBqY
DWZTUX4h8PCaUEcqfk6+MiHxgtdDGtMimveqio0ZlS6+X0N9gds3MW8QGl9KL4YsyDVW8uVd2YLE
N7YhViwLRelagG+EIRXNaQZMye6wGazWBzVgjURj4Dj2di/ZNZLAOv5Nq75Gi6ESaH374aURwM59
s0XUbjuDD7uAqhnbmVFSNi8YUpA4e8SOuWHxM1wjSWR81J4gwju01U8KvYSSD9ZbFPVywdJWuNo7
ytRJYFWCa2ef++cMDSDxG+0b0wvC+s/A4/f1iypHqVvfEI7Gduh8kN7SW9r3yGUlk+Sj9jqwAveT
5M5ul89Q93bo8nKVLhY431i3GNd2XqWQ2JjLORj2y1T5VA7ehgamc6dUetPfa1Kg+0hDhyR8u4Y2
a79K6qeOz1tx3KZkBokULkyAB5cvKPLue+KJv/L6Zju+TSlum7J4ZET7PbHVgqenA08xgqnrm4MG
jqQC2oGh0s4Rk7dd75FO/Sk7YIX0wRO2rSPypXq9BdEqLB85GZVYJiWlAaxueSIe42dzsCZXQ3+t
gRJRAjvREVH6cjAPlBWjwxCWEzb1Uz/3ROEIvpOiaiXOc1IeJ3D7HVXSB21eI6PgqYQ2W9nshgZ9
hvK339r8AKY7lbr0BZqPk6zix4eUGh2TBehWsMaPGoL3uaG2tl+SboguqBqs86xqK+BDA7vL1DRB
rzysw5SOoY4gZ+QeSOmYDp5OgLqgqk51PAzHvS1hmxoWZc97O+yNxF+ynwaMpx+YFN720KzApG5Z
KZO2b9pdgk5edLyP0qEU5l05OMA3cUDfbu+CcFvetJF8wp6XvCfudmQ/jyA/GSlkm6rQdB2IL9MD
PN8+3mrrG+ueeqEa5UVWUhYFuKkABH3G6cVNVdhUNmlLZiFYgPZay9WfqpkTnG+ppdIdu9xBffBU
3IZi2KF3p6mBrtkRplDoGgms1mWicS6ITjuhRzzuahxnCv1H5zXuChcL2cdqYfcKlW3TkTxr09gF
VXUof9WFXXVcQ0m5lzeSb3FPu90eslQA7kAEEVotpVslhAxdGlGiNl9Yudvrhqn+Ql/+4B6nWMjj
XSrKrZ/JXLD0DgNnL61cgPVphNJFy2pkAcpt6L9s5bIzAuy0bFF5eH37DR5ccdKSb2w3wZJAM6bn
Q2oNvFRDpA2WulabwI3n6iIAvWEVGkUzp5Ss/ci4cTjGUQbANnbeOJ/l9E3yy+MPpvui1Osps586
L0y+rjMsX6mnAyuuYNG0BJQ5oYTGeDOl3tMqxNzabei/dvqgrh1rwNIfxz3W0cQnoiE5BXQ9VRcR
s9LvofpuvMkxfr5JMyX1S8sVwW8rwPiVjUJFaksQmAgiKLzGRVV+Y0/8LqPoVULdzdppXAouRUKr
rRzledznNqBNntjS2HaM0tYRoNqKf2eGY3hoQH8Z43ToEe2m0538U7qi27UZDegFk/iPZOksUDWh
Nec9E128zGi4qIReGKbRocds+ef4E6dW5xngt/RPr1qJb0+tBPWFfeTR/eJHBX7llvK1ES1glQjy
SWcKEMSANhr8SVE49Tk7VL7a9F2saNwLD6RLrtLPZxSofhlor+NsOfSnFy513Lw556SaBtVFDgd9
OeUmyDSaRjk/IojfL9C2vewOPqChveILsnsDp8apeu74Mr99nUAuqnKuMl8LP31B8+l+cmrSxvWt
wPro2cl6J35FwFTESJ32619qDdqfrt88Scjypi6ofuOp88sLu6edxANamIxVW8D1fp2KHHbceLtr
g3tFDr5zGHkVNbUbx1UHDmAoiUsGLf0+WgDTIdOXRivfqsO/8IzuMy0ilQlMlxDGedUdZ7vB5Cjm
91HDFRSohEqmcQnbmwRrr3hlUpL28llXIi6DGh2RdqvAiStDon+2a8daCy2jrzb3vex+0iP17/Cp
OZB9ipb019Cr0gLqQMPOMVWdVmD5dHdCSyh5sfkLO5qbymtqHmCScQ0+mXPpwEoOpzis4//tAnSH
iLYpn3zdsFOWw7tcWG5ByXbqljhZm9duCZclpi1aJD6oQy4aL2T5JT/oJmsf6LUknhGlBrfd3Bss
u4HySJUmVwLIlWFYPsxahy3/N3CVILevIAsxAKm/yRfu9uQPHEBSKtWZA2ovMLVZT34SXWnMa0BS
lNZoeLU9gAP5cS1HuUbKxvk4dvcEB1CumrKU9HDdcDvFgQsZRAVpqmRfyD/fMulYzwWOJymljc9i
FCBdoX6GSWLLI3cJK8l6Dpnk/SYs318yIsB5YAXW8yAmHU8YshVAHcOpJS2sLRRgp58Ae10FE0ib
VNDtKxawaXMXKIV7r/7Bd6MZQfHABfflIkj025bTOIpDLiXsNXpViQwc/v9cJHFihp4rMM9OlruT
35TX7gf+cXVmuXKDQBTdkJ/kKub9byzqew8G5SeKcgK2Mabm6ulOKO0l93i7V8zwpOkAsILuOmKo
Sg31vjv+gyXPbcDrQyBAe5W0S5gkW3dEFg1tMs6x4/nEHYPvhrLjqM3QdXPpgwFqyZUn70PU2htw
7b2zOhakNBZBRTjl/Ss2qu8lUvpO/7pWkfy8uikS34cCcelAL5/hWk4/0k+AjaoLAVNP9hXhrPGD
A6jkqLHzW3toaWGVLMtq/0naaQ1sn1/PX2rRwWKI6V3ZbOSDlE3HriDgWOayFSwNFWnxkyJDdELX
dnSkddG4xyr/bKX8qwQ5z/PICUfam5J1QgsBTMqFZYIgSqehw29s0VRcV/SF4vJxeKjT2SqB3teJ
44vQ05rQ+hTCIfXzBEYBNrxQr8+OofXvQGKc39o3zbvp2L1sKMXQO2/A+YQ/uMo71fnRoYuuUxIK
iKcw+21g2reUqfWy1SCKFSdfUjllHgWo//k1uSADog/R8oblVrJSw7WewHROeNGbwapJmCRb+ItM
Ji2w+nQsUf0hOypgpE920lTre1+w74SRVbAcr1sd2ljdkp9E9lEN0chojfajLK0oG2Pn0a2bxbsN
iuImb6vfNCxAcqea2yYE2m+xjY2db1dEq92VKwkK0E+DiasNZJwaapHkXBRoIfKIN7Hc81b8JMQC
+SYY6TDGml98zIYYsBOwJprFDTfosM/i5Npp4oROFYCPq0UVEkwYAfdaYWyohN0QA9mKeGjnKiYL
jbOMI90C4jyug5v2a5Fzd479prrNNeh30kiFaMDE37FDMzbqgMWHJAkF2/BOKL5pKpOtYg2gxBu5
IhaM/jygxDd3ZpnLtGH7IZMr97wvKskvm59vvZ6xWG4tGEbPmP6axivllo4yZIn0BgwaGHF+L40M
oJbc34/zovsZ2r0qDeXVrHVY/opmr5h1x5YUlTWyQrQ6bH7uF9NNEnXYn6B9kdCGbxV9nBZ8C6rQ
vbUoqR3vNVTGMd9zLaSUHTqfTrRMHZDoOQdcSqygRrZgYjVDLbBzQlmmvIY6XjtO7Y6N2gXNHfsj
7NrP0wzbxrXtnHFO25rg/NlzbqJ2xF0BFgL66HhiA6Z7tVI/fAoJV7ATuXntO0A9gQoshCaQZNZK
bDioI6Ax5fIaAudDC7MTSr7uyf1GqcTaxlszlGksEe+2FhWx84JDDjRKzgth6AZMl4W/uyfmtfzT
FkaQFFot2ROo9R2DWo5tc3dgIQLadTyw+BVYH68A/RjjHtj2OZlKECBcEVBZxxOpsr07CzhOmCuL
EsfvoRL/Bd0icest4HrE8HqQEdrTVFpwdJqVJQdpAMOxDmr7Am/igFJx9dU2lCPu1xt8U0mEMXRq
AS1OrC2crkLAQh9L+/Wr/warrtn9E6R+79DmI5+wBUHgsaDdIQ4B1YBYuELHM6h49VNPjX2hk0Ay
N5ZHy8CKG/1LZE9OWVFrEl3KI+WKtpDB0novZ1RoERc0pQCTfyghutdpva+2/jw9PabvD5rbvl2+
nYtphaerVk9ooQKl+uqANeaRF7g9kxq61o86NqH90+eLLQq0TlFv4kWUHbXzDJakszu1+Ohqwh2s
ECF9X7TBER2i8W7FancTnNfUknX+oJJIwVkOEla1VRwoOPOGs6xlKsxTMJpAybolPeE4tKYhPmDk
aBANr8CK7cghXW3TApuThU9WmeYdprS/rU5ECAKfCVSn3uo9fq66oNOO0y9r1NFw4O4zRiE1kg7q
fGC7NrByzlXtZs/36KDDOSBg9/xsHJv04ECurHyD2tKpRlOE8yow9Yje3t3naU5YcXkNT1t2jAu6
a8+6ipZJ7Elg+/ITJKTvabGXi6SuRH69R46vSKRewcmA+mMVjtNWNTsYJ+Tu0RM31BLLvKLI2w5J
YNAomZfT8oZ6pRL7jVjrfpoiEbWKT2m6GWaBJeELtbbt3tyZQHnZ8Rni2MsGs31MmeJJl4SqgSsO
b5qknWk5KFFnudkGHF/m/OoYvWda+Sm/iviY98BFGQvFC6SMdtOgr0nQLW/4OYGxFTQyEMENnF7W
4GW1s371xTCnNXO8Z9xuUNnQZfcbC2hxRyuKVYhh54BWnV2fLtv7DZsUJfS4HRNOQxwRnxOIDzGg
A5ue7MvubQ9UztguUeKVlgHcXQY9OeM3jdcHPmXA3HCZUOIQuhb28jU2twoVw5L7HJMKwdG4oalJ
vGAC86GlHBa84AuUkuSt8J7q7Q6tPxp8vqx+bcCmbjUo9ckpXYHdjeQdR0tshQHU2UBzH7y9Z1pa
c+HP6WIMtI7WvYAnolVMwxYc+Tt44s9gedJWJ3V7S4sEkmnylVkdcdC1fellUI5jBphO9g52qU/M
BSyuW48tB25W/eE3LXyhLLkDmzMLi+BO00ugngFfoUxdh7Ogw8X2tBOkw2VtUBmNg5avO90tgOtp
EtFBudqoZ5O5Dm4OgkS7C2MC3cv6B50Q9l7z4ut5v9qSeR51SHPwa1mUFp/7HcTpdz/W5i1cFtTN
VJMq80LpVIHWp9Kxd7iZ2wXVzm/IzZvWZs8bdwTOojx24m8ZMHVNjK8NT5R71t1VffyU0AAGcD3K
c+NryGtxyamclLp87zSh8bhPx/YceyMBvQNp1Fh4aVNwvkEUMvxi+Z6KqRSzl6fQKXrmnY6IdnRu
+qAAZb+xqp3Y6JlVWry8ufYFVMedod2uaELsydiA6p+qM2AK5tI1eLoTvKz5OlSjc195PZ3TjOgX
dqWo2p+p2JU/ix43oVhiFGuhzYShTGQSxKyZtXNyLzmCMbEc8fjBBiQjQGFQydZ206KhevlOQrpO
NOw4UoMp5igD1ojjFGcGo9QBO4b5nf5YX+DJO6rWwOb9MNbCnf2gjXPvCjIrNXUkVtGhWmIrk6uS
HHnR8GboOkYaRW0d+NuLXUXUSgvt212Q7ysjrlJFdE7wGlAsk7Tpv4h6F2h5mnODx+/ikxTyAVUx
UdkVaN0R/tqhOtGgSoVcMkWg3bGDcM7aJFURylbeKSXk9dQJnfr0ToF/GffM6/zexOm02MI0SP7i
x08aTqEODTfU/rK087/RWMs6/If1F64b2snZfsvg6JxgBaYm1k3T4BkDxfwHBppf3/0FawKVOVZI
ZazWZ8sC0oLNJxgnTcI6GRhdqtPWsLkmrpOdA1R3Xqcp/bte6zgUYpQQlCG3ZATS7K3lNXG8vpuh
FJmxC1GA8Vu4ghcvqZodQGnC4RwO3A3RxNJn8A7Hl53LAkx7xVF2cWFHgRabrMQH33kxUtLil7CZ
uOPPNdt2BRdyP+6LOgc7y044LuOe1zHdpdkbB+o1s+1y3ECFyzIx3dEk72r6WD3UHYIrIoLitkNp
bFIoOgzBBKYkeqfQKzFzQrS8gS8GN97uwQO1Um+ztJ4fYVrgYls4EE0R99j6TMKsHRl8WHsklXbs
/L0n7VhVpyHbGTck6uVXPW2jB3AapjdMrzdbZDjfJZDvNPQpTFOqpD/+gAV1CRbcI2+WqpInSo1X
lkltxEV7tk/WA2H0PcIrRLOvDixkiVF5Rwcs4LaS/kT/CniCG549DUNUnZm7w1vpCGLet0vEs7K3
yn5fUMf4sDf31xRA+Xa8oanbPBAb+VOZst8wlHRkJYy1iAZLfe+a8OqZ1EWbt+1oWIV6OxfNh13r
nV3jhl9RsEASuwwoWgVReT/Noc2Fz26buwMLFdhtQcuFiac3C2y4eiIJ2YxrILE4Win5S9ShDF0O
7+EIZOgytGh76/HJuVgTGr/bjd3KAMUlgLljs7uCvx0x0iXZpArUvo3g0mCJm+U0cHIeJRgXMOZF
FxswlxnSQOAr+AL6yLSvQUq9lW9oJ7pN4duud4J6WZ22Lm9V3nQSg6AuYWloApV/9i0hekcY2lOX
cnlgNl2QxnFEI6m8gKVukeMY25D7GS/VdEQUsDIqMKmuDT5i+g9BneHwDZ3XGrksjt8ZQnTeEzcC
jfcvO9UAdq6638/r5YWO3Vk8qRXv88bza/WD38u6DHSX/qekIvK/QOMlu42A8TQEhntsCtjStt4O
5veLTngxzvYnl7IUDPRKFXRAU/tsnWj/8DEFtoVXWW06Jw5gteiieQ7R+5jQ5vbXuTvvvxd0RT2Z
VJK998Rs4+a6G3IMogElZGQBKYxx3661s0LPApfunZFy9QxcVgra0SVymhPv1OmvVUM4FDD7vxAD
cUW52HqtyS9ql0NLfTBKcFSSkDs9YQNcnoXY0JdGp0VgVffx4da6jQ8mgA0zJbGX475qt/so8WiO
fo8cj541HKGd62bzC87awHpvuKz15X59Fzvd/SrBiBjXDUlD645v0fX1pml50fdSxDdzyJZTR2oO
d0YCU8espD65U4eVp9N7myLsa9Jqr3V1xdLqN2uqIdbOw01yUQq/uzSQJTOz3JjquN2hq703nPQT
Z5vFf2PXs372lStjChsJ6OpkpbhR0XRDEn0KdZfrvSHxejJ+2jUw3vi894mwumB6R4aDaTFvRnZ7
Uqs26w0rrt4dGKyiL7RdMYGiwe3GXtrqoD6e5oPJBdIcV80FWCbcP6bO7MhhGIahLZnUQav/xjIB
QAs/md15sezIOiieyh2qz2ldLM/KRf1gWuUVYvn+ja8+wnYY95GCwlU6Rg/TdgXHuXlhPqqolRrh
ZzhUbbTuxpkOh1QnCsSN43Be548hCcDxgpmuZI6yOkmgm4oiLfxr+ZUlu232yWk7faWU5MDI4a0e
5VBRIcI0Ro17yZqN5dRpSLrr0JbtUFqIUGrE97JBx+tqk9rD8SAmBXLnuMlwOKjx/bzF0+Gkqby9
BOZ0uNo9uWBtK2db4my0F+hyWgwt6XI1rzHMbG23iiJ0CnM5SwlTmjUYT5fB4ejLdBhKc6Fd/Dhr
V/fvF144tfayyikzZDvNdvkakk0dYgi0wzsKyxqcPYkmX2qm09X5xTS043G6KRy0obTCoVTEbJjC
o+OXSpGW21c5PDcf7sLf03E8rLI19Wh7OIw/xGChFB9OIzUMZPqKvFCV4lSXh9WvlmOokuk+xD3H
IXfq+DpjvE4xQ5W0GxY/h6rp0lrbCIdtWk1JtW86hVaIjn383E6luqZyCIdUg/iBcAWQL51TaHl4
qNXuUU4xXyCFySPcYXbOquxg/LpLxGbwQEmBhLxkTvl+WvbvbTBJIUcceu9iqDqDTb2zeIgFGeOK
pDGnJ6nTfR1s6Z19vN3q3NmTu+V0yJioVgpGOVNAZxdUjWNQp+X4bHIRTgMZpVlU7+0af2KpBYvO
Eip0AlaUIeSEdaXNKapoI70drN9GP8f/GFhqhjc8v4Jzmll/uAjRu5juTL6lIbpJ+S6lzcI9jZW0
I0opqP2rSHE/vS8q9UCP6IHEgxxnyAFgLB7ZfTtJ7HAYdxtndi+eP0jRvRYe+Vi7L7c3WsDuVyJE
sz23uvj543RQWtKqpf0vUlRXhcKkWU9BDGcw2JsZ3zod7m8EBi1jDjtMLNsu/odb8CtRHEwfEgbZ
J53QptfYWKTxwM7TAdNDuucjKl2EZCAl9nsJ/x1/uva+9F/32oMUPb1Ytfd0hqAUcakn24WGBemg
QrcdLoXWLuO2usY0JoC1yxwcqhZJSdXx/g4Scsz02ypTSWss1mN3pRShH8sTCq7dwudLGHbqb9Te
1nBgbaweTHyAWILxBQwocv+OJZ7hChCK4jsi8ulTXW90W9rVIZwwG2BC00gFmqKsJjyK7x3/vMO/
wCoA2J5pGqMcIwqDF5OBW31Xwc0VNnuul8NCYbM7ZmYYhUFOqSv4/sPp6Vmpza6cah1GpZyA3OaQ
Purzf3PuVA6TkUerzfPnwmA30+U27TRMmB1EdP5PHmGMZ4z2zyvvwGShgUOhRkETTmk/UzcUN9GG
6F5qGljafBiUpveW26kyiiPGWZY8dDg9X+WizQARgzBnUNeQMpg6DboQXcftOQyj4zizNv6ct4OT
qqFgeYr8v6aT91oa5BhnweV+G1QKxZtk+S2nqOs/g5ZdRGw5VeZbRdm9Xc6IVBq1R3v+1sazReuv
VCglpZ9t+SHkdKv6bBQHH4Lnlmwpbr1GaZFTbsVSTtkhxhzKSlct0SaPYLJstdy75ZE/eOmvqXPJ
cTIGgvAeiTvkAAal2682O55CPDZwggEiQEBAAyy4PXRVmWQzo5lPf2L7t93tdrldIXAfVroUGcsT
dkGnYydjuXMWNtEqhULHz8W+WIdoU4aVqu+2609mN2ZCb4QC4/rRwSM9tvUPDGUT0topI3JT8LyT
SbY6NO9pzXbB3ChVjCk/HrSS0qoYB4ihdZNO0TRZEarIPn3fBJ3p0AZzWMe6fGxjvj8JoS42eoo6
R5PStFYVyUnRwrEzvDUdU6qC6F6DZkc33l5/ctddzJezRigwoTaMFBeryuNgpLR2imJ1VbWLhd4Z
4/47WVuILs09DID4vjqfVEpiKubW1kvWJWplyNVLZv36Sb/okSZjddTaAHcKWCmdZxDkGioihgNG
ELNZ0ipaqS+RH731Mk0Ug1HngQKVNvXiu3cOD8/n3zdfD89u3v/6fvvzweHFzb+/b//ce3J6f/r2
7nR7uHunvP7y696bZ+Xt60fl+etX5c2zVNP4LL5QJIcx20kAHTBKRcCzqtv4QBQRLJt8sSjYxTGt
9mdPjOuJjTdq8ySXsVLNgHMhgggs5V9W991boPDZBnKv1ZJPUr8MVkvny2H+MmkE6xFQuda9M8PQ
VEJxPtnzmZjFePXiVj1PwFEq7KMF8go2bckGYI4H2d2mcamEocBRPLKXLnoLe+l3dFBl6+1Y2NqC
bRBrmHEw4c92FdEAozinb8WX7RXdAHVc4G90B2zRHfAJVotH/rtWHJcIbtTVDtiKG+RlgeljXCc/
y5VNwOPt6L7GsgkiPyYX0oMm7fKpU/ECyOaWwoqoB6Y3Gjn3nTw7DGhlybEuiOwQXLHtW5myEtHo
VfqkCMHJUGPXPbA8xAJbA+joiobC9lIl4x9gVfOEIsiTh+/DAVtpK11CDgUfkj7xyV6cpxRGzl+d
PmufYPm3LiALrRtdWkIYfHlYmCGHTtg30CitYkuloZhaH3XCVaBP69zJmHSLkihqMCnbUjDGVcdh
xWECjOkzG9MFADmTTbgUaEEB6SSsxSC0kfqw368oqAO2YlhYsOFWDq+qcmZEk4ncEcSe91uyVcEG
qk/RPkO71IoCzixpP2azz6V8VtkJAIN3uzsUlBa8mFNsYU+vZ3Uk1MYW3PifqXAfTjWDJ53L/aQO
aoVDtDMlJwKcGRUB9GKakpznvhsTlQCmvaZ42aEuz0JAKlqBG6aJ2JmoVr7w0HDOrogWtJYWZ7EX
BRkEieuYbAxUOeExAKGs8l4cDjWq3fValDIhn6tKQDc1Z82livhO0lsx81VQNsoapVPFwzsXL4km
0L5cIzq6U+R3TAP1wjYLRLoZYdx6/qjFqGlhEAtbGqF6BqzBYqLeiTGcLREdsBeHRVQ8jn1pOtjg
qXgs5Qe3ytIbB5tbEmtHtL2x9doEhWEavDUYdeiQB4ItlK8zcdy+ETW2blbrzYFfi7Mx5Hxh4FYc
U5oN9hcoFfs+5+bMDz/vo5frAi0DQstBvoohp9MRQA1SJKmH5SbXDXupkq1SyzmPl68bKMZRfliQ
uYHN4mQT+xbB9vEBGPkaKvvAYiNO3I9DnE2EXqtEp0G94pA2NfsJXGMuZzvT28YCzSajBecXr/vM
XVxB0z5LHrwiSwwXvIPVkj4Fyl2d2nJTsJI741Mu5eQpWj8CYb6iT905wlK/DYQORJPuGP6IeErD
oXDiZBABsZcciIBpfjn8uA5wrhTAVvHIxglskcSVcAy5cCakbFUS5tovIROTrqlLDVz9EhhFh4K0
ky7FsksAs6Y/ZoPVdu0zOlCTn6Q4mUL+grwmgOttjKF6YaMY5rIBtd5OTs9KzOKIoCqLoSnwFqQw
huE7d8xS8iMDRNtUGjyMHb6OHRcqjp0W43n7NnXkgtSSxtbdeNe2CGH2mwyEIosOaMD4O2gtzhyn
SoXzX+sL2ootbkLy3EoozDnSiX78/cef288fP/06pL99ePr74+l8Ojy7/8+r/nZzuDl/OLw8nc+n
X58Ob/797/Z0fv/p7p2/UEsBAj8AFAAAAAgAB1DDWh9xfet6LwAA15QAAB0AJAAAAAAAAAAgAAAA
AAAAAEYtRl9SZXNlYXJjaF9EYXRhX0ZhY3RvcnMuY3N2CgAgAAAAAAABABgA4F+kNZjU2wEAAAAA
AAAAAAAAAAAAAAAAUEsFBgAAAAABAAEAbwAAALUvAAAAAA==
headers:
accept-ranges:
- bytes
content-length:
- '12346'
content-type:
- application/x-zip-compressed
date:
- Sun, 22 Jun 2025 21:11:29 GMT
etag:
- '"e6caea1580d5db1:0"'
last-modified:
- Wed, 04 Jun 2025 18:40:03 GMT
server:
- Microsoft-IIS/10.0
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,251 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate, br, zstd
Connection:
- keep-alive
method: GET
uri: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip
response:
body:
string: !!binary |
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
uYHN4mQT+xbB9vEBGPkaKvvAYiNO3I9DnE2EXqtEp0G94pA2NfsJXGMuZzvT28YCzSajBecXr/vM
XVxB0z5LHrwiSwwXvIPVkj4Fyl2d2nJTsJI741Mu5eQpWj8CYb6iT905wlK/DYQORJPuGP6IeErD
oXDiZBABsZcciIBpfjn8uA5wrhTAVvHIxglskcSVcAy5cCakbFUS5tovIROTrqlLDVz9EhhFh4K0
ky7FsksAs6Y/ZoPVdu0zOlCTn6Q4mUL+grwmgOttjKF6YaMY5rIBtd5OTs9KzOKIoCqLoSnwFqQw
huE7d8xS8iMDRNtUGjyMHb6OHRcqjp0W43n7NnXkgtSSxtbdeNe2CGH2mwyEIosOaMD4O2gtzhyn
SoXzX+sL2ootbkLy3EoozDnSiX78/cef288fP/06pL99ePr74+l8Ojy7/8+r/nZzuDl/OLw8nc+n
X58Ob/797/Z0fv/p7p2/UEsBAj8AFAAAAAgAB1DDWh9xfet6LwAA15QAAB0AJAAAAAAAAAAgAAAA
AAAAAEYtRl9SZXNlYXJjaF9EYXRhX0ZhY3RvcnMuY3N2CgAgAAAAAAABABgA4F+kNZjU2wEAAAAA
AAAAAAAAAAAAAAAAUEsFBgAAAAABAAEAbwAAALUvAAAAAA==
headers:
accept-ranges:
- bytes
content-length:
- '12346'
content-type:
- application/x-zip-compressed
date:
- Mon, 07 Jul 2025 04:50:25 GMT
etag:
- '"e6caea1580d5db1:0"'
last-modified:
- Wed, 04 Jun 2025 18:40:03 GMT
server:
- Microsoft-IIS/10.0
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,530 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/Developed_ex_US_6_Portfolios_ME_OP_CSV.zip
response:
body:
string: !!binary |
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
ruScfoZb/J7WuHxccxwW+1LiAleP27glt7fcbLneuMD54/bHdWE9+b85dubabsaNXLncOnI2n+vP
fzLuyPnGJY73k+c68sgxn54tl8Z1Keo2bpMXzuV85GLJ9evyccHxmHJ+vvQL8v3HUi63Ss5uv0Xd
wtnj6JfD+TdydahLOfvJuJTzjXNc/+KK437JTeCMOsftm3Ett0pu47KoM1zfjFtybXKr5IK62F/c
Zn9p1oPfdfQ+n74ZN3I1cnNdPfdh3Yw7crHk7vvr26+eeWk5NzlzuXWdU5e4eJyz/rZct1xt6gLn
5MKFXKdcfbl/z5yF0S9aLk0uF3X23ndcsC/9nNM4e9x6XMqtkrOSi1JdHFw/ruSCfWLo5+udF/LB
tZwbrnC3rr64xcfawbmcHeoSl4/j+9EKuXWdP+dY34wbuSy5lutTj0ty4Y6cLbkK3O2X/3d3Pifk
suVmU+c4JyfO5VbjXG6vLy5wIZdHzjcuqLP/ujT6WcjZyP083z84wxmO9TCFC5zh5otLuW3sny3X
hdvX3YwruXa5WXL1Pu845Ma1XG6cyeXcOtw8bsl5yu3G3X6J249j/wz61dz97HmOt8iJ43w/C5e4
ps5xSQ7ckdstl4Mz6uz/zu3zesaRK3/czY5zuRicyWU9zxvtZlzIucm545y6+a8r5tNny1nLxaJu
ydV5XMixT+Q5cn4eN49LuTJc4dYXN7iS85SblrNb17j1uOZzj8vtg+tf3JLbhmtcUFdf3GZegjkr
uf25z69zyNeNXOOycU7dws3jjlwazuRWfPk+tuTc5MzlIuS6qWvcetydF/r5xr39+mbcnZeFc7m6
Lv/r2uhXKWcll/W4mwNHv7hzNnIxv7hkXg7OcPaLK7ldchNy/lP3xbVc4/b+PKf94BLnuCWXLbca
V7jBxeOYT99yfwi7k9y4YhiKoltiq2b/GwuQdzX4DBwPCfCYhov+kqhyud68/Px3/9n0p1/HOS7I
6x/cldu4LDl79WrsW7ecm1wtuWhcjf1nDedyvuX8OSMvfnAhZyFni7nN+bh5rlr0pzFPjrtxOfat
Tty4kOvEFe7l2XCJS7koubPkTr/nwnCOKzkLuf2c4zbOcIZrzlWGM7l9v87ucEtuudwq3B5uDbfH
enQ/90DdOe5hG3fk1nMh54u8wL24cFeu3jq25OwOV1/nJudH7oz+rIvL4eiXc+X2fX32wzwycYEz
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
headers:
accept-ranges:
- bytes
content-length:
- '28250'
content-type:
- application/x-zip-compressed
date:
- Sun, 06 Jul 2025 05:29:48 GMT
etag:
- '"6083a4bb53ebdb1:0"'
last-modified:
- Wed, 02 Jul 2025 13:18:00 GMT
server:
- Microsoft-IIS/10.0
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,530 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate, br, zstd
Connection:
- keep-alive
method: GET
uri: https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/Developed_ex_US_6_Portfolios_ME_OP_CSV.zip
response:
body:
string: !!binary |
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
ruScfoZb/J7WuHxccxwW+1LiAleP27glt7fcbLneuMD54/bHdWE9+b85dubabsaNXLncOnI2n+vP
fzLuyPnGJY73k+c68sgxn54tl8Z1Keo2bpMXzuV85GLJ9evyccHxmHJ+vvQL8v3HUi63Ss5uv0Xd
wtnj6JfD+TdydahLOfvJuJTzjXNc/+KK437JTeCMOsftm3Ett0pu47KoM1zfjFtybXKr5IK62F/c
Zn9p1oPfdfQ+n74ZN3I1cnNdPfdh3Yw7crHk7vvr26+eeWk5NzlzuXWdU5e4eJyz/rZct1xt6gLn
5MKFXKdcfbl/z5yF0S9aLk0uF3X23ndcsC/9nNM4e9x6XMqtkrOSi1JdHFw/ruSCfWLo5+udF/LB
tZwbrnC3rr64xcfawbmcHeoSl4/j+9EKuXWdP+dY34wbuSy5lutTj0ty4Y6cLbkK3O2X/3d3Pifk
suVmU+c4JyfO5VbjXG6vLy5wIZdHzjcuqLP/ujT6WcjZyP083z84wxmO9TCFC5zh5otLuW3sny3X
hdvX3YwruXa5WXL1Pu845Ma1XG6cyeXcOtw8bsl5yu3G3X6J249j/wz61dz97HmOt8iJ43w/C5e4
ps5xSQ7ckdstl4Mz6uz/zu3zesaRK3/czY5zuRicyWU9zxvtZlzIucm545y6+a8r5tNny1nLxaJu
ydV5XMixT+Q5cn4eN49LuTJc4dYXN7iS85SblrNb17j1uOZzj8vtg+tf3JLbhmtcUFdf3GZegjkr
uf25z69zyNeNXOOycU7dws3jjlwazuRWfPk+tuTc5MzlIuS6qWvcetydF/r5xr39+mbcnZeFc7m6
Lv/r2uhXKWcll/W4mwNHv7hzNnIxv7hkXg7OcPaLK7ldchNy/lP3xbVc4/b+PKf94BLnuCWXLbca
V7jBxeOYT99yfwi7k9y4YhiKoltiq2b/GwuQdzX4DBwPCfCYhov+kqhyud68/Px3/9n0p1/HOS7I
6x/cldu4LDl79WrsW7ecm1wtuWhcjf1nDedyvuX8OSMvfnAhZyFni7nN+bh5rlr0pzFPjrtxOfat
Tty4kOvEFe7l2XCJS7koubPkTr/nwnCOKzkLuf2c4zbOcIZrzlWGM7l9v87ucEtuudwq3B5uDbfH
enQ/90DdOe5hG3fk1nMh54u8wL24cFeu3jq25OwOV1/nJudH7oz+rIvL4eiXc+X2fX32wzwycYEz
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
headers:
accept-ranges:
- bytes
content-length:
- '28250'
content-type:
- application/x-zip-compressed
date:
- Mon, 07 Jul 2025 04:50:27 GMT
etag:
- '"6083a4bb53ebdb1:0"'
last-modified:
- Wed, 02 Jul 2025 13:18:00 GMT
server:
- Microsoft-IIS/10.0
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,105 @@
"""Fama-French fetchers tests."""
import pytest
from openbb_core.app.service.user_service import UserService
from openbb_famafrench.models.breakpoints import FamaFrenchBreakpointFetcher
from openbb_famafrench.models.country_portfolio_returns import (
FamaFrenchCountryPortfolioReturnsFetcher,
)
from openbb_famafrench.models.factors import FamaFrenchFactorsFetcher
from openbb_famafrench.models.international_index_returns import (
FamaFrenchInternationalIndexReturnsFetcher,
)
from openbb_famafrench.models.regional_portfolio_returns import (
FamaFrenchRegionalPortfolioReturnsFetcher,
)
from openbb_famafrench.models.us_portfolio_returns import (
FamaFrenchUSPortfolioReturnsFetcher,
)
test_credentials = UserService().default_user_settings.credentials.model_dump(
mode="json"
)
@pytest.fixture(scope="module")
def vcr_config():
"""VCR configuration."""
return {
"filter_headers": [
("User-Agent", None),
],
}
@pytest.mark.record_http
def test_famafrench_factors(credentials=test_credentials):
"""Test Fama-French factors fetcher."""
params = {"region": "america", "factor": "3_factors", "interval": "annual"}
fetcher = FamaFrenchFactorsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_us_portfolio_returns(credentials=test_credentials):
"""Test US portfolio returns fetcher."""
params = {
"portfolio": "5_industry_portfolios",
"measure": "value",
"frequency": "monthly",
}
fetcher = FamaFrenchUSPortfolioReturnsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_regional_portfolio_returns(credentials=test_credentials):
"""Test regional portfolio returns fetcher."""
params = {
"region": "europe",
}
fetcher = FamaFrenchRegionalPortfolioReturnsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_country_portfolio_returns(credentials=test_credentials):
"""Test country portfolio returns fetcher."""
params = {
"country": "japan",
}
fetcher = FamaFrenchCountryPortfolioReturnsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_international_index_returns(credentials=test_credentials):
"""Test international index returns fetcher."""
params = {
"index": "europe_ex_uk",
"frequency": "annual",
}
fetcher = FamaFrenchInternationalIndexReturnsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_fama_french_breakpoints(credentials=test_credentials):
"""Test Fama-French breakpoints fetcher."""
params = {
"breakpoint_type": "op",
}
fetcher = FamaFrenchBreakpointFetcher()
result = fetcher.test(params, credentials)
assert result is None

View File

@@ -47,6 +47,7 @@ openbb-biztoc = { version = "^1.4.2", optional = true }
openbb-cboe = { version = "^1.4.1", optional = true }
openbb-deribit = { version = "^1.0.1", optional = true }
openbb-ecb = { version = "^1.4.2", optional = true }
openbb-famafrench = { version = "^1.0.0", optional = true }
openbb-finra = { version = "^1.4.1", optional = true }
openbb-finviz = { version = "^1.3.1", optional = true }
openbb-government-us = { version = "^1.4.1", optional = true }
@@ -69,8 +70,10 @@ alpha_vantage = ["openbb-alpha-vantage"]
biztoc = ["openbb-biztoc"]
cboe = ["openbb-cboe"]
charting = ["openbb-charting"]
deribit = ["openbb-deribit"]
ecb = ["openbb-ecb"]
econometrics = ["openbb-econometrics"]
famafrench = ["openbb-famafrench"]
finra = ["openbb-finra"]
finviz = ["openbb-finviz"]
government_us = ["openbb-government-us"]
@@ -94,6 +97,7 @@ all = [
"openbb-deribit",
"openbb-ecb",
"openbb-econometrics",
"openbb-famafrench",
"openbb-finra",
"openbb-finviz",
"openbb-government-us",