변경 사항 ---- - _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>
117 lines
4.2 KiB
Python
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()
|