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:
116
kis_trader/backtest/momentum_rr_quick_eval.py
Normal file
116
kis_trader/backtest/momentum_rr_quick_eval.py
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user