fix: add KR_FALLBACK_STOCKS for domestic scanner when ranking API returns empty (#153)
Some checks failed
CI / test (pull_request) Has been cancelled

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 <noreply@anthropic.com>
This commit is contained in:
agentson
2026-02-19 09:17:14 +09:00
parent c76e2dfed5
commit 86a47bec7f
3 changed files with 56 additions and 1 deletions

View File

@@ -75,6 +75,14 @@ class Settings(BaseSettings):
# Market selection (comma-separated market codes) # Market selection (comma-separated market codes)
ENABLED_MARKETS: str = "KR,US" 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 and Disaster Recovery (optional)
BACKUP_ENABLED: bool = True BACKUP_ENABLED: bool = True
BACKUP_DIR: str = "data/backups" BACKUP_DIR: str = "data/backups"

View File

@@ -1698,7 +1698,14 @@ async def run(settings: Settings) -> None:
logger.info("Smart Scanner: Scanning %s market", market.name) logger.info("Smart Scanner: Scanning %s market", market.name)
fallback_stocks: list[str] | None = None 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( fallback_stocks = await build_overseas_symbol_universe(
db_conn=db_conn, db_conn=db_conn,
overseas_broker=overseas_broker, overseas_broker=overseas_broker,

View File

@@ -1678,3 +1678,43 @@ def test_start_dashboard_server_enabled_starts_thread() -> None:
assert thread == mock_thread assert thread == mock_thread
mock_thread_cls.assert_called_once() mock_thread_cls.assert_called_once()
mock_thread.start.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"]