finsight-api / tests /test_technical.py
Eymen
feat: initial FinSight MVP β€” stock analysis, RAG, conversational chat
fa77b99
Raw History Blame Contribute Delete
2.91 kB
"""
Unit tests for core.analysis.technical
"""
import numpy as np
import pandas as pd
import pytest
from core.analysis.technical import add_indicators, get_signals, signal_summary
@pytest.fixture
def sample_ohlcv() -> pd.DataFrame:
"""Generate 250 rows of synthetic OHLCV data."""
rng = np.random.default_rng(42)
n = 250
close = 100 + np.cumsum(rng.normal(0, 1, n))
high = close + rng.uniform(0.5, 2.0, n)
low = close - rng.uniform(0.5, 2.0, n)
open_ = close + rng.normal(0, 0.5, n)
vol = rng.integers(1_000_000, 10_000_000, n).astype(float)
dates = pd.date_range("2024-01-01", periods=n, freq="B")
return pd.DataFrame(
{"Open": open_, "High": high, "Low": low, "Close": close, "Volume": vol},
index=dates,
)
class TestAddIndicators:
def test_returns_dataframe(self, sample_ohlcv):
result = add_indicators(sample_ohlcv)
assert isinstance(result, pd.DataFrame)
def test_does_not_mutate_input(self, sample_ohlcv):
original_cols = list(sample_ohlcv.columns)
add_indicators(sample_ohlcv)
assert list(sample_ohlcv.columns) == original_cols
def test_expected_columns_present(self, sample_ohlcv):
df = add_indicators(sample_ohlcv)
expected = [
"sma_20", "sma_50", "sma_200",
"ema_12", "ema_26",
"macd", "macd_signal", "macd_hist",
"rsi_14",
"bb_upper", "bb_middle", "bb_lower", "bb_pct",
"atr_14",
"vol_sma_20",
]
for col in expected:
assert col in df.columns, f"Missing column: {col}"
def test_rsi_bounds(self, sample_ohlcv):
df = add_indicators(sample_ohlcv)
rsi = df["rsi_14"].dropna()
assert (rsi >= 0).all() and (rsi <= 100).all()
class TestGetSignals:
def test_returns_dict(self, sample_ohlcv):
df = add_indicators(sample_ohlcv)
signals = get_signals(df)
assert isinstance(signals, dict)
def test_valid_values(self, sample_ohlcv):
df = add_indicators(sample_ohlcv)
valid = {"BULLISH", "BEARISH", "NEUTRAL"}
for k, v in get_signals(df).items():
assert v in valid, f"Signal '{k}' has invalid value '{v}'"
def test_empty_dataframe(self):
assert get_signals(pd.DataFrame()) == {}
class TestSignalSummary:
def test_bullish_majority(self):
signals = {"A": "BULLISH", "B": "BULLISH", "C": "BEARISH"}
assert signal_summary(signals) == "BULLISH"
def test_bearish_majority(self):
signals = {"A": "BEARISH", "B": "BEARISH", "C": "BULLISH"}
assert signal_summary(signals) == "BEARISH"
def test_tie_returns_mixed(self):
signals = {"A": "BULLISH", "B": "BEARISH"}
assert signal_summary(signals) == "MIXED"
def test_empty_returns_neutral(self):
assert signal_summary({}) == "NEUTRAL"