galaxis-po/backend/tests/unit/test_kjb_signal.py
zephyrdark 932b46c5fe feat: add KJBSignalGenerator for daily buy/sell signal detection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:10:53 +09:00

101 lines
3.1 KiB
Python

"""
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