101 lines
3.1 KiB
Python
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
|