Files
kis_trader/docs/정합성.md
Your Name 1d69c217e2 fix(정합성): 틱 lag wall-clock 정합 + 3벤더 DB 저장 스위치 통일
- feed_fallback.bar_is_garbage: 봉끝 기준 → 각 틱의 recv_ts wall-clock 기준으로 정정
  실매 RAM 3초컷과 동일 논리 → 유동성 낮은 종목 부당 스킵 해소
- candle_garbage_fallback_enabled: 기본 True 복원 (wall-clock 정정 후 안전)
- param_search_optuna·run_tail_backtest_cli: CANDLE_GARBAGE_FALLBACK·BACKTEST_USE_RUST
  강제 os.environ 세팅 제거 → DB env·CLI 플래그로만 관리 (UI 존중)
- WS_TICK_DB_SAVE_LAG_CUT_ENABLED 신설 (bool, 기본 false, 3벤더 공통)
  OFF=키움/KIS/LS 모든 틱 lag 무관 전부 저장 (벤더 통계·재현·백테 정합)
  ON=lag > LIVE_FEED_FALLBACK_MAX_AGE_SEC 이면 미저장 (미래 A안)
- KIWOOM_TICK_LIVE_MAX_LAG_SEC 완전 폐기 → 위 스위치로 통일
- kiwoom_ws: _skip_persist 로직 새 스위치로 교체
- kis_ws·ls_ws: _skip_persist_kis/_ls 신규 (벤더별 상이했던 정책 통일)
- docs/정합성.md §9 신설 (문제·결정·시나리오·향후 A안 전환법)
- docs/rust_engine_parity_port_plan.md (신규 설계)

실매 스모크: logs/test_live_execution_validation_20260906_191845.log
  → 최종: 통과 · 👑 완결
브라우저 검증: http://192.168.0.149:5050/#liveconfig → 새 스위치 노출, JS 오류 없음

영향: 실매(DB 저장 정책 통일, RAM 컷 변경 없음) + 백테/Optuna(실매 정합)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-06 19:23:01 +09:00

14 KiB
Raw Blame History

정합성

최종안 (2026-07-17) — 실매 · 웹백테 · Optuna가 같은 분봉(OHLCV) 진실을 쓰게 하는 규칙.
신호/진입 바 오프셋(±1) 땜빵은 금지. 봉이 확정 후에도 커지는 것이 근본 원인이다.

관련: CANDLE_FLOW.md · SCALP_BACKTEST_VS_LIVE.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 금지뿐. 파람/아침용 봉 소스를 둘로 나누지 않음.

0.2 증권사별 저장 · 읽기 (2026-08-14)

ws_candles.source = 증권사(kiwoom|kis), channel = ws|rest|rollup.
UNIQUE = (code, timeframe, candle_time, source, channel).

쓰기 규칙
키움/KIS WS 틱 분봉 source=증권사, channel=ws
갭보정 항상 키움 ka10080source=kiwoom, channel=rest. KIS REST 페이지 갭보정 사용 금지
1M→nM 롤업 source=kiwoom, channel=rollup
영구구독 KR LS WS → ls_ws_candles 만. ws_candles에 넣지 않음. 자동 갭보정 제외. 탭 「확정봉 가져오기」=키움 REST → ls_ws_candles INSERT IGNORE

읽기(실매 RAM = Optuna bt_candle_source): 틱 메인 WS 한 칸, 없으면 kiwoom+rest, 그다음 롤업.
CANDLE_SOURCE=kis 여도 구멍은 키움 REST. 키움 REST로 KIS WS 행을 덮지 않음.

영구구독 마스터 PERMANENT_SUBSCRIBE_ENABLED(기본 true): OFF면 행 유지, KR LS / US 해외 WS 구독만 안 함.

0.3 호가필터 구멍 ≠ 봉 ±1 (혼동 금지)

