Files
Danglewood 869cfed125 [Release] openbb-v5.0.0 (#7741)
* merge branch develop with main

* bump desktop version

* fix the merge

* sync Cargo.lock to desktop 2.0.0

* fix cli conftest.py

---------

Co-authored-by: deeleeramone <>
2026-09-29 10:58:44 -07:00

592 lines
17 KiB
Python

import asyncio
import pytest
from openbb_core.app.model.abstract.error import OpenBBError
from pandas import DataFrame, MultiIndex
from openbb_famafrench.models.breakpoints import (
FamaFrenchBreakpointFetcher,
FamaFrenchBreakpointQueryParams,
)
from openbb_famafrench.models.country_portfolio_returns import (
FamaFrenchCountryPortfolioReturnsFetcher,
FamaFrenchCountryPortfolioReturnsQueryParams,
)
from openbb_famafrench.models.factors import (
FamaFrenchFactorsFetcher,
FamaFrenchFactorsQueryParams,
)
from openbb_famafrench.models.international_index_returns import (
FamaFrenchInternationalIndexReturnsFetcher,
FamaFrenchInternationalIndexReturnsQueryParams,
)
from openbb_famafrench.models.regional_portfolio_returns import (
FamaFrenchRegionalPortfolioReturnsFetcher,
FamaFrenchRegionalPortfolioReturnsQueryParams,
)
from openbb_famafrench.models.us_portfolio_returns import (
FamaFrenchUSPortfolioReturnsFetcher,
FamaFrenchUSPortfolioReturnsQueryParams,
)
def test_factors_query_defaults():
query = FamaFrenchFactorsQueryParams()
assert query.region == "america"
assert query.factor == "3_factors"
assert query.frequency == "monthly"
def test_factors_query_valid_variant():
query = FamaFrenchFactorsQueryParams(
region="europe", factor="5_factors", frequency="daily"
)
assert query.region == "europe"
assert query.factor == "5_factors"
def test_factors_query_invalid_region():
with pytest.raises(ValueError, match="Invalid region"):
FamaFrenchFactorsQueryParams(region="atlantis")
def test_factors_query_invalid_factor_for_region():
with pytest.raises(ValueError, match="Invalid factor"):
FamaFrenchFactorsQueryParams(region="europe", factor="st_reversal")
def test_factors_query_invalid_frequency():
with pytest.raises(ValueError, match="Invalid frequency"):
FamaFrenchFactorsQueryParams(
region="europe", factor="momentum", frequency="weekly"
)
def test_factors_transform_query():
query = FamaFrenchFactorsFetcher.transform_query({"region": "japan"})
assert isinstance(query, FamaFrenchFactorsQueryParams)
assert query.region == "japan"
def test_factors_transform_data_with_date_filters():
frame = DataFrame(
{
"Mkt-RF": [1.0, 2.0, 3.0],
"SMB": [0.1, 0.2, 0.3],
"RF": [0.01, 0.02, 0.03],
},
index=["2020-01-31", "2020-02-29", "2020-03-31"],
)
frame.index.name = "Date"
data = ([frame], [{"description": "Factors", "frequency": "monthly"}])
query = FamaFrenchFactorsQueryParams(start_date="2020-02-01", end_date="2020-02-29")
result = FamaFrenchFactorsFetcher.transform_data(query, data)
assert len(result.result) == 1
assert result.metadata["frequency"] == "monthly"
def test_factors_aextract_data_invalid_dataset():
class _Query:
region = "emerging"
factor = "3_factors"
frequency = "monthly"
with pytest.raises(OpenBBError):
asyncio.run(FamaFrenchFactorsFetcher.aextract_data(_Query(), None))
def test_factors_aextract_data_helper_error(monkeypatch):
def _boom(*args, **kwargs):
raise RuntimeError("network down")
monkeypatch.setattr("openbb_famafrench.utils.helpers.get_portfolio_data", _boom)
query = FamaFrenchFactorsQueryParams(region="america", factor="3_factors")
with pytest.raises(OpenBBError):
asyncio.run(FamaFrenchFactorsFetcher.aextract_data(query, None))
def test_breakpoints_query_defaults():
query = FamaFrenchBreakpointQueryParams()
assert query.breakpoint_type == "me"
def test_breakpoints_transform_query():
query = FamaFrenchBreakpointFetcher.transform_query({"breakpoint_type": "op"})
assert isinstance(query, FamaFrenchBreakpointQueryParams)
assert query.breakpoint_type == "op"
def test_breakpoints_transform_data_with_date_filters():
frame = DataFrame(
{
"date": ["2020-01-31", "2020-02-29", "2020-03-31"],
"num_firms": [10, 20, 30],
**{f"percentile_{p}": [1.0, 2.0, 3.0] for p in range(5, 101, 5)},
}
)
data = ([frame], ["Breakpoints metadata"])
query = FamaFrenchBreakpointQueryParams(
start_date="2020-02-01", end_date="2020-02-29"
)
result = FamaFrenchBreakpointFetcher.transform_data(query, data)
assert len(result.result) == 1
assert result.metadata["description"] == "Breakpoints metadata"
def test_breakpoints_transform_data_empty():
query = FamaFrenchBreakpointQueryParams()
with pytest.raises(OpenBBError, match="unexpectedly empty"):
FamaFrenchBreakpointFetcher.transform_data(query, ([], []))
def test_breakpoints_aextract_data_helper_error(monkeypatch):
def _boom(*args, **kwargs):
raise RuntimeError("network down")
monkeypatch.setattr("openbb_famafrench.utils.helpers.get_breakpoint_data", _boom)
query = FamaFrenchBreakpointQueryParams()
with pytest.raises(OpenBBError):
asyncio.run(FamaFrenchBreakpointFetcher.aextract_data(query, None))
def _portfolio_frame():
frame = DataFrame(
{
"Lo 30": ["1.0", "2.0", "3.0"],
"Hi 30": ["-99.99", "-99.99", "-99.99"],
},
index=["2020-01-31", "2020-02-29", "2020-03-31"],
)
frame.index.name = "Date"
return frame
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(FamaFrenchUSPortfolioReturnsFetcher, FamaFrenchUSPortfolioReturnsQueryParams),
(
FamaFrenchRegionalPortfolioReturnsFetcher,
FamaFrenchRegionalPortfolioReturnsQueryParams,
),
],
)
def test_portfolio_transform_query(fetcher, query_cls):
query = fetcher.transform_query({})
assert isinstance(query, query_cls)
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(FamaFrenchUSPortfolioReturnsFetcher, FamaFrenchUSPortfolioReturnsQueryParams),
(
FamaFrenchRegionalPortfolioReturnsFetcher,
FamaFrenchRegionalPortfolioReturnsQueryParams,
),
],
)
def test_portfolio_transform_data(fetcher, query_cls):
query = query_cls(measure="value", start_date="2020-02-01", end_date="2020-02-29")
data = ([_portfolio_frame()], [{"description": "Portfolio"}])
result = fetcher.transform_data(query, data)
assert result.result
assert all(r.portfolio == "Lo 30" for r in result.result)
assert all(r.measure == "value" for r in result.result)
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(FamaFrenchUSPortfolioReturnsFetcher, FamaFrenchUSPortfolioReturnsQueryParams),
(
FamaFrenchRegionalPortfolioReturnsFetcher,
FamaFrenchRegionalPortfolioReturnsQueryParams,
),
],
)
def test_portfolio_transform_data_number_of_firms(fetcher, query_cls):
frame = DataFrame(
{"Lo 30": ["10", "20"]},
index=["2020-01-31", "2020-02-29"],
)
frame.index.name = "Date"
query = query_cls(measure="number_of_firms")
data = ([frame], [{"description": "Portfolio"}])
result = fetcher.transform_data(query, data)
assert all(isinstance(r.value, int) for r in result.result)
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(FamaFrenchUSPortfolioReturnsFetcher, FamaFrenchUSPortfolioReturnsQueryParams),
(
FamaFrenchRegionalPortfolioReturnsFetcher,
FamaFrenchRegionalPortfolioReturnsQueryParams,
),
],
)
def test_portfolio_transform_data_empty(fetcher, query_cls):
with pytest.raises(OpenBBError, match="returned empty"):
fetcher.transform_data(query_cls(), ([], []))
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(FamaFrenchUSPortfolioReturnsFetcher, FamaFrenchUSPortfolioReturnsQueryParams),
(
FamaFrenchRegionalPortfolioReturnsFetcher,
FamaFrenchRegionalPortfolioReturnsQueryParams,
),
],
)
def test_portfolio_aextract_data_helper_error(monkeypatch, fetcher, query_cls):
def _boom(*args, **kwargs):
raise RuntimeError("network down")
monkeypatch.setattr("openbb_famafrench.utils.helpers.get_portfolio_data", _boom)
with pytest.raises(OpenBBError):
asyncio.run(fetcher.aextract_data(query_cls(), None))
def test_us_portfolio_aextract_data_daily(monkeypatch):
captured = {}
def _capture(dataset, measure=None, frequency=None):
captured["frequency"] = frequency
return ([], [])
monkeypatch.setattr("openbb_famafrench.utils.helpers.get_portfolio_data", _capture)
query = FamaFrenchUSPortfolioReturnsQueryParams(
portfolio="portfolios_formed_on_me_daily"
)
asyncio.run(FamaFrenchUSPortfolioReturnsFetcher.aextract_data(query, None))
assert captured["frequency"] is None
def test_regional_portfolio_aextract_data_daily(monkeypatch):
captured = {}
def _capture(dataset, measure=None, frequency=None):
captured["frequency"] = frequency
return ([], [])
monkeypatch.setattr("openbb_famafrench.utils.helpers.get_portfolio_data", _capture)
query = FamaFrenchRegionalPortfolioReturnsQueryParams(
portfolio="europe_6_portfolios_me_be-me_daily"
)
asyncio.run(FamaFrenchRegionalPortfolioReturnsFetcher.aextract_data(query, None))
assert captured["frequency"] is None
def _international_frame(multiindex: bool = False):
frame = DataFrame(
{
"Mkt": ["1.0", "2.0", "3.0"],
"High": ["0.5", "0.6", "0.7"],
"Low": ["-99.99", "-99.99", "-99.99"],
},
index=["2020-01-31", "2020-02-29", "2020-03-31"],
)
frame.index.name = "Date"
if multiindex:
frame.columns = MultiIndex.from_arrays(
[["", "BE/ME", "BE/ME"], ["Mkt", "High", "Low"]]
)
return frame
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(
FamaFrenchCountryPortfolioReturnsFetcher,
FamaFrenchCountryPortfolioReturnsQueryParams,
),
(
FamaFrenchInternationalIndexReturnsFetcher,
FamaFrenchInternationalIndexReturnsQueryParams,
),
],
)
def test_international_transform_query(fetcher, query_cls):
query = fetcher.transform_query({})
assert isinstance(query, query_cls)
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(
FamaFrenchCountryPortfolioReturnsFetcher,
FamaFrenchCountryPortfolioReturnsQueryParams,
),
(
FamaFrenchInternationalIndexReturnsFetcher,
FamaFrenchInternationalIndexReturnsQueryParams,
),
],
)
def test_international_transform_data(fetcher, query_cls):
query = query_cls(measure="usd", start_date="2020-02-01", end_date="2020-02-29")
data = ([_international_frame()], [{"description": "Index"}])
result = fetcher.transform_data(query, data)
assert result.result
assert "description" in result.metadata
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(
FamaFrenchCountryPortfolioReturnsFetcher,
FamaFrenchCountryPortfolioReturnsQueryParams,
),
(
FamaFrenchInternationalIndexReturnsFetcher,
FamaFrenchInternationalIndexReturnsQueryParams,
),
],
)
def test_international_transform_data_multiindex(fetcher, query_cls):
query = query_cls(measure="usd")
data = ([_international_frame(multiindex=True)], [{"description": "Index"}])
result = fetcher.transform_data(query, data)
assert result.result
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(
FamaFrenchCountryPortfolioReturnsFetcher,
FamaFrenchCountryPortfolioReturnsQueryParams,
),
(
FamaFrenchInternationalIndexReturnsFetcher,
FamaFrenchInternationalIndexReturnsQueryParams,
),
],
)
def test_international_transform_data_ratios_firms(fetcher, query_cls):
frame = DataFrame(
{
"firms": ["100", "200"],
"B/M": ["1.0", "2.0"],
},
index=["2020-12-31", "2021-12-31"],
)
frame.index.name = "Date"
query = query_cls(measure="ratios")
data = ([frame], [{"description": "Ratios"}])
result = fetcher.transform_data(query, data)
assert all(isinstance(r.firms, int) for r in result.result)
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(
FamaFrenchCountryPortfolioReturnsFetcher,
FamaFrenchCountryPortfolioReturnsQueryParams,
),
(
FamaFrenchInternationalIndexReturnsFetcher,
FamaFrenchInternationalIndexReturnsQueryParams,
),
],
)
def test_international_transform_data_empty(fetcher, query_cls):
with pytest.raises(OpenBBError, match="returned empty"):
fetcher.transform_data(query_cls(), ([], []))
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(
FamaFrenchCountryPortfolioReturnsFetcher,
FamaFrenchCountryPortfolioReturnsQueryParams,
),
(
FamaFrenchInternationalIndexReturnsFetcher,
FamaFrenchInternationalIndexReturnsQueryParams,
),
],
)
def test_international_aextract_data_helper_error(monkeypatch, fetcher, query_cls):
def _boom(*args, **kwargs):
raise RuntimeError("network down")
monkeypatch.setattr(
"openbb_famafrench.utils.helpers.get_international_portfolio", _boom
)
with pytest.raises(OpenBBError):
asyncio.run(fetcher.aextract_data(query_cls(), None))
@pytest.mark.parametrize(
("fetcher", "query_cls"),
[
(
FamaFrenchCountryPortfolioReturnsFetcher,
FamaFrenchCountryPortfolioReturnsQueryParams,
),
(
FamaFrenchInternationalIndexReturnsFetcher,
FamaFrenchInternationalIndexReturnsQueryParams,
),
],
)
def test_international_aextract_data_ratios_monthly_warns(
monkeypatch, fetcher, query_cls
):
captured = {}
def _capture(*args, **kwargs):
captured["frequency"] = kwargs.get("frequency")
return ([], [])
monkeypatch.setattr(
"openbb_famafrench.utils.helpers.get_international_portfolio", _capture
)
query = query_cls(measure="ratios", frequency="monthly")
with pytest.warns(UserWarning, match="annual"):
asyncio.run(fetcher.aextract_data(query, None))
assert captured["frequency"] == "annual"
DATES = ["2020-01-31", "2020-02-29", "2020-03-31"]
PORTFOLIO_FETCHERS = [
(FamaFrenchUSPortfolioReturnsFetcher, FamaFrenchUSPortfolioReturnsQueryParams),
(
FamaFrenchRegionalPortfolioReturnsFetcher,
FamaFrenchRegionalPortfolioReturnsQueryParams,
),
]
INTERNATIONAL_FETCHERS = [
(
FamaFrenchCountryPortfolioReturnsFetcher,
FamaFrenchCountryPortfolioReturnsQueryParams,
),
(
FamaFrenchInternationalIndexReturnsFetcher,
FamaFrenchInternationalIndexReturnsQueryParams,
),
]
def test_factors_transform_data_missing_values():
frame = DataFrame(
{"Mkt-RF": ["1.0", "-99.99"], "SMB": ["0.1", "0.2"], "RF": ["0.01", "-999"]},
index=DATES[:2],
)
frame.index.name = "Date"
result = FamaFrenchFactorsFetcher.transform_data(
FamaFrenchFactorsQueryParams(), ([frame], [{"description": "Factors"}])
).result
assert [r.mkt_rf for r in result] == [1.0, None]
assert [r.smb for r in result] == [0.1, 0.2]
assert [r.rf for r in result] == [0.01, None]
@pytest.mark.parametrize(("fetcher", "query_cls"), PORTFOLIO_FETCHERS)
def test_portfolio_transform_data_missing_values(fetcher, query_cls):
frame = DataFrame(
{
"Lo 30": ["1.0", "-99.99", "3.0"],
"Hi 30": ["-999", "2.0", "-99.990"],
"Med 40": ["-99.99", "-999", "-999.00"],
},
index=DATES,
)
frame.index.name = "Date"
result = fetcher.transform_data(
query_cls(measure="value"), ([frame], [{"description": "Portfolio"}])
).result
assert {(str(r.date), r.portfolio): r.value for r in result} == {
("2020-01-31", "Lo 30"): 1.0,
("2020-02-29", "Lo 30"): None,
("2020-03-31", "Lo 30"): 3.0,
("2020-01-31", "Hi 30"): None,
("2020-02-29", "Hi 30"): 2.0,
("2020-03-31", "Hi 30"): None,
}
@pytest.mark.parametrize(("fetcher", "query_cls"), PORTFOLIO_FETCHERS)
def test_portfolio_transform_data_number_of_firms_missing_values(fetcher, query_cls):
frame = DataFrame({"Lo 30": ["10", "-999", "-99.99"]}, index=DATES)
frame.index.name = "Date"
result = fetcher.transform_data(
query_cls(measure="number_of_firms"), ([frame], [{"description": "Portfolio"}])
).result
assert [r.value for r in result] == [10, None, None]
assert isinstance(result[0].value, int)
@pytest.mark.parametrize(("fetcher", "query_cls"), INTERNATIONAL_FETCHERS)
def test_international_transform_data_missing_values(fetcher, query_cls):
frame = DataFrame(
{
"Date": DATES,
"Mkt": ["1.0", "-99.99", "3.0"],
"High": ["0.5", "-99.99", "-999"],
"Low": ["-99.99", "0.2", "0.3"],
}
).set_index(["Date", "Mkt"])
frame.columns = MultiIndex.from_arrays([["BE/ME", "BE/ME"], ["High", "Low"]])
result = fetcher.transform_data(
query_cls(measure="usd"), ([frame], [{"description": "Index"}])
).result
assert [r.mkt for r in result] == [1.0, None, 3.0]
assert [r.be_me_high for r in result] == [0.5, None, None]
assert [r.be_me_low for r in result] == [None, 0.2, 0.3]