From 86a47bec7fdaea9e206447279a433fcb48e32ea4 Mon Sep 17 00:00:00 2001 From: agentson Date: Thu, 19 Feb 2026 09:17:14 +0900 Subject: [PATCH] fix: add KR_FALLBACK_STOCKS for domestic scanner when ranking API returns empty (#153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KIS VTS (paper trading) does not return data from the domestic volume-rank API. The smart scanner had no fallback for KR market, causing permanent "No candidates" and zero trades all day. - src/config.py: add KR_FALLBACK_STOCKS with 10 major KRX stocks (삼성전자, SK하이닉스, NAVER, 현대차, 셀트리온, LG화학, 카카오, 삼성SDI, 삼성바이오로직스, 기아) - src/main.py: pass KR fallback stocks to scanner.scan() for domestic markets (mirrors the overseas build_overseas_symbol_universe pattern) - tests/test_main.py: add TestKrFallbackStocksConfig (3 tests) Closes #153 Co-Authored-By: Claude Sonnet 4.6 --- src/config.py | 8 ++++++++ src/main.py | 9 ++++++++- tests/test_main.py | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/config.py b/src/config.py index 80e3203..5e9ecae 100644 --- a/src/config.py +++ b/src/config.py @@ -75,6 +75,14 @@ class Settings(BaseSettings): # Market selection (comma-separated market codes) ENABLED_MARKETS: str = "KR,US" + # Fallback stock list for KR domestic market when ranking API returns empty + # (KIS VTS does not return data from volume-rank API). + # Comma-separated 6-digit stock codes. Override in .env if needed. + KR_FALLBACK_STOCKS: str = ( + "005930,000660,035420,005380,068270," # 삼성전자,SK하이닉스,NAVER,현대차,셀트리온 + "051910,035720,006400,207940,000270" # LG화학,카카오,삼성SDI,삼성바이오로직스,기아 + ) + # Backup and Disaster Recovery (optional) BACKUP_ENABLED: bool = True BACKUP_DIR: str = "data/backups" diff --git a/src/main.py b/src/main.py index d506f94..647b02e 100644 --- a/src/main.py +++ b/src/main.py @@ -1698,7 +1698,14 @@ async def run(settings: Settings) -> None: logger.info("Smart Scanner: Scanning %s market", market.name) fallback_stocks: list[str] | None = None - if not market.is_domestic: + if market.is_domestic: + # KIS VTS ranking API often returns empty for domestic. + # Use configured fallback so scanner can still run. + raw = settings.KR_FALLBACK_STOCKS if settings else "" + fallback_stocks = [ + c.strip() for c in raw.split(",") if c.strip() + ] or None + else: fallback_stocks = await build_overseas_symbol_universe( db_conn=db_conn, overseas_broker=overseas_broker, diff --git a/tests/test_main.py b/tests/test_main.py index 2f50702..46a798b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1678,3 +1678,43 @@ def test_start_dashboard_server_enabled_starts_thread() -> None: assert thread == mock_thread mock_thread_cls.assert_called_once() mock_thread.start.assert_called_once() + + +# --------------------------------------------------------------------------- +# KR fallback stocks config (issue #153) +# --------------------------------------------------------------------------- + + +class TestKrFallbackStocksConfig: + """Test KR_FALLBACK_STOCKS default value and parsing.""" + + def test_default_contains_samsung(self) -> None: + settings = Settings( + KIS_APP_KEY="k", + KIS_APP_SECRET="s", + KIS_ACCOUNT_NO="12345678-01", + GEMINI_API_KEY="g", + ) + codes = [c.strip() for c in settings.KR_FALLBACK_STOCKS.split(",") if c.strip()] + assert "005930" in codes # 삼성전자 + + def test_default_has_ten_codes(self) -> None: + settings = Settings( + KIS_APP_KEY="k", + KIS_APP_SECRET="s", + KIS_ACCOUNT_NO="12345678-01", + GEMINI_API_KEY="g", + ) + codes = [c.strip() for c in settings.KR_FALLBACK_STOCKS.split(",") if c.strip()] + assert len(codes) == 10 + + def test_env_override(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("KR_FALLBACK_STOCKS", "005930,000660") + settings = Settings( + KIS_APP_KEY="k", + KIS_APP_SECRET="s", + KIS_ACCOUNT_NO="12345678-01", + GEMINI_API_KEY="g", + ) + codes = [c.strip() for c in settings.KR_FALLBACK_STOCKS.split(",") if c.strip()] + assert codes == ["005930", "000660"] -- 2.49.1