Files
kis_bot/scripts/append_tail_optuna_compare.py
2026-07-30 18:05:07 +09:00

181 lines
5.7 KiB
Python

#!/usr/bin/env python3
"""Optuna 결과 JSON vs 현재 DB — 백테 비교표를 로그 파일 끝에 append (DB 미저장)."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from database import TradeDB
from kis_trader.backtest import tail_backtest_common as tbc
from kis_trader.engine import tail_engine as te
COMPARE_KEYS = [
"entry_mode",
"min_drop_rate",
"min_recovery_ratio",
"tail_ratio_min",
"tail_pct_min",
"max_rec_3m",
"shoulder_min_high",
"shoulder_cut_pct",
"stop_atr_mult",
"target_atr_mult",
"atr_sl_min_pct",
"atr_sl_max_pct",
"atr_tp_min_pct",
"atr_tp_max_pct",
"max_daily_change",
"max_loss_krw",
"limit_atr_mult",
"tail_vol_mult",
"tail_vol_win",
"ratchet_tiers",
"rsi_threshold",
"cooldown_min",
"bar_chg_min_pct",
"bar_chg_max_pct",
"symbol_daily_loss_limit_krw",
"symbol_daily_loss_limit_pct",
"reentry_min_edge_krw",
"reentry_require_nonneg",
"pattern_pin",
"pattern_engulfing",
"pattern_piercing",
"max_daily",
"max_spread_pct",
"min_bid_ask_ratio",
]
def _same(a, b) -> bool:
if a == b:
return True
try:
return abs(float(a) - float(b)) < 1e-9
except Exception:
return str(a) == str(b)
def _run(candles_by_code, universe, base, port, fee, tax, slot, budget, meta, overrides):
params = dict(base)
tbc.merge_tail_portfolio_into_params(params, port)
params.update(overrides or {})
trades = tbc.run_tail_backtest_web_aligned(
candles_by_code,
params,
universe,
slot_money=slot,
fee_rate=fee,
sell_tax=tax,
total_budget_krw=budget,
meta_out=dict(meta),
)
wins = [t for t in trades if float(t.get("pnl") or 0) > 0]
pnl = sum(float(t.get("pnl") or 0) for t in trades)
wr = (len(wins) / len(trades) * 100.0) if trades else 0.0
return {
"trades": len(trades),
"wins": len(wins),
"wr": wr,
"pnl": pnl,
"params": {k: params.get(k) for k in COMPARE_KEYS},
}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--json", required=True, help="optuna_tail_*.json 경로")
ap.add_argument("--log", required=True, help="append 대상 로그 경로")
ap.add_argument("--date", default="2026-07-10", help="백테 일자 YYYY-MM-DD")
args = ap.parse_args()
json_path = Path(args.json)
log_path = Path(args.log)
data = json.loads(json_path.read_text(encoding="utf-8"))
best = (data.get("results") or [None])[0]
if not best:
msg = "❌ Optuna results 비어 있음 — 비교 스킵\n"
with log_path.open("a", encoding="utf-8") as f:
f.write(msg)
print(msg, end="")
return 1
best_params = best.get("params") or {}
day = args.date
start_key = day.replace("-", "") + "0000"
end_key = day.replace("-", "") + "2359"
start_ymd, end_ymd = start_key[:8], end_key[:8]
db = TradeDB()
base = te.get_tail_defaults_from_db(db)
universe, src, n_slots, _ = tbc.resolve_tail_universe(
start_ymd, end_ymd, use_saved_history=True, strategy_id="SHORT",
)
tf = int(base.get("timeframe") or 3)
rsi = int(base.get("rsi_period") or 14)
candles_by_code, _, _ = tbc.load_tail_candles_by_code(
db, start_key, end_key, tf, rsi_period=rsi,
)
row = db.conn.execute("SELECT * FROM env_config ORDER BY id DESC LIMIT 1").fetchone()
r = dict(row) if row else {}
fee, tax, _ = tbc.fee_and_slot_from_env_row(r)
port = tbc.resolve_tail_portfolio_params(r, base)
slot = float(port["slot_money"])
budget = float(port["total_budget_krw"])
meta = {"db": db, "start_key": start_key, "end_key": end_key}
cur = _run(candles_by_code, universe, base, port, fee, tax, slot, budget, meta, {})
bst = _run(
candles_by_code, universe, base, port, fee, tax, slot, budget, meta, best_params,
)
delta = bst["pnl"] - cur["pnl"]
trial_no = best.get("optuna_trial_number") or data.get("optuna_best_trial_number")
mode = data.get("mode") or "?"
elapsed = data.get("elapsed_sec")
lines = []
lines.append("")
lines.append("=" * 72)
lines.append(f"[COMPARE] CURRENT_DB vs OPTUNA_{mode.upper()}_BEST | {day} | DB미저장")
lines.append("=" * 72)
lines.append(f"json={json_path}")
lines.append(f"universe={src} slots={n_slots} | trial=#{trial_no} elapsed={elapsed}s")
lines.append("")
lines.append("| 구분 | 거래 | 승 | 승률 | 손익 |")
lines.append("|------|------|----|------|------|")
lines.append(
f"| CURRENT_DB | {cur['trades']} | {cur['wins']} | {cur['wr']:.1f}% | {cur['pnl']:+,.0f} |"
)
lines.append(
f"| OPTUNA_BEST | {bst['trades']} | {bst['wins']} | {bst['wr']:.1f}% | {bst['pnl']:+,.0f} |"
)
lines.append(f"| Δ(best-current) | | | | {delta:+,.0f} |")
lines.append("")
lines.append("| 파라미터 | CURRENT_DB | OPTUNA_BEST | diff |")
lines.append("|----------|------------|--------------|------|")
for k in COMPARE_KEYS:
a = cur["params"].get(k)
b = bst["params"].get(k)
mark = "" if _same(a, b) else "<<"
lines.append(f"| {k} | {a} | {b} | {mark} |")
lines.append("")
lines.append("DB 저장 없음 (--apply-best 미사용)")
lines.append("=" * 72)
lines.append("")
text = "\n".join(lines)
with log_path.open("a", encoding="utf-8") as f:
f.write(text)
print(text, end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())