Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions core/live_readiness.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,20 @@ def check_basket_live_readiness(config, strategy_name: str) -> list[str]:
# KIS 모의투자 서버(use_mock=true)는 실돈이 아니다 — 게이트의 목적은
# 실자금 보호이므로, 모의서버에서의 live 경로 리허설(런북 Phase 1)은
# 평가 기간을 기다리지 않고 허용한다. 실계좌(use_mock=false)는 그대로 차단.
if bool((getattr(config, "kis_api", {}) or {}).get("use_mock", True)):
# 단, use_mock=true라도 mock_url이 실전 도메인으로 오설정돼 있으면 주문이
# 실서버로 가므로(KISApi는 use_mock 시 mock_url을 base_url로 사용) 실효
# 도메인이 모의투자(openapivts)일 때만 완화한다 — fail-closed.
kis_cfg = (getattr(config, "kis_api", {}) or {})
effective_mock_url = str(
kis_cfg.get("mock_url", "https://openapivts.koreainvestment.com:29443")
)
is_mock_rehearsal = (
bool(kis_cfg.get("use_mock", True))
and "openapivts" in effective_mock_url
)
if is_mock_rehearsal:
logger.warning(
"바스켓 '{}' 평가 미통과({})지만 KIS 모의투자 서버(use_mock=true) — "
"바스켓 '{}' 평가 미통과({})지만 KIS 모의투자 서버(use_mock=true, vts 도메인) — "
"live 경로 리허설 허용. 실계좌 전환 전 평가 통과 필수.",
basket_name, result["verdict"],
)
Expand Down
21 changes: 17 additions & 4 deletions tests/test_basket_live_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,25 @@ def _eval_result(verdict, issues=None, progress=60):
}


def _config(use_mock=False):
def _config(use_mock=False, mock_url=None):
"""게이트 테스트용 config — 기본은 실계좌(use_mock=False, 평가 차단 적용)."""
return SimpleNamespace(kis_api={"use_mock": use_mock})
kis = {"use_mock": use_mock}
if mock_url is not None:
kis["mock_url"] = mock_url
return SimpleNamespace(kis_api=kis)


class TestBasketLiveGate:
def _run(self, strategy_name, verdict="PASS_CANDIDATE", eval_issues=None,
progress=60, use_mock=False, baskets=None):
progress=60, use_mock=False, baskets=None, mock_url=None):
with patch(
"core.basket_rebalancer.BasketRebalancer._load_baskets_config",
return_value=baskets or _BASKETS,
), patch(
"core.basket_evaluation.collect_basket_paper_evaluation",
return_value=(_eval_result(verdict, eval_issues, progress), "label"),
) as collect:
issues = check_basket_live_readiness(_config(use_mock), strategy_name)
issues = check_basket_live_readiness(_config(use_mock, mock_url), strategy_name)
self._last_collect = collect
return issues

Expand Down Expand Up @@ -207,3 +210,13 @@ def test_promotion_min_days_resolved_by_collector_single_source(self):
basket_name="bk_md", include_benchmark=False, min_days=5,
)
assert result2["min_trading_days"] == 5

def test_mock_waiver_requires_vts_domain(self):
"""use_mock=true라도 mock_url이 실전 도메인으로 오설정돼 있으면 완화하지 않는다
— 주문이 실서버로 가는 조합에서 평가 면제는 금지(fail-closed)."""
issues = self._run(
strategy_name="basket_rebalance:kr_diversified_hold",
verdict="WAIT", progress=1, use_mock=True,
mock_url="https://openapi.koreainvestment.com:9443", # 실전 도메인
)
assert len(issues) == 1 and "WAIT" in issues[0]
Loading