mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 20:42:26 +00:00
* refactor quantitative for v5 * add factors endpoint and add a few charts --------- Co-authored-by: deeleeramone <>
82 lines
2.4 KiB
Python
82 lines
2.4 KiB
Python
"""Tests for ``openbb_quantitative.helpers``."""
|
|
|
|
import io
|
|
import zipfile
|
|
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from openbb_quantitative.helpers import get_fama_raw, validate_window
|
|
|
|
_FAMA_CSV = (
|
|
"Fama/French factors -- test fixture\n"
|
|
"second descriptive line\n"
|
|
"third descriptive line\n"
|
|
",Mkt-RF,SMB,HML,RF\n"
|
|
"202201,1.00,0.50,0.30,0.01\n"
|
|
"202202,-0.50,0.20,0.10,0.01\n"
|
|
"202203,0.75,-0.10,0.05,0.01\n"
|
|
"\n"
|
|
" Annual Factors: January-December \n"
|
|
"2022,5.00,2.00,1.00,0.12\n"
|
|
)
|
|
|
|
|
|
def _fama_zip_bytes() -> bytes:
|
|
"""Build an in-memory Fama-French factors zip archive."""
|
|
buffer = io.BytesIO()
|
|
with zipfile.ZipFile(buffer, "w") as archive:
|
|
archive.writestr("F-F_Research_Data_Factors.csv", _FAMA_CSV)
|
|
return buffer.getvalue()
|
|
|
|
|
|
class _FakeResponse:
|
|
"""Minimal stand-in for the urlopen response context manager."""
|
|
|
|
def __init__(self, payload: bytes):
|
|
self._payload = payload
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
return False
|
|
|
|
def read(self) -> bytes:
|
|
return self._payload
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_fama(monkeypatch):
|
|
"""Patch urlopen so get_fama_raw reads the in-memory archive, not the web."""
|
|
payload = _fama_zip_bytes()
|
|
monkeypatch.setattr("urllib.request.urlopen", lambda url: _FakeResponse(payload))
|
|
|
|
|
|
def test_get_fama_raw(mock_fama):
|
|
"""get_fama_raw parses the factor archive into a date-indexed DataFrame."""
|
|
df = get_fama_raw("2022-01-01", "2022-12-31")
|
|
assert list(df.columns) == ["mkt_rf", "smb", "hml", "rf"]
|
|
assert df.index.name == "date"
|
|
# Only the three monthly rows survive the six-character-date filter.
|
|
assert len(df) == 3
|
|
# Percent figures are converted to decimal fractions.
|
|
assert df["mkt_rf"].iloc[0] == pytest.approx(0.01)
|
|
|
|
|
|
def test_get_fama_raw_start_date_too_late(mock_fama):
|
|
"""A start date after the last observation raises a ValueError."""
|
|
with pytest.raises(ValueError, match="after the last available"):
|
|
get_fama_raw("2099-01-01", "2099-12-31")
|
|
|
|
|
|
def test_validate_window_within_bounds():
|
|
"""validate_window accepts a window no larger than the data."""
|
|
validate_window(pd.Series(range(100)), 20)
|
|
|
|
|
|
def test_validate_window_too_large():
|
|
"""validate_window raises when the window exceeds the data length."""
|
|
with pytest.raises(ValueError, match="larger than the data length"):
|
|
validate_window(pd.Series(range(10)), 50)
|