feat(tests): 신규 키움 웹소켓 조건검색 및 실시간 조건검색 테스트 추가

변경 사항
----
- _test_kiwoom_condition_list.py: 키움 웹소켓 조건검색 '목록조회' 기능을 단독으로 테스트하는 스크립트 추가
- _test_kiwoom_condition_realtime.py: 'momentum' 조건식을 실시간으로 등록하고 초기 매칭 종목 리스트 및 실시간 편입/이탈을 수신하는 테스트 스크립트 추가
- _verify_columnar_bitid.py, _verify_shared_e2e_breakout.py, _verify_shared_e2e.py: 공유 메모리 및 dict 간의 데이터 일관성을 검증하는 테스트 추가

영향
----
- 신규 테스트 스크립트 추가로 키움 웹소켓 API의 기능 검증 및 안정성을 높임
- 기존 기능에 대한 영향 없음

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 01:27:00 +09:00
parent d8ba01afa4
commit 61c72a8a4c
171 changed files with 176914 additions and 7329 deletions

View File

@@ -6,7 +6,7 @@ kis_trader/backtest/param_apply_ai.py
골라 DB env_config 에 적용한다.
- 스캘핑 : 최근 search_*.json (top 배열) 중 total_pnl > 0 만 AI 후보
- 꼬리잡기: tail_param_result.json 또는 tail_search_*.json (results 배열) 중 total_pnl > 0 만 AI 후보
- 꼬리잡기: 최신 search_tail_*.json (또는 tail_search_*.json · 레거시 tail_param_result.json)
- 승률만 높거나 손익만 큰 것은 제외하고 "수익이 나는 것 중 승률이 높은 것" 을 고르도록 프롬프트 구성
- [안전장치] 총손익(total_pnl)이 양수인 조합이 하나도 없으면 AI 적용을 즉시 취소하여 계좌를 보호
@@ -16,8 +16,8 @@ kis_trader/backtest/param_apply_ai.py
경로 정책:
- ROOT = kis_bot 프로젝트 루트 (kis_trader/backtest/파일 기준 3단계 위)
- HERE = 본 스크립트 폴더 (kis_trader/backtest/)
- 결과 디렉터리 = [HERE/results] 가 있으면 사용, 없으면 구(舊) [ROOT/backtest_scalping/results] 를 fallback
- tail 중간파일 = [HERE/tail_param_result.json] → fallback [ROOT/backtest_scalping/tail_param_result.json]
- 결과 디렉터리 = [HERE/results] · [~/.kis_bot_search_results] · 구 [ROOT/backtest_scalping/results]
- tail 결과 파일 = search_tail_{mode}_{ts}.json (매 실행 새 파일 · momentum/breakout 동일 패턴)
사용: param_search.py / tail_param_search.py 에서 --apply-ai 옵션으로 호출 (이 모듈을 임포트해 사용).
"""
@@ -39,6 +39,8 @@ HERE = os.path.dirname(os.path.abspath(__file__))
if HERE not in sys.path:
sys.path.insert(0, HERE)
from kis_trader.backtest.backtest_portfolio_common import merge_param_search_apply_source # noqa: E402
logger = logging.getLogger("param_apply_ai")
# Gemini 클라이언트 (지연 초기화)
@@ -51,15 +53,16 @@ _gemini_model_id = "gemini-2.5-flash"
# ──────────────────────────────────────────────────────────────────────────────
def _candidate_result_dirs() -> List[str]:
"""결과 디렉터리 후보 [신규 > 구] 순서로 반환."""
"""결과 디렉터리 후보 [신규 > 홈 폴백 > 구] 순서로 반환."""
return [
os.path.join(HERE, "results"),
os.path.join(os.path.expanduser("~"), ".kis_bot_search_results"),
os.path.join(ROOT, "backtest_scalping", "results"),
]
def _find_latest_json(prefix: str) -> Optional[str]:
"""prefix(예: 'search_', 'tail_search_') 로 시작하는 가장 최근 JSON 경로를 반환."""
"""prefix(예: 'search_tail_', 'tail_search_') 로 시작하는 가장 최근 JSON 경로를 반환."""
best_path, best_mtime = None, -1.0
for d in _candidate_result_dirs():
if not os.path.isdir(d):
@@ -78,16 +81,6 @@ def _find_latest_json(prefix: str) -> Optional[str]:
return best_path
def _find_tail_result_json() -> Optional[str]:
"""tail_param_result.json 을 신규·구 경로 순으로 찾는다."""
candidates = [
os.path.join(HERE, "tail_param_result.json"),
os.path.join(ROOT, "backtest_scalping", "tail_param_result.json"),
]
for p in candidates:
if os.path.isfile(p):
return p
return None
# ──────────────────────────────────────────────────────────────────────────────
@@ -241,10 +234,12 @@ def apply_ai_scalp() -> bool:
print("📌 DB 미적용. 기존 설정 유지.")
return False
# 같은 폴더의 param_search 모듈 사용 (HERE 가 sys.path 에 들어있음)
import param_search as _ps
# 같은 폴더의 param_search_scalping 모듈 사용 (HERE 가 sys.path 에 들어있음)
# 2026-05: param_search.py → param_search_scalping.py 로 rename (전략별 파일 분리)
import param_search_scalping as _ps
merged = profitable_top[rank - 1].get("merged_params") or profitable_top[rank - 1].get("params")
chosen = profitable_top[rank - 1]
merged = merge_param_search_apply_source(chosen, data)
if not merged:
msg = "❌ 해당 순위에 merged_params/params가 없습니다."
logger.error(msg)
@@ -260,17 +255,17 @@ def apply_ai_scalp() -> bool:
def apply_ai_tail() -> bool:
"""
tail_param_result.json (또는 최근 tail_search_*.json) 을 읽어 results 중
Gemini가 고른 순위를 DB에 적용.
최신 search_tail_*.json (또는 tail_search_* / 레거시 tail_param_result.json) 을 읽어
results 중 Gemini가 고른 순위를 DB에 적용.
Returns: 성공 여부.
"""
# 1) tail_param_result.json 우선 (신규 위치 → 구 위치)
path = _find_tail_result_json()
# 2) 없으면 tail_search_*.json 중 최신
import tail_param_search as _tps
path = _tps._find_tail_result_json()
if not path:
path = _find_latest_json("tail_search_")
if not path:
msg = "❌ tail_param_result.json / tail_search_*.json 이 없습니다."
msg = (
"❌ search_tail_*.json / tail_search_*.json / tail_param_result.json 을 찾을 수 없습니다."
)
logger.error(msg)
print(msg)
print("📌 DB 미적용. 기존 설정 유지.")
@@ -311,10 +306,8 @@ def apply_ai_tail() -> bool:
print("📌 DB 미적용. 기존 설정 유지.")
return False
import tail_param_search as _tps
# AI가 고른 순위(profitable_results 기준)의 params를 DB에 직접 반영
merged = profitable_results[rank - 1].get("params")
chosen = profitable_results[rank - 1]
merged = merge_param_search_apply_source(chosen, data)
_tps.apply_params_to_db(merged)
logger.info(f"✅ 꼬리잡기 {rank}위 조합을 DB에 적용했습니다. (파일: {path})")
print(f"✅ 꼬리잡기 {rank}위 조합을 DB에 적용했습니다.")