봉 정합(본 문서)과 별개. 진입 호가필터가 저장 TTL로 None이 되면 필터 ON인데 통과한다.
고치는 키: WS_ORDERBOOK_FILTER_MAX_AGE_SEC(기본 0=마지막 RAM). 저장은 WS_ORDERBOOK_TICK_MAX_AGE_SEC.
사진 한 장도 없으면 WS_ORDERBOOK_FILTER_REJECT_IF_EMPTY(기본 true)로 안 삼.
상세 docs/호가.md. ±1봉 보정으로 맞추지 말 것.


1. 잠긴 매매 규칙 (변경 금지)

항목 규칙
신호 봉 T1 확정봉 (live_backtest_align=True)
진입 봉 T 확정봉
유니버스 실매=키움 RAM 푸시/팝. 백테=target_candidates_history 완성 스냅샷 재생 (event_time 마이크로초). 분 슬롯 ±1 해킹 금지
HTS SCAN 후보 참고. *_SKIP_HTS_SCAN_DUPES 기본 false 유지
차트봉 매매·백테·Optuna 경로에 사용하지 않음 (ws_candles + ws_ticks)

2. 근본 원인 (측정, 2026-07-16)

현상 수치/관찰 의미
장중 확정 후 재기록 봉 마감 +5분 이후 updated_at76% 갭보정 REST가 기존 봉 UPDATE(큰 volume 우선)
새벽/매도 후 백필 다음날 updated_at3.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이 실매 저널과 같은지 (웹백테 동일 파라미터).

코드 변경 전: 핵심 매매 로직이 아닌 저장/병합 계층 패치.
시그널 T1/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/1516)은 참고만. 정합 합격 기준으로 쓰지 말 것.

3순위 — 이 문서 §5 체크리스트 닫기

위 1·2가 통과하면 §5 미체크 칸을 체크한다.
그때 “정합 검증 완료”라고 말해도 된다.

선택(후속) — 틱 합으로 volume 통일

freeze가 안정된 뒤에만. 지금은 필수 아님.

안 하는 것 (여기선 안내만 — 강제 금지는 .cursorrules)

  • 신호/진입을 ±1분으로 땜빵하지 않는다
  • freeze를 끄거나 *_SKIP_HTS_SCAN_DUPES를 true로 바꾸지 않는다

9. 틱 lag 정합 (2026-09-06 · 실매 RAM ↔ 백테 wall-clock 일치)

이전 사고: 옵투나에서 12~22% 봉이 "쓰레기" 로 스킵. 원인은 3가지가 겹침.

9.1 문제 (근본원인 요약)

# 코드 문제
1 bar_is_garbage봉끝 시각(bar_end) 을 기준으로 lag = bar_end - tick_time 실매 RAM은 wall-clock - tick_time 로 판정 → 백테는 봉 시작 근처 유동성 낮은 종목 부당 스킵
2 param_search_optuna.py 가 import 시점에 CANDLE_GARBAGE_FALLBACK=False 강제 세팅 사용자가 UI에서 켜도 무시
3 벤더별 DB 저장 정책 제각각 (kiwoom: KIWOOM_TICK_LIVE_MAX_LAG_SEC>0 이면 컷 · kis: 무조건 저장 · ls: RAM skip 시 저장 안 함) 통계·재현·백테 정합이 벤더마다 다름

9.2 결정 (사용자 승인)

  1. bar_is_garbage = wall-clock 정합 — 각 틱의 recv_ts 를 기준으로 lag = recv_ts - tick_time 계산.
    → 실매 RAM 3초컷과 동일 논리. 유동성 낮은 종목 부당 스킵 해소.
  2. DB 는 전부 수집 — 벤더별 지연 통계·재현·백테 정합용. 3벤더 공통 정책.
  3. KIWOOM_TICK_LIVE_MAX_LAG_SEC 완전 폐기 → 새 스위치 WS_TICK_DB_SAVE_LAG_CUT_ENABLED 하나로 통일.
  4. param_search_optuna.py 강제 세팅 제거 — DB env 로만 관리 (사용자 UI 존중).

9.3 새 스위치: WS_TICK_DB_SAVE_LAG_CUT_ENABLED (bool, 기본 false · 3벤더 공통)

