mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-28 13:01:38 +00:00
* 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 <>
412 lines
12 KiB
Python
412 lines
12 KiB
Python
import asyncio
|
|
from datetime import date
|
|
|
|
import pytest
|
|
from openbb_core.app.model.abstract.error import OpenBBError
|
|
from openbb_core.provider.utils.errors import EmptyDataError
|
|
|
|
from openbb_cftc.models.cot import CftcCotData, CftcCotFetcher, CftcCotQueryParams
|
|
from openbb_cftc.models.cot_search import (
|
|
CftcCotSearchFetcher,
|
|
CftcCotSearchQueryParams,
|
|
)
|
|
|
|
COT_RECORD = {
|
|
"report_date_as_yyyy_mm_dd": "2024-08-20T00:00:00.000",
|
|
"market_and_exchange_names": "GOLD - COMMODITY EXCHANGE INC.",
|
|
"cftc_contract_market_code": "088691",
|
|
"commodity_name": "GOLD",
|
|
"commodity_group_name": "NATURAL RESOURCES",
|
|
"commodity_subgroup_name": "PRECIOUS METALS",
|
|
"contract_units": "(TROY OUNCES)",
|
|
"open_interest_all": "500000",
|
|
"noncomm_positions_long_all": "250000",
|
|
"noncomm_positions_short_all": "100000",
|
|
"change_in_noncomm_long_all": "1500",
|
|
"change_in_noncomm_short_all": "-500",
|
|
"pct_of_oi_noncomm_long_all": "50.0",
|
|
"traders_noncomm_long_all": "120",
|
|
"conc_gross_le_4_tdr_long": "22.5",
|
|
"yyyy_report_week_ww": "2024 Report Week 34",
|
|
}
|
|
|
|
|
|
def _capture_url(monkeypatch, response=None):
|
|
calls: list[str] = []
|
|
|
|
async def _request(url, **kwargs):
|
|
calls.append(url)
|
|
return response if response is not None else [COT_RECORD]
|
|
|
|
monkeypatch.setattr("openbb_core.provider.utils.helpers.amake_request", _request)
|
|
|
|
return calls
|
|
|
|
|
|
def test_cot_extract_builds_a_code_query(monkeypatch):
|
|
calls = _capture_url(monkeypatch)
|
|
asyncio.run(
|
|
CftcCotFetcher.aextract_data(CftcCotQueryParams(code="CFTC_088691"), None)
|
|
)
|
|
|
|
assert "088691" in calls[0]
|
|
assert "CFTC_" not in calls[0]
|
|
assert "1995-01-01" in calls[0]
|
|
|
|
|
|
def test_cot_extract_serves_a_repeat_from_the_response_cache(monkeypatch):
|
|
calls = _capture_url(monkeypatch)
|
|
first = asyncio.run(
|
|
CftcCotFetcher.aextract_data(CftcCotQueryParams(code="CFTC_088691"), None)
|
|
)
|
|
second = asyncio.run(
|
|
CftcCotFetcher.aextract_data(CftcCotQueryParams(code="CFTC_088691"), None)
|
|
)
|
|
|
|
assert first == second
|
|
assert len(calls) == 1
|
|
|
|
|
|
def test_cot_search_serves_a_repeat_from_the_response_cache(monkeypatch):
|
|
calls: list[str] = []
|
|
|
|
async def _request(url, **kwargs):
|
|
calls.append(url)
|
|
return [COT_RECORD]
|
|
|
|
monkeypatch.setattr("openbb_core.provider.utils.helpers.amake_request", _request)
|
|
|
|
first = asyncio.run(
|
|
CftcCotSearchFetcher.aextract_data(CftcCotSearchQueryParams(query="gold"), None)
|
|
)
|
|
second = asyncio.run(
|
|
CftcCotSearchFetcher.aextract_data(CftcCotSearchQueryParams(query="gold"), None)
|
|
)
|
|
|
|
assert first == second
|
|
assert len(calls) == 1
|
|
|
|
|
|
def test_cot_extract_all_returns_the_latest_week(monkeypatch):
|
|
calls = _capture_url(monkeypatch)
|
|
asyncio.run(CftcCotFetcher.aextract_data(CftcCotQueryParams(code="all"), None))
|
|
|
|
assert "like UPPER" not in calls[0]
|
|
assert "1995-01-01" not in calls[0]
|
|
|
|
|
|
def test_cot_extract_wraps_a_name_query(monkeypatch):
|
|
calls = _capture_url(monkeypatch)
|
|
asyncio.run(CftcCotFetcher.aextract_data(CftcCotQueryParams(code="gold"), None))
|
|
|
|
assert "%gold%" in calls[0]
|
|
|
|
|
|
def test_cot_extract_escapes_reserved_characters(monkeypatch):
|
|
calls = _capture_url(monkeypatch)
|
|
asyncio.run(CftcCotFetcher.aextract_data(CftcCotQueryParams(code="S&P 500+"), None))
|
|
|
|
assert "%26" in calls[0]
|
|
assert "%2B" in calls[0]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("report_type", "futures_only", "expected"),
|
|
[
|
|
("legacy", True, "6dca-aqww"),
|
|
("legacy", False, "jun7-fc8e"),
|
|
("disaggregated", True, "72hh-3qpy"),
|
|
("financial", False, "yw9f-hn96"),
|
|
("supplemental", True, "4zgm-a668"),
|
|
],
|
|
)
|
|
def test_cot_extract_selects_the_dataset(
|
|
monkeypatch, report_type, futures_only, expected
|
|
):
|
|
calls = _capture_url(monkeypatch)
|
|
asyncio.run(
|
|
CftcCotFetcher.aextract_data(
|
|
CftcCotQueryParams(
|
|
code="088691", report_type=report_type, futures_only=futures_only
|
|
),
|
|
None,
|
|
)
|
|
)
|
|
|
|
assert expected in calls[0]
|
|
|
|
|
|
def test_cot_extract_appends_the_app_token(monkeypatch):
|
|
calls = _capture_url(monkeypatch)
|
|
asyncio.run(
|
|
CftcCotFetcher.aextract_data(
|
|
CftcCotQueryParams(code="088691"), {"cftc_app_token": "SECRET"}
|
|
)
|
|
)
|
|
|
|
assert "$$app_token=SECRET" in calls[0]
|
|
|
|
|
|
def test_cot_extract_honours_explicit_dates(monkeypatch):
|
|
calls = _capture_url(monkeypatch)
|
|
asyncio.run(
|
|
CftcCotFetcher.aextract_data(
|
|
CftcCotQueryParams(
|
|
code="088691",
|
|
start_date=date(2024, 8, 19),
|
|
end_date=date(2024, 8, 21),
|
|
),
|
|
None,
|
|
)
|
|
)
|
|
|
|
assert "2024-08-19" in calls[0]
|
|
assert "2024-08-21" in calls[0]
|
|
|
|
|
|
def test_cot_extract_propagates_errors(monkeypatch):
|
|
|
|
async def _raise(url, **kwargs):
|
|
raise OpenBBError("upstream down")
|
|
|
|
monkeypatch.setattr("openbb_core.provider.utils.helpers.amake_request", _raise)
|
|
|
|
with pytest.raises(OpenBBError, match="upstream down"):
|
|
asyncio.run(CftcCotFetcher.aextract_data(CftcCotQueryParams(code="x"), None))
|
|
|
|
|
|
def test_cot_extract_raises_on_empty(monkeypatch):
|
|
_capture_url(monkeypatch, response=[])
|
|
|
|
with pytest.raises(EmptyDataError, match="No data found"):
|
|
asyncio.run(CftcCotFetcher.aextract_data(CftcCotQueryParams(code="x"), None))
|
|
|
|
|
|
def test_cot_transform_types_and_scales():
|
|
results = CftcCotFetcher.transform_data(
|
|
CftcCotQueryParams(code="088691"), [COT_RECORD]
|
|
)
|
|
record = results[0]
|
|
|
|
assert isinstance(record, CftcCotData)
|
|
assert record.date == date(2024, 8, 20)
|
|
assert record.open_interest_all == 500000
|
|
assert record.contract_units == "TROY OUNCES"
|
|
assert record.commodity == "GOLD"
|
|
assert record.open_interest_pct_non_commercial_long_all == pytest.approx(0.5)
|
|
assert record.concentration_gross_top_4_traders_long == pytest.approx(22.5)
|
|
|
|
|
|
def test_cot_transform_skips_empty_records():
|
|
assert (
|
|
CftcCotFetcher.transform_data(CftcCotQueryParams(code="x"), [{"a": None}]) == []
|
|
)
|
|
|
|
|
|
def test_cot_transform_keeps_unparseable_values():
|
|
results = CftcCotFetcher.transform_data(
|
|
CftcCotQueryParams(code="x"),
|
|
[{**COT_RECORD, "futonly_or_combined": "FutOnly"}],
|
|
)
|
|
|
|
assert results[0].futonly_or_combined == "FutOnly"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("measure", "present", "absent"),
|
|
[
|
|
(
|
|
"changes",
|
|
"change_in_non_commercial_long_all",
|
|
"traders_non_commercial_long_all",
|
|
),
|
|
(
|
|
"traders",
|
|
"traders_non_commercial_long_all",
|
|
"change_in_non_commercial_long_all",
|
|
),
|
|
(
|
|
"percent_of_oi",
|
|
"open_interest_pct_non_commercial_long_all",
|
|
"traders_non_commercial_long_all",
|
|
),
|
|
(
|
|
"concentration",
|
|
"concentration_gross_top_4_traders_long",
|
|
"change_in_non_commercial_long_all",
|
|
),
|
|
(
|
|
"positions",
|
|
"non_commercial_positions_long_all",
|
|
"change_in_non_commercial_long_all",
|
|
),
|
|
],
|
|
)
|
|
def test_cot_transform_measure_filter(measure, present, absent):
|
|
results = CftcCotFetcher.transform_data(
|
|
CftcCotQueryParams(code="088691", measure=measure), [COT_RECORD]
|
|
)
|
|
dumped = results[0].model_dump()
|
|
|
|
assert dumped.get(present) is not None
|
|
assert dumped.get(absent) is None
|
|
assert dumped["open_interest_all"] == 500000
|
|
assert dumped["date"] == date(2024, 8, 20)
|
|
|
|
|
|
def test_cot_transform_limit_keeps_the_latest():
|
|
older = {**COT_RECORD, "report_date_as_yyyy_mm_dd": "2024-08-13T00:00:00.000"}
|
|
results = CftcCotFetcher.transform_data(
|
|
CftcCotQueryParams(code="088691", limit=1), [older, COT_RECORD]
|
|
)
|
|
|
|
assert len(results) == 1
|
|
assert results[0].date == date(2024, 8, 20)
|
|
|
|
|
|
def test_cot_transform_blanks_duplicated_crop_year_columns():
|
|
record = {
|
|
**COT_RECORD,
|
|
"noncomm_positions_long_old": "250000",
|
|
"noncomm_positions_long_other": "250000",
|
|
}
|
|
results = CftcCotFetcher.transform_data(CftcCotQueryParams(code="x"), [record])
|
|
|
|
assert results[0].non_commercial_positions_long_all == 250000
|
|
assert results[0].non_commercial_positions_long_old is None
|
|
|
|
|
|
def test_cot_search_extract_filters(monkeypatch):
|
|
calls = _capture_url(
|
|
monkeypatch, response=[{"cftc_contract_market_code": "088691"}]
|
|
)
|
|
asyncio.run(
|
|
CftcCotSearchFetcher.aextract_data(
|
|
CftcCotSearchQueryParams(
|
|
query="gold's",
|
|
category="natural_resources",
|
|
subcategory="precious_metals",
|
|
futures_only=True,
|
|
),
|
|
{"cftc_app_token": "SECRET"},
|
|
)
|
|
)
|
|
url = calls[0]
|
|
|
|
assert "6dca-aqww" in url
|
|
assert "NATURAL%20RESOURCES" in url
|
|
assert "PRECIOUS%20METALS" in url
|
|
assert "$$app_token=SECRET" in url
|
|
assert "gold%27%27s" in url
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("subcategory", "expected"),
|
|
[
|
|
("currency_non_major", "CURRENCY(NON-MAJOR)"),
|
|
("foodstuffs_softs", "FOODSTUFFS/SOFTS"),
|
|
("interest_rates_us_treasury", "INTEREST RATES - U.S. TREASURY"),
|
|
("base_metals", "BASE METALS"),
|
|
],
|
|
)
|
|
def test_cot_search_subcategory_mapping(monkeypatch, subcategory, expected):
|
|
from urllib.parse import unquote
|
|
|
|
calls = _capture_url(monkeypatch, response=[{"cftc_contract_market_code": "1"}])
|
|
asyncio.run(
|
|
CftcCotSearchFetcher.aextract_data(
|
|
CftcCotSearchQueryParams(subcategory=subcategory), None
|
|
)
|
|
)
|
|
|
|
assert expected in unquote(calls[0])
|
|
|
|
|
|
def test_cot_search_supplemental_is_not_suffixed(monkeypatch):
|
|
calls = _capture_url(monkeypatch, response=[{"cftc_contract_market_code": "1"}])
|
|
asyncio.run(
|
|
CftcCotSearchFetcher.aextract_data(
|
|
CftcCotSearchQueryParams(report_type="supplemental", futures_only=True),
|
|
None,
|
|
)
|
|
)
|
|
|
|
assert "4zgm-a668" in calls[0]
|
|
|
|
|
|
def test_cot_search_propagates_errors(monkeypatch):
|
|
|
|
async def _raise(url, **kwargs):
|
|
raise OpenBBError("upstream down")
|
|
|
|
monkeypatch.setattr("openbb_core.provider.utils.helpers.amake_request", _raise)
|
|
|
|
with pytest.raises(OpenBBError, match="upstream down"):
|
|
asyncio.run(
|
|
CftcCotSearchFetcher.aextract_data(CftcCotSearchQueryParams(), None)
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("params", "message"),
|
|
[
|
|
({"query": "nothing"}, "No results found for 'nothing'"),
|
|
({}, "No results returned"),
|
|
],
|
|
)
|
|
def test_cot_search_raises_on_empty(monkeypatch, params, message):
|
|
_capture_url(monkeypatch, response=[])
|
|
|
|
with pytest.raises(EmptyDataError, match=message):
|
|
asyncio.run(
|
|
CftcCotSearchFetcher.aextract_data(CftcCotSearchQueryParams(**params), None)
|
|
)
|
|
|
|
|
|
def test_cot_search_transform_prefixes_and_dedupes():
|
|
row = {
|
|
"cftc_contract_market_code": "088691",
|
|
"contract_market_name": "GOLD",
|
|
"commodity_name": "GOLD",
|
|
"commodity_group_name": "NATURAL RESOURCES",
|
|
"commodity_subgroup_name": "PRECIOUS METALS",
|
|
}
|
|
results = CftcCotSearchFetcher.transform_data(
|
|
CftcCotSearchQueryParams(), [row, dict(row)]
|
|
)
|
|
|
|
assert len(results) == 1
|
|
assert results[0].code == "CFTC_088691"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("name", "category", "subcategory"),
|
|
[
|
|
("S&P 500 INDICES", "FINANCIAL INSTRUMENTS", "STOCK INDICES"),
|
|
("CRYPTO ASSET", "FINANCIAL INSTRUMENTS", "DIGITAL ASSET (NON-MAJOR)"),
|
|
],
|
|
)
|
|
def test_cot_search_transform_infers_missing_groups(name, category, subcategory):
|
|
results = CftcCotSearchFetcher.transform_data(
|
|
CftcCotSearchQueryParams(),
|
|
[
|
|
{
|
|
"cftc_contract_market_code": "1",
|
|
"contract_market_name": name,
|
|
"commodity_name": name,
|
|
}
|
|
],
|
|
)
|
|
|
|
assert results[0].category == category
|
|
assert results[0].subcategory == subcategory
|
|
|
|
|
|
def test_cot_search_transform_skips_rows_without_a_code():
|
|
assert (
|
|
CftcCotSearchFetcher.transform_data(
|
|
CftcCotSearchQueryParams(), [{"contract_market_name": "GOLD"}]
|
|
)
|
|
== []
|
|
)
|