galaxis-po/backend/tests/unit/test_kjb_signal.py

101 lines
3.1 KiB
Python
Raw Normal View History

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