동작 용도
false (기본) 키움/KIS/LS 모든 틱을 lag 무관 ws_ticks 전부 저장 B안 — 벤더 통계·재현·백테 정합
true lag > LIVE_FEED_FALLBACK_MAX_AGE_SEC (기본 3초) 인 틱은 DB 미저장 A안 — 통계 확신 후 저장·매매 정합 통일
  • 매매 RAM 은 항상 LIVE_FEED_FALLBACK_MAX_AGE_SEC (기본 3초) 로 컷 (변경 없음).
  • 이 스위치는 DB 저장 컷 만 제어.

9.4 코드 진실 (수정 후)

경로 동작
kis_trader/engine/feed_fallback.py::bar_is_garbage 각 틱의 recv_ts 기준 wall-clock lag. recv_ts 없으면 봉끝 폴백
kis_trader/engine/feed_fallback.py::candle_garbage_fallback_enabled 기본 True 복원 (실매 정합 논리 정정 후)
kis_trader/backtest/param_search_optuna.py CANDLE_GARBAGE_FALLBACK/BACKTEST_USE_RUST 강제 세팅 제거
kis_trader/ws/kiwoom_ws.py _skip_persist = WS_TICK_DB_SAVE_LAG_CUT_ENABLED and lag > LIVE_FEED_FALLBACK
kis_trader/ws/kis_ws.py _skip_persist_kis = 동일 로직 (기존엔 무조건 저장)
kis_trader/ws/ls_ws.py _skip_persist_ls = 동일 로직 (기존엔 skip_ram 시 저장 안 함)

9.5 Parquet 초고속 틱 로더 (백테)

kis_trader/backtest/breakout_tick_loader.py · kis_trader/utils/export_ticks_parquet.py

  • Parquet = DB 스냅샷 (export_ticks_parquet.pyws_ticks 를 필터 없이 그대로 export).
  • 스위치 OFF (기본) 상태 → DB 는 전부 저장 → Parquet 도 전부 포함 → DB 로드와 완전 동일.
  • 로드 후 bar_is_garbage (wall-clock recv_ts 기준) 가 동일하게 적용 → 실매 정합.
  • Parquet 로더는 "로드 경로 초고속화"만 이고, 쓰레기 스킵 판정은 후단(candle_series) 에서 동일.

9.6 시나리오 재검증 (수정 후)

시나리오 이전 (bar_end 기준) 이후 (wall-clock 기준) 실매 RAM
유동성 낮은 종목: 09:00:05 유일 틱 lag=55s → 쓰레기 스킵 lag ≈ 0s (recv_ts 즉시) → 유효 유효
WS 재연결: 09:00:00 틱을 09:00:10 수신 lag=59s → 스킵 recv_ts-tick_time=10s → 스킵 스킵 (RAM 3초컷)
정상 실시간 틱 lag < 60s → 유효 lag ≈ 0s → 유효 유효

결론: 백테 = 실매 정합 완료.

9.7 향후 A안 (통계 확신 후)

  1. feed_collect_stats 탭에서 벤더별 lag > 3s 비율 확인 (수 주간).
  2. 확신 서면 UI 에서 WS_TICK_DB_SAVE_LAG_CUT_ENABLED = true 로 전환.
  3. 이후 ws_ticks 는 lag > 3s 인 틱 제외되어 저장 (매매 RAM 컷과 완전 일치).
  4. bar_is_garbage 는 그대로 유지 (안전장치).

9.8 무엇을 하지 말 것

  • 스위치를 선제적으로 true 로 바꾸지 말 것 (통계 부재 상태에서 벤더별 커버리지 손실 위험).
  • bar_is_garbage 를 다시 봉끝 기준으로 되돌리지 말 것 (실매 정합 깨짐).
  • CANDLE_GARBAGE_FALLBACK=False 로 다시 끄지 말 것 (12~22% 쓰레기는 봉끝 기준의 버그였음. wall-clock 기준으로는 훨씬 낮음).
  • param_search_optuna.pyos.environ["..."]="..." 강제 세팅 재도입 금지 (DB env 우회 → UI 무력화).