181 lines
6.1 KiB
Python
181 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Step1 검증: enrich_out_data_with_mode_combo 가 best/mode _trades 를 JSON에 남기는지.
|
|
|
|
기존 Optuna JSON의 #1·mode params 만 빌려, 동일 evaluate 경로로 1회 실측.
|
|
DB 미변경. 전체 Optuna 재실행 아님.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import traceback
|
|
from copy import deepcopy
|
|
from pathlib import Path
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument(
|
|
"--json",
|
|
default="kis_trader/backtest/results/optuna_momentum_tpe_20260821_220227.json",
|
|
)
|
|
ap.add_argument("--out", required=True)
|
|
args = ap.parse_args()
|
|
|
|
src = Path(args.json)
|
|
d = json.loads(src.read_text())
|
|
results = list(d.get("results") or [])
|
|
if not results:
|
|
print("no results", flush=True)
|
|
return 1
|
|
|
|
from kis_trader.backtest.optuna_momentum import prepare_momentum_search_context
|
|
from kis_trader.backtest.optuna_mode_combo import enrich_out_data_with_mode_combo
|
|
from kis_trader.backtest.param_search_momentum import evaluate_momentum_param_combo
|
|
|
|
print(
|
|
f"prepare {d['start']}~{d['end']} for trades-export smoke",
|
|
flush=True,
|
|
)
|
|
ctx = prepare_momentum_search_context(
|
|
d["start"],
|
|
d["end"],
|
|
"tpe",
|
|
slot_money=float(d["slot_money"]),
|
|
max_stocks=int(d["max_stocks"]),
|
|
total_budget_krw=float(d["total_budget_krw"]),
|
|
orderbook_filter="off",
|
|
market="KR",
|
|
history_source="kiwoom",
|
|
)
|
|
if ctx is None:
|
|
print("prepare failed", flush=True)
|
|
return 1
|
|
|
|
grid_keys = list(d.get("grid_keys") or ctx.grid_keys)
|
|
|
|
def _eval(combo):
|
|
return evaluate_momentum_param_combo(
|
|
combo,
|
|
base_fixed=ctx.base_fixed,
|
|
grid_keys=grid_keys,
|
|
codes_candles=ctx.codes_candles,
|
|
min_trades=1,
|
|
min_win_rate=0.0,
|
|
min_pf=0.0,
|
|
universe_by_slot=ctx.universe_by_slot,
|
|
slot_money=ctx.slot_money,
|
|
max_stocks=ctx.max_stocks,
|
|
total_budget_krw=ctx.total_budget_krw,
|
|
fee_rate=ctx.fee_rate,
|
|
sell_tax=ctx.sell_tax,
|
|
period_days=ctx.period_days,
|
|
cache_holder=ctx.cache_holder,
|
|
ticks_by_code=ctx.ticks_by_code,
|
|
orderbook_by_code=ctx.orderbook_by_code,
|
|
program_by_code=ctx.program_by_code,
|
|
log_verdict_by_code=ctx.log_verdict_by_code,
|
|
start_key=ctx.start_key,
|
|
end_key=ctx.end_key,
|
|
include_trades=True,
|
|
)
|
|
|
|
out_data = {
|
|
"engine": "optuna_trades_smoke",
|
|
"strategy": "momentum",
|
|
"mode": d.get("mode"),
|
|
"start": d["start"],
|
|
"end": d["end"],
|
|
"grid_keys": grid_keys,
|
|
# enrich 가 results[0] 을 best 로 씀 — 원본 #199 행 유지
|
|
"results": [deepcopy(results[0])],
|
|
"source_json": str(src),
|
|
"db_touched": False,
|
|
# 후처리(호가 TPE)는 스킵 — evaluate 만 검증. attach_topn 은 enrich 안이므로
|
|
# run_ob 을 막으려면 env? 그냥 돌리면 오래 걸림.
|
|
# → enrich 전체가 후처리까지 함. 스모크는 후처리 OFF 필요.
|
|
}
|
|
|
|
# 후처리 끄기: attach_topn_postprocess 의 run_ob_whipsaw — enrich 가 True 고정.
|
|
# 스모크 시간 절약: enrich 대신 best/mode 직접 호출과 동일 검증.
|
|
from kis_trader.backtest.optuna_common import slim_trades_for_optuna_json
|
|
from kis_trader.backtest.optuna_mode_combo import _attach_best_trial_trades
|
|
|
|
_attach_best_trial_trades(out_data, _eval, log=__import__("logging").getLogger("smoke"))
|
|
|
|
mode_params = dict((d.get("mode_combo") or {}).get("params") or {})
|
|
bt = _eval(mode_params) if mode_params else None
|
|
mode_trades = []
|
|
mode_summary = None
|
|
if isinstance(bt, dict):
|
|
mode_trades = slim_trades_for_optuna_json(list(bt.pop("_trades", None) or []))
|
|
mode_summary = {
|
|
"ok": True,
|
|
"total_pnl": bt.get("total_pnl"),
|
|
"total_trades": bt.get("total_trades"),
|
|
"win_rate": bt.get("win_rate"),
|
|
"pf": bt.get("pf"),
|
|
"_trades": mode_trades,
|
|
}
|
|
out_data["mode_combo"] = {
|
|
"params": mode_params,
|
|
"backtest": mode_summary,
|
|
"note": "smoke: mode params from source JSON, re-eval with _trades",
|
|
}
|
|
|
|
best_tr = out_data.get("best_trial_trades") or []
|
|
reeval = out_data.get("best_trial_reeval") or {}
|
|
checks = {
|
|
"best_trades_n": len(best_tr),
|
|
"best_trades_eq_reeval_tr": len(best_tr) == int(reeval.get("total_trades") or -1),
|
|
"mode_trades_n": len(mode_trades),
|
|
"mode_trades_eq_summary_tr": (
|
|
len(mode_trades) == int((mode_summary or {}).get("total_trades") or -1)
|
|
if mode_summary
|
|
else False
|
|
),
|
|
"best_has_aug21": any(
|
|
str(t.get("sell_time") or "").startswith("20260821") for t in best_tr
|
|
),
|
|
"slim_keys_ok": (
|
|
all(
|
|
set(t.keys())
|
|
>= {"code", "buy_time", "sell_time", "pnl", "sell_reason"}
|
|
for t in best_tr[:3]
|
|
)
|
|
if best_tr
|
|
else False
|
|
),
|
|
}
|
|
out_data["smoke_checks"] = checks
|
|
out_data["smoke_ok"] = all(
|
|
[
|
|
checks["best_trades_n"] > 0,
|
|
checks["best_trades_eq_reeval_tr"],
|
|
checks["mode_trades_n"] > 0,
|
|
checks["mode_trades_eq_summary_tr"],
|
|
checks["slim_keys_ok"],
|
|
]
|
|
)
|
|
|
|
out = Path(args.out)
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
out.write_text(json.dumps(out_data, ensure_ascii=False, indent=2))
|
|
print("WROTE", out, flush=True)
|
|
print("CHECKS", json.dumps(checks, ensure_ascii=False), flush=True)
|
|
print("SMOKE_OK", out_data["smoke_ok"], flush=True)
|
|
print(
|
|
"BEST_REEVAL",
|
|
json.dumps(reeval, ensure_ascii=False),
|
|
flush=True,
|
|
)
|
|
return 0 if out_data["smoke_ok"] else 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except Exception:
|
|
traceback.print_exc()
|
|
raise SystemExit(1)
|