Files
OpenBB/openbb_platform/extensions/technical/tests/multi/test_compose.py
Danglewood d3d33a219b [V5] openbb-technical: Refactor for V5 & Add More Quick Win Endpoints (#7502)
* refactor openbb-technical for v5

* forward refs

* linting

* no python 3.15 in test matrix

* reame

* ty

* move all params to body

---------

Co-authored-by: deeleeramone <>
2026-05-29 10:48:51 +03:00

127 lines
3.9 KiB
Python

"""Tests for ``openbb_technical.multi.compose``."""
from __future__ import annotations
import pytest
from openbb_technical.multi.compose import (
MultiIndicatorRequest,
MultiQueryParams,
MultiResultRow,
_numeric_columns,
_resolve_indicator,
_row_key,
_to_records,
multi,
)
class TestResolveIndicator:
def test_resolves_known(self):
fn = _resolve_indicator("rsi")
assert callable(fn)
def test_skip_on_missing(self):
assert _resolve_indicator("___definitely_not_a_real_indicator___") is None
class TestHelpers:
def test_to_records_passthrough_dict(self):
rows = _to_records([{"a": 1}])
assert rows == [{"a": 1}]
def test_to_records_from_pydantic(self):
class _Row:
def model_dump(self):
return {"a": 2}
assert _to_records([_Row()]) == [{"a": 2}]
def test_to_records_unwraps_obbject(self):
class _OBB:
results = [{"a": 3}]
assert _to_records(_OBB()) == [{"a": 3}]
def test_row_key_uses_index(self):
assert _row_key({"date": "2021-01-01", "x": 1}, "date") == "2021-01-01"
def test_numeric_columns_drops_ohlc(self):
row = {
"date": "2021-01-01",
"open": 1.0,
"high": 1.0,
"low": 1.0,
"close": 1.0,
"volume": 1.0,
"rsi": 55.0,
"label": "tag",
}
out = _numeric_columns(row, target="close")
assert "rsi" in out
assert "label" not in out
assert "open" not in out
def test_numeric_columns_passes_none(self):
row = {"rsi": None}
out = _numeric_columns(row, target="close")
assert out == {"rsi": None}
class TestMultiEndpoint:
def test_single_indicator(self, single_symbol_records):
out = multi(
MultiQueryParams(
data=single_symbol_records,
indicators=[
MultiIndicatorRequest(indicator="rsi", params={"length": 14})
],
)
)
assert out.results
assert all(isinstance(r, MultiResultRow) for r in out.results)
assert any(r.values.get("rsi.rsi") is not None for r in out.results)
def test_two_indicators_merge(self, single_symbol_records):
out = multi(
MultiQueryParams(
data=single_symbol_records,
indicators=[
MultiIndicatorRequest(indicator="rsi", params={"length": 14}),
MultiIndicatorRequest(indicator="atr", params={"length": 14}),
],
)
)
assert out.results
for row in out.results:
if "rsi.rsi" in row.values and "atr.atr" in row.values:
assert row.values["rsi.rsi"] is not None
assert row.values["atr.atr"] is not None
break
else: # pragma: no cover - sanity guard, fixture has 300 bars
pytest.fail("Never found a row where both indicators warmed up.")
def test_unknown_indicator_skipped(self, single_symbol_records):
out = multi(
MultiQueryParams(
data=single_symbol_records,
indicators=[
MultiIndicatorRequest(indicator="not_a_real_indicator", params={}),
MultiIndicatorRequest(indicator="rsi", params={"length": 14}),
],
)
)
assert out.results
assert all(
all(not k.startswith("not_a_real_indicator.") for k in r.values)
for r in out.results
)
def test_query_params_defaults(self, single_symbol_records):
params = MultiQueryParams(
data=single_symbol_records,
indicators=[MultiIndicatorRequest(indicator="rsi")],
)
assert params.index == "date"
assert params.target == "close"