# 돌파 실매↔백테 괴리 Q&A (2026-07-16 기준) > **패치 적용 완료 (2026-07-17).** 실매 봇은 재시작하지 않음. 백테 웹만 재시작. --- ## 1번 — 웜업 넣으면 되나? **네 → 적용됨.** - env: `BREAKOUT_BACKTEST_CANDLE_WARMUP_BARS` (기본 **50**, DB 시드) - `prepend_breakout_candle_warmup` — 기간 시작 전 1분봉 prepend - 포트폴리오 `all_times`는 `_backtest_period_start_key` 이후만 (전일 시계 오염 방지) - 모멘텀 `MOMENTUM_BACKTEST_CANDLE_WARMUP_BARS`와 동일 계열 --- ## 2번 — 유니버스 sticky/grace **실매 따라가기 → 적용됨.** ### 동작 - 공통 `resolve_universe_exit_debounce_sec()` → 기본값 = `CONDITION_EXIT_GRACE_SEC`(운영 **60**, 기존 120→2026-07-17 축소) - 돌파/꼬리/모멘텀 타임라인 `debounce_sec`에 연결 - 전략별 오버라이드: `BREAKOUT_UNIVERSE_EXIT_DEBOUNCE_SEC` / `TAIL_*` / `MOMENTUM_*` (있으면 우선, `0`=OFF) ### 09:07 1종 스냅샷 점검 결과 | event_time | n | |---|---| | 03:16 | 22 | | 09:07 | **1** (248170만) | | 09:11 | 2 | - history는 **effective 유니버스 저장** 설계 → 09:07에 1종이면 그 tick의 live effective도 1종으로 보는 게 맞음 (저장이 grace를 빼먹은 버그로 단정하지 않음). 새벽 스냅샷 후 장초 첫 변동·콜드스타트 가능성이 큼. - 디바운스 ON 시: 09:07 스냅샷에서도 overnight 종목을 **60초** 유지 → 이후 신규 스냅샷 기준으로 정리. 실매 EXIT grace와 같은 전환 규칙. ### 영향 범위 - **백테 유니버스 재생** (돌파·꼬리·모멘텀 등 타임라인 사용 전략). 실매 주문 로직 자체는 변경 없음. - 망가뜨리는 패치가 아니라 **실매 grace 정합** 쪽. 백테 거래수/PnL은 실매에 가까워지도록 **바뀔 수 있음**. --- ## 3번 — 슬롯 늘려서 테스트 **사용자 검증용 (코드 변경 없음).** 총한도/동시보유↑로 슬롯 경쟁 효과 확인. --- ## 4번 — 미청산 flatten / EOD **적용됨 (돌파·모멘텀·RANGE_BREAK).** - 공통 `flatten_remaining_portfolio_trades` — 루프 끝 미청산을 **마지막 확정봉 종가**로 장부 기록 - `eod_enabled`면 `sell_reason=eod`, 아니면 `bt_flatten` - skip_stats: `bt_flatten_count` - 모멘텀: 웜업은 기존, **flatten은 이번에 추가** (이전 “미완” 해소) --- ## (부수) invest_cap 이중 % 스케일 **적용됨 (돌파·RANGE_BREAK).** - `_resolve_breakout_sl_pct_ui`: `stop_loss_pct`(비율) → UI% ; `sl_pct`만 있으면 UI%/비율 자동 판별 - `breakout_invest_amount_krw`에 **UI% 한 번만** 전달 (×100 이중 제거) --- ## 패치 상태 | 순위 | 항목 | 상태 | |------|------|------| | 1 | 돌파 백테 **전일 봉 웜업** | **완료** | | 2 | 유니버스 **sticky/grace 디바운스** + 09:07 점검 | **완료** | | 3 | 슬롯/한도 검증용 백테 | 사용자 | | 4 | 백테 종료 **미청산 flatten** | **완료** (돌파·모멘텀·레인지) | | 부수 | `invest_cap` `%` 이중 스케일 | **완료** | --- ## 관련 파일 - 웜업·디바운스: `kis_trader/backtest/breakout_backtest_common.py` - 공통 디바운스: `kis_trader/backtest/universe_timeline.py` (`resolve_universe_exit_debounce_sec`) - flatten: `kis_trader/backtest/backtest_portfolio_common.py` - 포트폴리오: `breakout_portfolio_backtest.py`, `momentum_portfolio_backtest.py`, `range_break_portfolio_backtest.py` - 꼬리 디바운스: `tail_backtest_common.py` - env 키: `database.py` (`BREAKOUT_BACKTEST_CANDLE_WARMUP_BARS` 등) --- ## 검증 - `py_compile` OK - 스모크: debounce(03:16→09:07 유지→09:11 정리) / invest_cap(30만) / flatten(`eod`) → `SMOKE_OK` - DB: `BREAKOUT_BACKTEST_CANDLE_WARMUP_BARS=50` 시드, config 컬럼 추가 - `kis_backtest_web.service` 재시작 (`active` + HTTP 200) - 브라우저: `http://192.168.0.149:5050/` → **돌파매매 백테스트** 탭 클릭, 폼·「백테스트 실행」버튼 정상 표시 ### 잔여 / 다음 확인 | 순위 | 항목 | 상태 | |------|------|------| | 다음 | **2번 검증** — 장초 이노테나·한울 `직전봉약세` | **근본원인 확정 + 갭보정 진행분 제외 적용** (봇 재시작 시 반영) | | 후보 | 1번 A — 마지막 봉 경과 시 포트폴리오 루프에서 **즉시** 슬롯 해제 (전 전략 공통) | 대기 — 저유동은 C로도 분 생성 불가 시 필요 | | 진행 | **1번 C — 판 뒤 1회 REST 백필** | **적용** (아래) | | — | 3번 슬롯 경쟁 | 패스 | ### 1번 C 적용 (2026-07-17) - 모듈: `kis_trader/engine/post_sell_candle_backfill.py` - 실매 훅: `order_manager._finalize_sell_fill` → 매도 후 **비동기 1회** (시세=키움 **실키**) - env: `POST_SELL_CANDLE_BACKFILL` (기본 true), `POST_SELL_CANDLE_ROLLUP_3M`, sleep 1~3초 - 즉시 백필 CLI: `scripts/backfill_trade_candles.py --like '2026-07-16%'` - 로그: `/tmp/backfill_trade_candles_0716c.log` (및 선행 0716b) - 한계: 거래 자체가 없는 분(저유동)은 REST에도 없음 → **C만으로는 완전 메꿈 불가**, A 보완 여지 - 웹 돌파 **2026-07-16** 재백테 ↔ 실매 비교는 C 반영 후 / 장초 직전봉 수정과 병행 - 실매 봇: post-sell 훅 반영하려면 **재시작 필요** (백필 CLI는 봇 없이 완료 가능) --- ## 장초 직전봉 (이노테나·한울) — 2번 검증 결과 (2026-07-17) ### 현상 | | 이노테나 `333050` | 한울 `320000` | |--|--|--| | 실매 매수 | 09:01:09 @5350 | 09:01:42 @11590 | | 실매 로그 `prevChg` | **1.34%** | **0.61%** | | BT (DB+웜업) | `탈락-직전봉약세` **0.19%** | `탈락-직전봉약세` **-2.37%** | | `PREV_CHG_MIN` | 0.3 (실매=BT 동일) | 동일 | ### 숫자 재현 (스펙: 전전봉 종가 → 직전봉 종가 %) - 이노테나 09:00: O=5220 C=5290 / 전일 15:30 C=5280 - **전일대비 C2C** `(5290-5280)/5280` = **0.19%** ← BT 탈락 - **당일 몸통** `(5290-5220)/5220` = **1.34%** ← 실매 `prevChg`와 **일치** - 한울: 전일 15:58 C=11810 → 09:00 C=11530 → C2C **-2.37%**; 몸통 ≈ **0.70%** ≈ 실매 0.61%(RAM OHLC 미세차) ### 실매 journal 타임라인 (결정적) 1. `09:00:25` 333050 갭보정 REST 빈응답 → `09:00:34` **500봉 RAM 적재** (진행 중 **당일 09:00** 포함) 2. `09:00:34~09:01:01` 내내 `탈락-직전봉약세 prev=-1.14%` - `(5220-5280)/5280 = -1.14%` → 갭보정이 넣은 **미완성 09:00(종가≈시가 5220)** 이 확정봉으로 잡힌 상태 3. `09:01:01` `봉강제확정` 09:00 C=5290 4. `09:01:02~` `탈락-저항미돌파` (직전봉 게이트 **통과**) → `09:01:06` `BREAKOUT-B ... prevChg=1.34%` 매수 한울도 동일 패턴: 장중 `prev=-2.96%`(갭/전일) → 09:00 확정 후 저항 대기 → `prevChg=0.61%`로 매수. ### 근본원인 **갭보정(REST ka10080)이 장중에 “아직 진행 중인 당일 1분봉”을 `is_confirmed`로 RAM에 넣음.** - 실매 `check_buy` = `get_candles`(확정) + `get_current_candle`(진행) → **같은 09:00이 확정·진행 양쪽에** 있거나, 확정 체인이 **전일 종가 대신 시가(불완전봉 종가)** 를 `prev_prev`로 물음. - 그 결과 직전봉 필터가 문서/코드 스펙(전전봉→직전봉 **종가대비**)이 아니라 **당일 첫 봉 몸통%** 에 가깝게 통과. - BT는 DB 확정봉(+웜업 전일 종가)만 쓰므로 **0.19%/-2.37%로 올바르게 거름** → 장초 실매만 사는 괴리. 코드 위치: `kis_ws.merge_confirmed_bars` / `fill_gap_from_rest` (현재 분 제외 없음) + `breakout.check_buy` (`virtual = confirmed + forming`). ### 수정 방향 (승인 후 — 아직 미적용) 1. **갭보정 시 `candle_time >= 현재 진행 분` 봉은 confirmed merge 제외** (또는 `_current`만 갱신). 실매·BT 모두 전일 종가 C2C로 통일. 2. (선택) 매수 성공 로그에 `confirmed[-2/-1] candle_time/close` 남겨 재발 추적. 3. 스펙을 “직전봉 몸통%”로 바꾸려면 실매·웹·Optuna를 **한 세트**로 바꿔야 함 — 비권장(HTS/주석과 불일치). **권장: 1번(진행분 REST 제외).** 핵심 매매 임계값 변경이 아니라 인프라 정합. ### 수정 적용 (2026-07-17) - env: `WS_GAP_FILL_SKIP_INCOMPLETE_BUCKET` (기본 **true**, DB 시드) - `fill_gap_from_rest` / `merge_confirmed_bars`: 진행 중 버킷(`>=` 현재 봉시작) **insert 금지 + RAM purge** - 스모크: `scripts/smoke_candle_upsert_rollup.py` (진행분 skip/purge) - **실매 반영: `kis_trader_main` 재시작 필요** (미재시작 시 기존 프로세스 구코드)