변경 사항 (Changes): 구문 오류(Syntax error) 및 토큰 낭비를 방지하기 위해 에이전트 쉘(Agent shell)과 파이썬 코드 스니펫에 다수의 신규 안전 규칙(Safety rules)을 추가함. 스키마 검증 및 적절한 SQL 포맷팅을 보장하기 위해 임시(Ad-hoc) 데이터베이스 쿼리 작성 가이드라인을 도입함. 코드 수정 후 UI 기능이 정상 작동하는지 확인하기 위해, 백테스트 웹 서비스 재시작 및 브라우저 검증에 대한 새로운 규칙을 구현함. 시스템 전반의 무결성(Integrity)을 유지하기 위해 실전 매매(Live trading), 웹 백테스팅, 파라미터 탐색(Parameter searches) 간의 일관성 검사(Consistency checks) 체계를 확립함. 기대 효과 (Impact): 이러한 개선 사항들은 트레이딩 시스템의 견고성(Robustness)과 신뢰성을 향상시키며, 에러 발생을 최소화하고 다양한 시스템 컴포넌트 간의 원활한 상호작용을 보장함.
114 lines
4.2 KiB
Python
114 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 kis_trader.backtest.backtest_portfolio_common import load_portfolio_env_row
|
|
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}")
|
|
|
|
env_row = load_portfolio_env_row()
|
|
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()
|