# 정합성 > **최종안 (2026-07-17)** — 실매 · 웹백테 · Optuna가 **같은 분봉(OHLCV) 진실**을 쓰게 하는 규칙. > 신호/진입 바 오프셋(±1) 땜빵은 **금지**. 봉이 확정 후에도 커지는 것이 근본 원인이다. 관련: [CANDLE_FLOW.md](./CANDLE_FLOW.md) · [SCALP_BACKTEST_VS_LIVE.md](./SCALP_BACKTEST_VS_LIVE.md) · [BACKTEST_ALIGNMENT_FINAL.md](./BACKTEST_ALIGNMENT_FINAL.md) --- ## 0. 한 줄 원칙 ``` 실매가 확정한 1분봉(그 순간의 OHLCV) = DB에 동결 = 웹백테/Optuna가 읽는 봉 ``` - **틱** = 체결가(진입/청산 슬리피지) - **분봉** = 매수 여부(RSI·거래량배수·평균 등) - **EOD** = 포지션 장마감 청산(≈15:25). **봉 동결이 아님.** ### 0.1 누가 무엇을 읽나 (오해 방지) | 곳 | 읽는 것 | 아님 | |----|---------|------| | **실매(장중)** | WS로 막 확정된 봉 → RAM + DB에 **첫 INSERT** | — | | **웹백테 · 파람/Optuna** | DB **`ws_candles`(+`ws_ticks`)** 과거 재생 | Optuna가 **실시간 WS에 붙지 않음** | | **아침 기동** | 같은 DB 로드 + 없는 분만 INSERT | “새벽 INSERT만 보는” 전용 분기 **없음** | 대화에서 쓰인 “실매가 확정한 **실시간** 분봉” = **장중 확정 순간의 숫자**를 뜻함. → 파람을 실시간 피드로 바꾸라는 뜻이 **아님**. → 고칠 코드 = **존재 시 UPDATE 금지**뿐. 파람/아침용 봉 소스를 둘로 나누지 않음. --- ## 1. 잠긴 매매 규칙 (변경 금지) | 항목 | 규칙 | |------|------| | 신호 봉 | **T−1 확정봉** (`live_backtest_align=True`) | | 진입 봉 | **T 확정봉** | | 유니버스 | 초 단위 `event_time` — 분 슬롯 ±1 해킹 금지 | | HTS | SCAN 후보 참고. `*_SKIP_HTS_SCAN_DUPES` 기본 **false** 유지 | | 차트봉 | 매매·백테·Optuna 경로에 사용하지 않음 (`ws_candles` + `ws_ticks`) | --- ## 2. 근본 원인 (측정, 2026-07-16) | 현상 | 수치/관찰 | 의미 | |------|-----------|------| | 장중 확정 후 재기록 | 봉 마감 +5분 이후 `updated_at` ≈ **76%** | 갭보정 REST가 기존 봉 **UPDATE**(큰 volume 우선) | | 새벽/매도 후 백필 | 다음날 `updated_at` ≈ **3.8%** (00:48대), 매도 종목과 100% 겹침 | `POST_SELL_CANDLE_BACKFILL`이 hold 구간 UPSERT | | 갭 구멍 INSERT | ENTER 시 결측분 흔함 | 웜업용 — **INSERT는 유지**, 덮어쓰기만 문제 | | 샘표류(007540) | 실매 vol_avg≈1328 vs DB≈634 | 같은 공식, **lookback 시리즈가 다름** | **정합을 깨는 본체 = 확정 후 UPDATE.** INSERT(구멍 메우기)가 아니다. --- ## 3. 최종 설계: Freeze-on-confirm ### 3.1 규칙 1. **행이 이미 있으면 OHLCV·RSI 덮어쓰기 금지** (RAM `merge_confirmed_bars` + DB upsert + 매도/보유 백필 동일). 2. **없을 때만 INSERT** — 갭 구멍·웜업 유지. 3. 진행 중(미확정) 버킷은 기존처럼 confirmed에 넣지 않음 (`WS_GAP_FILL_SKIP_INCOMPLETE_BUCKET`). 4. (옵션, 후속) 거래량을 틱 합으로 통일 — 1번 안정화 후. ### 3.2 수정 대상 (구현 시) | 경로 | 현재 | 목표 | |------|------|------| | `kis_ws.merge_confirmed_bars` | 기존 봉 + `new_vol > old_vol` → upsert | **존재 시 skip** | | `kis_ws` DB batch / `_flush_batch` | `ON DUPLICATE KEY UPDATE` volume 등 덮음 | **존재 시 no-op** (또는 INSERT IGNORE / 조건부) | | `post_sell_candle_backfill` `_INSERT_SQL` | `volume=IF(VALUES>volume…)` | **존재 시 skip**, 없는 분만 INSERT | | 기타 REST/키움 갭보정 → merge | 위 merge 경유 | merge 규칙만 바꿔도 대부분 흡수 | ### 3.3 하지 않을 것 - 09:05 시작·새벽 수집 전면 중지만으로 “해결” 선언 (보조책은 가능, 본체 아님) - 신호/유니버스 ±1분 보정 - “EOD 했으니 당일 봉만 보면 된다” (EOD≠봉동결; lookback·전일시가 필요) - 매수 직전 REST 재조회로 “안정화 체크”를 본치료로 쓰기 (느림·429·나중에 또 덮이면 무의미) ### 3.4 운영 보조 (선택, 본치료 아님) | 아이디어 | 기대 | 한계 | |----------|------|------| | 09:05 기동 (조건식·봉 정리 후) | 장초 혼선 ↓ | 장중 UPDATE·새벽 백필 미해결 | | 새벽/장전 **덮어쓰기** 금지 | freeze와 동일 방향 | 수집 자체 금지가 아니라 INSERT only | | 실시간 “봉 안정” 폴링 | 체감용 | freeze 없으면 DB는 결국 갈라짐 | --- ## 4. 구현 순서 (승인 후) 1. **존재 시 덮어쓰기 금지** `merge_confirmed_bars` / DB upsert / 백필 — 공통 규칙. 2. **스모크** 확정 직후 volume vs 1시간 뒤 · 다음날 — **동일**해야 함. 3. **7/16 샘표류 대조** vol 평균·PASS/FAIL이 실매 저널과 같은지 (웹백테 동일 파라미터). 코드 변경 전: 핵심 매매 로직이 아닌 **저장/병합 계층** 패치. 시그널 T−1/T·청산식은 이 작업에서 건드리지 않음. --- ## 5. 검증 체크리스트 - [ ] 확정 봉 DB row: 이후 REST/백필이 volume을 키우지 않음 - [ ] 결측 분은 여전히 INSERT로 채워짐 (웜업 0건 폭주 없음) - [ ] 재시작 후 RAM 재로드 → 확정분과 DB 일치 - [ ] 갭보정 로그: `update=` 가 0에 수렴(또는 skip 카운트), `insert=` 만 정상 - [ ] 전략 2개 이상 경로 스모크 (예: SCALP + TAIL) — 공유 `ws_candles` 부작용 없음 - [ ] 웹백테 / Optuna: 동일 `ws_candles` → 실매 저널과 PASS·지표 근접 - [ ] `POST_SELL_CANDLE_BACKFILL` 켠 상태에서도 hold 구간 **구멍만** 채움 --- ## 6. 영향 범위 분류 | 구분 | 영향 | |------|------| | **실매** | RAM/DB에 남는 확정봉이 “첫 확정값”으로 고정 → 이후 매수 판정 입력 안정 | | **웹백테·Optuna** | 같은 DB를 읽으므로 실매와 입력 정렬 (엔진식 변경 없음) | | **과거 DB** | 이미 덮여 커진 봉은 자동 복구 안 됨. 필요 시 해당일 재수집·재백테는 별도 | --- ## 7. 승인 상태 | 항목 | 상태 | |------|------| | 방향: freeze-on-confirm / INSERT only | **사용자 승인·구현 진행 (2026-07-17)** | | env | `WS_CANDLE_FREEZE_ON_CONFIRM` 기본 **true** (false=레거시 덮어쓰기) | | 구현 | `kis_ws` merge/confirm/DB flush · DB시드 · `post_sell_candle_backfill` · `fill_kiwoom_candles` | | 재시작 | `db_seed=N` 로그 = DB 확정봉을 RAM에 시드 (REST로 안 덮음) | 첫 패치: **존재 시 UPDATE 금지** (확정행 OHLCV 동결). 미확정→확정 갱신은 허용. 재시작 시 RAM이 비면 REST가 “첫 삽입”처럼 보이므로, **DB에 이미 있으면 DB값으로만 RAM 시드**. --- ## 8. 다음에 할 일 (쉬운 말 · 우선순위) > 코드(freeze)는 이미 켜져 있다. 아래는 **새 기능이 아니라 “잘 됐는지 확인”** 순서다. ### 1순위 — 깨끗한 장일 하루만 검증 **말:** 봉이 끝난 직후 적힌 거래량(volume)이, 한 시간 뒤·다음날에도 **그대로**여야 한다. 중간에 REST·갭보정·매도백필이 숫자를 키우면 실패. **보는 법:** - 확정 직후 volume vs 1시간 뒤 · 다음날 → **같아야 함** - 갭보정 로그: `freeze_skip` 있고, `update≈0`, **`insert`만** 정상 (구멍 메우기) ### 2순위 — 웹백테 1회 (같은 날 · 같은 파라미터) **말:** 그날 실매가 본 봉으로, 웹 백테도 같은 시험을 한 번 돌려 본다. PASS/지표가 실매 저널과 **크게 안 벌어지면** OK. **주의:** 이미 덮여 커진 날(예: **7/15–16**)은 참고만. **정합 합격 기준으로 쓰지 말 것.** ### 3순위 — 이 문서 §5 체크리스트 닫기 위 1·2가 통과하면 §5 미체크 칸을 체크한다. 그때 “정합 검증 완료”라고 말해도 된다. ### 선택(후속) — 틱 합으로 volume 통일 freeze가 안정된 **뒤에만**. 지금은 필수 아님. ### 안 하는 것 (여기선 안내만 — 강제 금지는 `.cursorrules`) - 신호/진입을 ±1분으로 땜빵하지 않는다 - freeze를 끄거나 `*_SKIP_HTS_SCAN_DUPES`를 true로 바꾸지 않는다