""" Unit tests for KJB signal generator. """ import pandas as pd from datetime import date, timedelta from app.services.strategy.kjb import KJBSignalGenerator def _make_price_df(closes, volumes=None, start_date=date(2024, 1, 2)): dates = pd.bdate_range(start=start_date, periods=len(closes)) if volumes is None: volumes = [1000000] * len(closes) highs = [c * 1.01 for c in closes] lows = [c * 0.99 for c in closes] opens = closes.copy() return pd.DataFrame({ "open": opens, "high": highs, "low": lows, "close": closes, "volume": volumes, }, index=dates) def _make_kospi_df(closes, start_date=date(2024, 1, 2)): dates = pd.bdate_range(start=start_date, periods=len(closes)) return pd.DataFrame({"close": closes}, index=dates) def test_relative_strength_above_market(): gen = KJBSignalGenerator() stock_closes = [100 + i for i in range(25)] kospi_closes = [100 + i * 0.5 for i in range(25)] stock_df = _make_price_df(stock_closes) kospi_df = _make_kospi_df(kospi_closes) rs = gen.calculate_relative_strength(stock_df, kospi_df, lookback=10) assert rs.dropna().iloc[-1] > 100 def test_relative_strength_below_market(): gen = KJBSignalGenerator() stock_closes = [100 + i * 0.3 for i in range(25)] kospi_closes = [100 + i for i in range(25)] stock_df = _make_price_df(stock_closes) kospi_df = _make_kospi_df(kospi_closes) rs = gen.calculate_relative_strength(stock_df, kospi_df, lookback=10) assert rs.dropna().iloc[-1] < 100 def test_detect_breakout(): gen = KJBSignalGenerator() closes = [100.0] * 20 + [105.0] stock_df = _make_price_df(closes) breakouts = gen.detect_breakout(stock_df, lookback=20) assert breakouts.iloc[-1] == True assert breakouts.iloc[-2] == False def test_detect_large_candle(): gen = KJBSignalGenerator() closes = [100.0] * 21 + [106.0] volumes = [1000000] * 21 + [3000000] stock_df = _make_price_df(closes, volumes) large = gen.detect_large_candle(stock_df, pct_threshold=0.05, vol_multiplier=1.5) assert large.iloc[-1] == True assert large.iloc[-2] == False def test_no_large_candle_low_volume(): gen = KJBSignalGenerator() closes = [100.0] * 21 + [106.0] volumes = [1000000] * 22 stock_df = _make_price_df(closes, volumes) large = gen.detect_large_candle(stock_df) assert large.iloc[-1] == False def test_generate_buy_signal(): gen = KJBSignalGenerator() closes = [100.0] * 20 + [106.0] volumes = [1000000] * 20 + [3000000] kospi_closes = [100.0 + i * 0.1 for i in range(21)] stock_df = _make_price_df(closes, volumes) kospi_df = _make_kospi_df(kospi_closes) signals = gen.generate_signals(stock_df, kospi_df) assert signals["buy"].iloc[-1] == True def test_no_signal_weak_stock(): gen = KJBSignalGenerator() # Stock underperforms market closes = [100.0 + i * 0.1 for i in range(25)] kospi_closes = [100 + i for i in range(25)] stock_df = _make_price_df(closes) kospi_df = _make_kospi_df(kospi_closes) signals = gen.generate_signals(stock_df, kospi_df) assert signals["buy"].iloc[-1] == False