# -*- coding: utf-8 -*- """백테 REST 전일봉 웜업 DB 캐시 — 다른 PC/동일 study 재사용. 성공분만 저장. 실패는 저장하지 않아 다른 머신·코드 개선 후 재시도 가능. """ from __future__ import annotations import json import logging from datetime import datetime from typing import Any, Dict, List, Optional, Tuple logger = logging.getLogger("kis_trader.bt_rest_warmup_cache") _TABLE = "bt_rest_warmup_cache" _ENSURED = False _DDL = """ CREATE TABLE IF NOT EXISTS bt_rest_warmup_cache ( code VARCHAR(16) NOT NULL, period_day CHAR(8) NOT NULL COMMENT '백테 구간 시작일 YYYYMMDD', market VARCHAR(8) NOT NULL DEFAULT 'KR', n_bars INT NOT NULL DEFAULT 0 COMMENT '확보에 쓴 REST 봉수', ok TINYINT NOT NULL DEFAULT 0, prefix_json LONGTEXT NULL COMMENT '[{datetime,open,high,low,close,volume},...]', updated_at DATETIME NOT NULL, PRIMARY KEY (code, period_day, market), KEY idx_bt_rest_warm_day (period_day, market) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='모멘텀 백테 REST 전일봉 웜업 공유 캐시(성공만)' """ def ensure_bt_rest_warmup_cache_table() -> None: global _ENSURED if _ENSURED: return try: from database import TradeDB db = TradeDB() db.conn.execute(_DDL) _ENSURED = True except Exception as exc: logger.warning("⚠️ bt_rest_warmup_cache 테이블 생성 실패: %s", exc) def _day_key(start_key: str) -> str: s = str(start_key or "").strip().replace("-", "")[:8] return s if len(s) == 8 and s.isdigit() else "" def load_rest_warmup_prefix( code: str, *, period_day: str, market: str = "KR", ) -> Optional[Tuple[List[Dict[str, Any]], int]]: """성공 캐시면 (prefix_rows, n_bars), 없으면 None.""" c = str(code or "").strip() day = _day_key(period_day) mkt = str(market or "KR").strip().upper() or "KR" if not c or not day: return None try: ensure_bt_rest_warmup_cache_table() from database import TradeDB cur = TradeDB().conn.execute( "SELECT ok, n_bars, prefix_json FROM bt_rest_warmup_cache " "WHERE code=%s AND period_day=%s AND market=%s LIMIT 1", (c, day, mkt), ) row = cur.fetchone() if not row: return None if int(row.get("ok") or 0) != 1: return None raw = row.get("prefix_json") or "[]" rows = json.loads(raw) if isinstance(raw, str) else (raw or []) if not isinstance(rows, list) or not rows: return None return rows, int(row.get("n_bars") or 0) except Exception as exc: logger.debug("bt_rest_warmup load miss %s: %s", c, exc) return None def save_rest_warmup_prefix( code: str, *, period_day: str, market: str, n_bars: int, prefix_rows: List[Dict[str, Any]], ) -> bool: """성공 접두만 upsert. 실패는 저장하지 않음.""" c = str(code or "").strip() day = _day_key(period_day) mkt = str(market or "KR").strip().upper() or "KR" if not c or not day or not prefix_rows: return False try: ensure_bt_rest_warmup_cache_table() from database import TradeDB payload = json.dumps(prefix_rows, ensure_ascii=False, separators=(",", ":")) now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") TradeDB().conn.execute( """ INSERT INTO bt_rest_warmup_cache (code, period_day, market, n_bars, ok, prefix_json, updated_at) VALUES (%s, %s, %s, %s, 1, %s, %s) ON DUPLICATE KEY UPDATE n_bars=VALUES(n_bars), ok=1, prefix_json=VALUES(prefix_json), updated_at=VALUES(updated_at) """, (c, day, mkt, int(n_bars or 0), payload, now), ) return True except Exception as exc: logger.warning("⚠️ bt_rest_warmup 저장 실패 %s: %s", c, exc) return False