옵투나 오류수정정

This commit is contained in:
Your Name
2026-08-21 20:18:24 +09:00
parent 0780b2cdd0
commit 3eaa3b61df
14 changed files with 370 additions and 106 deletions

View File

@@ -194,9 +194,9 @@ def inject_momentum_rest_warmup_memory(
"""
DB 전일봉이 없을 때 키움 ka10080 REST를 종목당 호출해 **메모리에만** prepend.
- 1차: MOMENTUM_BACKTEST_REST_WARMUP_BARS (기본 700)
- 전일 종가 미확보 시에만 2차: MOMENTUM_BACKTEST_REST_WARMUP_BARS_RETRY (기본 1500)
- DB INSERT 없음. 성공 prefix·영구실패는 프로세스 캐시 (Optuna trial 재조회·로그 스팸 방지).
- 사다리 최대 4단(실패 종목만 다음 단): BARS → RETRY → BARS_3 → BARS_4
- 성공 prefix: 프로세스 RAM + `bt_rest_warmup_cache` (다른 PC 공유)
- 실패는 DB 미저장(재시도 가능). 프로세스 영구실패는 동일 런 내 재호출 방지.
"""
from kis_trader.utils.env import get_env_bool, get_env_float, get_env_int
from kis_trader.utils.logger import get_logger
@@ -213,6 +213,7 @@ def inject_momentum_rest_warmup_memory(
if len(ps) < 8 or not candles_by_code:
return stats
period_day = ps[:8]
use_db_cache = get_env_bool("MOMENTUM_BACKTEST_REST_WARMUP_DB_CACHE", True)
target: Set[str]
if universe_by_slot:
@@ -238,15 +239,17 @@ def inject_momentum_rest_warmup_memory(
if max_codes > 0:
need_codes = need_codes[:max_codes]
n_bars = max(
50,
int(get_env_int("MOMENTUM_BACKTEST_REST_WARMUP_BARS", 700)),
)
# 전일(직전 세션) 종가가 1차에 안 잡힐 때만 — 평소엔 700만
n_retry = max(
n_bars,
int(get_env_int("MOMENTUM_BACKTEST_REST_WARMUP_BARS_RETRY", 1500)),
)
n1 = max(50, int(get_env_int("MOMENTUM_BACKTEST_REST_WARMUP_BARS", 700)))
n2 = max(n1, int(get_env_int("MOMENTUM_BACKTEST_REST_WARMUP_BARS_RETRY", 1500)))
n3 = max(n2, int(get_env_int("MOMENTUM_BACKTEST_REST_WARMUP_BARS_3", 2500)))
n4 = max(n3, int(get_env_int("MOMENTUM_BACKTEST_REST_WARMUP_BARS_4", 4000)))
ladder: List[int] = []
seen_n: Set[int] = set()
for n in (n1, n2, n3, n4):
if n not in seen_n:
seen_n.add(n)
ladder.append(n)
ladder = ladder[:4]
sleep_sec = float(get_env_float("MOMENTUM_BACKTEST_REST_SLEEP_SEC", 0.25))
kw_key, kw_secret, is_mock = _kiwoom_gap_credentials()
if not kw_key or not kw_secret:
@@ -256,15 +259,14 @@ def inject_momentum_rest_warmup_memory(
from kis_trader.ws.kis_ws import get_kiwoom_candles_df
# 전부 캐시 hit면 Optuna trial 경로에서 INFO 스팸 금지
will_fetch = any(
(c, period_day) not in _REST_WARMUP_PREFIX_CACHE for c in need_codes
)
if will_fetch:
log.info(
"📡 모멘텀 REST 웜업(메모리): 전일봉 부족 %d종목 · ka10080 n=%d"
" (실패 시 n=%d 1회 재시도, DB 미기록)",
len(need_codes), n_bars, n_retry,
"📡 모멘텀 REST 웜업(메모리): 전일봉 부족 %d종목 · 사다리 n=%s"
" (실패 시만 다음 단, DB캐시=%s)",
len(need_codes), ladder, "ON" if use_db_cache else "OFF",
)
for i, code in enumerate(need_codes):
@@ -274,58 +276,70 @@ def inject_momentum_rest_warmup_memory(
stats["fail"] += 1
continue
cache_key = (code, period_day)
prefix: List[Dict[str, Any]] = []
cached = _REST_WARMUP_PREFIX_CACHE.get(cache_key)
did_network = False
if cached is not None:
stats["cache_hit"] += 1
prefix = [dict(r) for r in cached]
else:
did_network = True
elif use_db_cache:
try:
df = get_kiwoom_candles_df(
code, 1, kw_key, kw_secret, is_mock=is_mock, n=n_bars,
)
except Exception as e:
log.warning("⚠️ REST 웜업 실패 %s: %s", code, e)
_REST_WARMUP_PERM_FAIL.add(cache_key)
stats["fail"] += 1
continue
if df is None or getattr(df, "empty", True):
_REST_WARMUP_PERM_FAIL.add(cache_key)
stats["fail"] += 1
continue
try:
prefix = _rest_df_to_prefix(df, rows, ps)
except Exception as e:
log.warning("⚠️ REST 웜업 파싱 실패 %s: %s", code, e)
_REST_WARMUP_PERM_FAIL.add(cache_key)
stats["fail"] += 1
continue
from kis_trader.backtest.bt_rest_warmup_cache import load_rest_warmup_prefix
hit = load_rest_warmup_prefix(code, period_day=period_day, market="KR")
except Exception:
hit = None
if hit:
db_rows, _n_used = hit
prefix = [dict(r) for r in db_rows]
_REST_WARMUP_PREFIX_CACHE[cache_key] = [dict(r) for r in prefix]
stats["cache_hit"] += 1
# 1차로 전일 종가 미확보 → 봉 수 늘려 1회만 재시도 (중간 거래일 0봉 등)
if (not prefix or not _momentum_rows_have_prev_day(prefix, period_day)) and n_retry > n_bars:
stats["retry"] += 1
log.info(
"📡 REST 웜업 재시도 %s: n=%d → n=%d (전일 종가 미확보)",
code, n_bars, n_retry,
)
if not prefix:
for tier_i, n_try in enumerate(ladder):
if tier_i > 0:
stats["retry"] += 1
log.info(
"📡 REST 웜업 재시도 %s: n=%d → n=%d (전일 종가 미확보 · %d/%d단)",
code, ladder[tier_i - 1], n_try, tier_i + 1, len(ladder),
)
try:
df2 = get_kiwoom_candles_df(
code, 1, kw_key, kw_secret, is_mock=is_mock, n=n_retry,
df = get_kiwoom_candles_df(
code, 1, kw_key, kw_secret, is_mock=is_mock, n=int(n_try),
)
except Exception as e:
log.warning("⚠️ REST 웜업 재시도 실패 %s: %s", code, e)
df2 = None
if df2 is not None and not getattr(df2, "empty", True):
try:
prefix = _rest_df_to_prefix(df2, rows, ps)
except Exception as e:
log.warning("⚠️ REST 웜업 재시도 파싱 실패 %s: %s", code, e)
prefix = []
_REST_WARMUP_PREFIX_CACHE[cache_key] = [dict(r) for r in prefix]
if sleep_sec > 0 and did_network and i + 1 < len(need_codes):
time.sleep(sleep_sec)
log.warning("⚠️ REST 웜업 실패 %s n=%s: %s", code, n_try, e)
df = None
if sleep_sec > 0 and i + 1 < len(need_codes):
time.sleep(sleep_sec)
if df is None or getattr(df, "empty", True):
continue
try:
prefix = _rest_df_to_prefix(df, rows, ps)
except Exception as e:
log.warning("⚠️ REST 웜업 파싱 실패 %s: %s", code, e)
prefix = []
if prefix and _momentum_rows_have_prev_day(prefix, period_day):
_REST_WARMUP_PREFIX_CACHE[cache_key] = [dict(r) for r in prefix]
if use_db_cache:
try:
from kis_trader.backtest.bt_rest_warmup_cache import (
save_rest_warmup_prefix,
)
save_rest_warmup_prefix(
code,
period_day=period_day,
market="KR",
n_bars=int(n_try),
prefix_rows=prefix,
)
except Exception:
pass
break
else:
# 사다리 전부 실패
_REST_WARMUP_PERM_FAIL.add(cache_key)
stats["fail"] += 1
continue
if not prefix or not _momentum_rows_have_prev_day(prefix, period_day):
_REST_WARMUP_PERM_FAIL.add(cache_key)
@@ -335,7 +349,7 @@ def inject_momentum_rest_warmup_memory(
stats["ok"] += 1
stats["bars"] += len(prefix)
if will_fetch:
if will_fetch or stats["cache_hit"] > 0:
log.info(
"✅ 모멘텀 REST 웜업 완료: ok=%d fail=%d cache=%d retry=%d bars=%d",
stats["ok"], stats["fail"], stats["cache_hit"], stats["retry"], stats["bars"],