Files
OpenBB/openbb_platform/extensions/technical/tests/signals/test_regime.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

120 lines
4.0 KiB
Python

"""Tests for openbb_technical.signals.regime."""
from __future__ import annotations
import numpy as np
import pandas as pd
import pytest
from openbb_core.app.utils import df_to_basemodel
from openbb_technical.signals.regime import (
RegimeData,
RegimeQueryParams,
_classify,
regime,
)
@pytest.fixture(scope="module")
def trending_records():
"""A clean monotone uptrend — ADX should rise, choppiness should fall."""
n = 120
base = np.arange(1, n + 1, dtype="float64")
df = pd.DataFrame(
{
"open": base,
"high": base + 0.5,
"low": base - 0.5,
"close": base,
"volume": base * 100,
},
index=pd.date_range("2022-01-01", periods=n, freq="D", name="date"),
)
return df_to_basemodel(df.reset_index())
@pytest.fixture(scope="module")
def choppy_records():
"""Sideways sawtooth so choppiness sits near the upper rail."""
n = 120
sawtooth = np.tile([100.0, 101.0, 100.0, 99.0], n // 4 + 1)[:n]
df = pd.DataFrame(
{
"open": sawtooth,
"high": sawtooth + 0.2,
"low": sawtooth - 0.2,
"close": sawtooth,
"volume": np.full(n, 1000.0),
},
index=pd.date_range("2022-01-01", periods=n, freq="D", name="date"),
)
return df_to_basemodel(df.reset_index())
class TestRegimeEndpoint:
def test_returns_one_row_per_bar(self, trending_records):
result = regime(RegimeQueryParams(data=trending_records))
assert len(result.results) == 120
assert all(isinstance(r, RegimeData) for r in result.results)
def test_trending_data_reaches_trend_label(self, trending_records):
result = regime(RegimeQueryParams(data=trending_records))
labels = {r.regime for r in result.results}
assert "strong_trend" in labels or "weak_trend" in labels
def test_first_bars_are_warm_up_transition(self, trending_records):
result = regime(RegimeQueryParams(data=trending_records))
assert result.results[0].regime == "transition"
assert result.results[0].regime_changed is False
assert result.results[0].adx is None
assert result.results[0].choppiness is None
def test_choppy_data_hits_ranging(self, choppy_records):
result = regime(RegimeQueryParams(data=choppy_records))
labels = {r.regime for r in result.results}
assert "ranging" in labels
def test_regime_changed_flag(self, trending_records):
result = regime(RegimeQueryParams(data=trending_records))
assert any(r.regime_changed for r in result.results)
def test_custom_thresholds_alter_labels(self, trending_records):
tight = regime(
RegimeQueryParams(data=trending_records, adx_trend_threshold=200.0)
)
assert "strong_trend" not in {r.regime for r in tight.results}
class TestClassifyDirect:
"""Drive ``_classify`` directly to guarantee every branch executes."""
def test_none_adx_short_circuits(self):
assert _classify(None, 10.0, 25.0, 61.8) == "transition"
def test_none_choppiness_short_circuits(self):
assert _classify(50.0, None, 25.0, 61.8) == "transition"
def test_strong_trend(self):
assert _classify(40.0, 30.0, 25.0, 61.8) == "strong_trend"
def test_weak_trend(self):
assert _classify(20.0, 70.0, 25.0, 61.8) == "weak_trend"
def test_weak_trend_high_chop_overrides_strong(self):
assert _classify(30.0, 70.0, 25.0, 61.8) == "weak_trend"
def test_ranging(self):
assert _classify(5.0, 70.0, 25.0, 61.8) == "ranging"
def test_transition_fallback(self):
assert _classify(5.0, 30.0, 25.0, 61.8) == "transition"
class TestQueryParamModel:
def test_defaults(self, trending_records):
params = RegimeQueryParams(data=trending_records)
assert params.adx_length == 14
assert params.choppiness_length == 14
assert params.adx_trend_threshold == 25.0
assert params.choppiness_range_threshold == 61.8