Files
kis_bot/kis_trader/backtest/momentum_rr_quick_eval.py
Hwang 61c72a8a4c 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>
2026-07-06 01:27:00 +09:00

117 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""손익비 후보 소수 조합 — 단일 프로세스·학습+OOS 동시 표 (멀티프로세싱 없음)."""
from __future__ import annotations
import os
import sys
from itertools import product
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(HERE))
if ROOT not in sys.path:
sys.path.insert(0, ROOT)
from database import TradeDB
from kis_trader.backtest.param_search_momentum import (
_evaluate_momentum_chunk,
_load_candles_for_search,
_mom_fixed_defaults,
)
from kis_trader.backtest.momentum_rr_crossval import _base_entry_ui, _load_universe
def main() -> None:
train = ("2026-05-11", "2026-05-30")
oos = ("2026-06-01", "2026-06-01")
fixed = _mom_fixed_defaults()
base = _base_entry_ui(fixed)
# 핵심 손익비 후보만 (실행 시간 — 전체 Cartesian 은 param_search --mode rr 사용)
candidates = [
(2.0, 2.0, 1.8, 0.5, 0.02), # baseline_live
(2.0, 2.5, 2.5, 50.0, 0.25), # shoulder_off
(1.5, 2.5, 2.5, 1.0, 0.25), # wide_shoulder
(1.2, 3.0, 3.0, 1.2, 0.30), # delayed_shoulder
(1.5, 2.0, 2.0, 50.0, 0.20), # tp_only
(1.8, 2.2, 2.2, 0.8, 0.20), # loose_shoulder
(1.5, 2.5, 2.5, 0.5, 0.15),
(1.5, 2.5, 2.5, 1.0, 0.15),
(1.8, 2.0, 2.0, 1.0, 0.20),
(2.0, 2.5, 2.5, 1.0, 0.15),
(1.2, 2.5, 2.5, 1.0, 0.25),
(1.5, 3.0, 3.0, 50.0, 0.25),
]
combos = []
for sl, tp, tpm, sa, sc in candidates:
ui = dict(base)
ui.update({
"sl_pct": sl, "tp_pct": tp, "tp_max_pct": tpm,
"shoulder_min_high": sa, "shoulder_cut_pct": sc,
})
combos.append(ui)
print(f"평가 조합: {len(combos)}개 (단일 프로세스)")
periods = [("train", train), ("oos", oos)]
data_cache = {}
uni_cache = {}
for label, (s, e) in periods:
data_cache[label] = _load_candles_for_search(s, e, 3)
uni_cache[label], src = _load_universe(s, e)
print(f" {label} {s}~{e}: candles={len(data_cache[label])} uni={src}")
db = TradeDB()
row = db.conn.execute("SELECT * FROM env_config ORDER BY id DESC LIMIT 1").fetchone()
env_row = dict(row) if row else {}
db.close()
from kis_trader.backtest import scalping_backtest_common as sbc
fee, tax, _ = sbc.fee_and_slot_from_env(env_row, strategy="MOMENTUM")
rows = []
keys = list(combos[0].keys()) if combos else []
for i, ui in enumerate(combos, 1):
row_train = row_oos = None
for label, _ in periods:
heap = _evaluate_momentum_chunk(
[ui], fixed, keys,
data_cache[label], 1, 0.0, 0.0, 1, uni_cache[label],
200_000, 20, 2_000_000, fee, tax, 20 if label == "train" else 1,
)
if heap:
_, _, _, pkg = heap[0]
if label == "train":
row_train = pkg
else:
row_oos = pkg
if not row_train or not row_oos:
continue
rows.append({
"sl": ui["sl_pct"], "tp": ui["tp_pct"], "tpmax": ui["tp_max_pct"],
"sh": f"{ui['shoulder_min_high']}/{ui['shoulder_cut_pct']}",
"train_pnl": row_train["total_pnl"],
"oos_pnl": row_oos["total_pnl"],
"train_pf": row_train["pf"],
"oos_pf": row_oos["pf"],
"oos_trades": row_oos["total_trades"],
})
if i % 10 == 0:
print(f" ... {i}/{len(combos)}")
rows.sort(key=lambda r: (r["oos_pnl"], r["train_pnl"]), reverse=True)
print("\n=== OOS 손익 우선 TOP 10 (train 5/11~30 + oos 6/1) ===")
print(f"{'sl':>4} {'tp':>4} {'max':>4} {'shoulder':>12} {'train':>10} {'oos':>10} {'oos_pf':>6}")
for r in rows[:10]:
print(
f"{r['sl']:4.1f} {r['tp']:4.1f} {r['tpmax']:4.1f} {r['sh']:>12} "
f"{r['train_pnl']:>10,} {r['oos_pnl']:>10,} {r['oos_pf']:>6.2f}"
)
pos_both = [r for r in rows if r["train_pnl"] > 0 and r["oos_pnl"] > 0]
print(f"\n학습·OOS 둘 다 플러스: {len(pos_both)}")
if pos_both:
b = pos_both[0]
print(f" 추천: sl={b['sl']} tp={b['tp']} tpmax={b['tpmax']} shoulder={b['sh']}")
if __name__ == "__main__":
main()