Files
kis_bot/docs/rust_engine_parity_port_plan.md
Your Name d8ac1595c1 fix(backtest): 22.8% 쓰레기스킵 → 5.2% (bt_candle_source 로더 통일)
## 근본원인
bt_candle_source._load_ticks_by_code_bulk / _ticks_for_bar_garbage 가
자체 SELECT 로 recv_ts/tick_time_raw 미포함 → bar_is_garbage 가
_lag_sec/recv_ts 없이 봉끝 폴백 → WS 봉의 76% 대량 컷.

엔진 로더(breakout_tick_loader.load_common_ticks_by_code) 는 이미
recv_ts + _lag_sec 완비였지만 두 로더가 발산 상태 → 이번에 사고 촉발.

## 수정 (C안: 로더 통일)
- _load_ticks_by_code_bulk: breakout_tick_loader._fetch_ws_ticks_day_rows
  위임. flat 리턴은 _ingest_flat_tick_rows (신설) 로 어댑팅.
  recv_ts + _lag_sec 모두 포함 → 엔진 로더와 100% 동일 SELECT/계산.
- _ticks_for_bar_garbage: 단일 종목용으로 _load_ticks_by_code_bulk
  (codes_filter=[code]) 재사용. 별도 SELECT 제거.

## 검증
Optuna scalp TPE 10 trials · 2026-09-04 · Python 엔진:
- Before: 슬롯 63671 · 쓰레기스킵 14495 (22.8%) · hole 7.4% · kis/ws pick 11%
- After:  슬롯 63671 · 쓰레기스킵  3324 ( 5.2%) · hole 3.7% · kis/ws pick 20%

실매 스모크 (scripts/test_live_execution_validation.py): 최종 통과 · 완결
로그: logs/test_live_execution_validation_20260906_201535.log

## 문서 (rust_engine_parity_port_plan.md)
- 부록 A: A6~A11 사전 정지 작업 추가 (wall-clock 정합·로더 통일·파케이·게이팅·뱃지)
- 부록 C (신설): D안 로드맵 — Rust 세션이 현재 봉만 받고 틱/호가/프로그램매매 미전달.
  Python 정합 개선을 받아도 Rust 는 여전히 데이터 절반짜리 상태. P0 최우선 6단계.

## 룰
- CRITICAL §0 (봉 freeze · OHLC 폴백 금지) 준수
- 룰 19 (근본원인 딥다이브 · 3안 선보고) — B/C/D 안 제시 후 사용자 C안 선택
- 룰 25-1 (교차검증) — 두 로더 발산 재발 방지

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-06 20:18:11 +09:00

25 KiB
Raw Blame History

Python → Rust 엔진 완전 이식 설계안 (B안)

작성일: 2026-09-06
대상: kis_rust_src/ 백테 엔진 확장 (whipsaw · 호가 · 포트폴리오 · 틱 · 청산 우선순위)
목표: Optuna(Rust) 결과 = 웹 백테(Python) 결과 = 실매 엔진 을 1원 단위로 정합
진행: 이 대화에서는 이 문서 작성까지만. 실제 구현은 새 대화에서 이 문서 기준으로 진행.


0. 왜 필요한가 (배경)

param_search_optuna.py:44-48 이 import 시점에 BACKTEST_USE_RUST=1강제 설정하여, Optuna는 사실상 Rust 엔진으로만 돌아간다:

os.environ["BACKTEST_CANDLE_GARBAGE_OFF"] = "1"
os.environ["CANDLE_GARBAGE_FALLBACK"] = "False"
# 파이썬 엔진 Parquet 틱 로더 활성화
os.environ["BACKTEST_USE_RUST"] = "1"

문제: Rust 엔진은 실제로 OHLC 단일종목·종목별 병렬 시뮬레이터 수준. 아래 12개 항목이 Python 실매/백테와 다르므로 Optuna 랭킹 ≠ 웹 백테 결과 ≠ 실매.

  • 이로 인해 Optuna best 를 실매 DB에 적용하면 예상과 다른 엔진으로 매매 → 사고 위험.
  • CRITICAL §0 「봉 freeze / OHLC 폴백 금지 / skip_hts_scan_dupes false 유지」 룰 관점에서도 Rust는 위반 상태.

1. 목표 (성공 조건)

# 조건 검증
G1 Rust ON + post_filters OFF 결과 = Python OFF 결과 (동일 candles/ticks) golden test 1원 단위 diff = 0
G2 Rust ON + whipsaw+orderbook ON = Python 동일 조건 결과 Optuna best JSON 웹 백테 재실행 시 1원 일치 (룰 28)
G3 실매 코어 스모크 통과 유지 scripts/test_live_execution_validation.py
G4 봉 freeze / skip_hts_scan_dupes=false / OHLC 폴백 OFF 절대 준수 Rust 코드에 look-ahead 금지 + freeze 존중
G5 백테 웹 UI(파라미터·차트·거래내역) 무변경 유지 브라우저 검증 (룰 14)

2. 현재 상태 인벤토리 (요약)

2.1 Rust 노출 함수 (kis_rust_src/src/lib.rs)

종류 심볼 라인
#[pyfunction] run_dummy_backtest 2026
#[pymodule] kis_rust_core 2960
전략 run_tail_backtest_fast, run_engine_trial_tail 3233
전략 run_scalp_backtest_fast, run_engine_trial_scalp 35, 42
전략 run_breakout_backtest_fast, run_engine_trial_breakout 3637
전략 run_momentum_backtest_fast, run_engine_trial_momentum 3839
전략 run_box_backtest_fast, run_engine_trial_box 4041 (레거시)
세션 init_backtest_session, init_backtest_session_json, clear_backtest_session 4345
로더 db_loader::load_candles_from_db, parquet_loader::load_parquet_ticks_fast 34, 46

Cargo deps (Cargo.toml:10-19): pyo3 0.20 (extension-module), serde, sqlx(mysql), tokio, rayon, serde_json, polars(parquet,lazy), chrono, lazy_static.

2.2 Rust vs Python 기능 갭 (요약)

기능 Python Rust 이식 우선순위
포트폴리오 큐 (max_stocks / slot_money / 시각순 경쟁) P0
틱 청산 (resolve_backtest_selltry_sell_on_ticks) (SESSION 저장만) P0
T1 신호 · T 시가 진입 (align) 부분 (tail trial만) P0
봉 freeze / OHLC 폴백 금지 (틱 ON) (봉 high 선반영) P0
청산 우선순위 (전략별 상이) (EOD·TP 우선) P1
래칫 tiers P1
ATR 동적 SL (돌파) (고정%만) P1
min_hold_sec 필드만·미사용 P1
금액손실컷 (KRW×qty) 근사 (drop_pct만) P1
whipsaw 필터 (진입 시점) (post_filter만) P2
호가 필터 (진입 · exit · stop) (post_filter만) P2
프로그램매매 필터 P2
Optuna 브릿지 키 정합 부분 3건 버그 P3

3. Python 실매/백테 엔진 청산 순서 (전략별 · 이식 목표)

3.1 꼬리 — kis_trader/engine/tail_engine.py:1406-1518

순위 조건 라인 params/env
gate min_hold_sec 1436-1444 min_hold_sec
1 래칫 / 어깨 1452-1472 ratchet_tiers, TAIL_RATCHET_TIERS, shoulder_min_high, shoulder_cut_pct
2 익절 (target=ATR 캡) 1474-1477 target
3 손절 1478-1481 stop
4 트레일 1482-1493 trail_pct, trail_arm_pct
5 금액손실컷 1494-1502 max_loss_krw, min_drop_pct_for_loss_cut, qty
6 시간컷 1503-1510 TAIL_MAX_HOLD_BARS(분)
7 장마감 1511-1514 is_eod

포트폴리오: run_tail_backtest_portfolio (tail_engine.py:1799).

3.2 모멘텀 — kis_trader/engine/momentum_hts_logic.py:678-801

순위 reason 라인 params
gate min_hold 717-723 min_hold_sec
1 래칫컷 733-743 ratchet_tiers
2 어깨컷 745-750 shoulder_min_high, shoulder_cut_pct
3 호가컷 (exit_ob) 752-757 _ob_or_history
4 손절호가 (stop_ob) 759-761 동상
5 손절 763-765 sl_pct
6 트레일컷 767-773 trail_pct, trail_arm_pct
7 시간컷 775-779 max_hold_bars
8 금액손실컷 781-791 max_loss_krw, min_drop_pct_for_loss_cut
9 익절 793-795 tp_pct
10 장마감 797-799 is_eod

포트폴리오: momentum_portfolio_backtest.py:625.

3.3 돌파 — kis_trader/strategies/breakout.py:1217-1306

  • ATR SL 함수: _breakout_sl_line (170-192) — sl_mode='atr' + entry_atratr_sl_mult, atr_sl_min_pct, atr_sl_max_pct.
  • 청산 순서: EOD → TP → shoulder/ratchet → 호가컷 → 손절호가 → SL(ATR or 고정) → trailing → max_hold.
  • 포트폴리오: breakout_portfolio_backtest.py:135.

3.4 스캘핑 — kis_trader/engine/scalping_engine.py:1281-1360

순위 reason 라인 params
gate min_hold 1319-1326 min_hold_sec
1 어깨컷 1334-1340 shoulder_*
2 익절 1341-1343 target
3 손절 1344-1346 stop
4 금액손실컷 1347-1353 max_loss_krw, min_drop_pct_for_loss_cut
5 장마감 1354-1356 is_eod

백테 intrabar: check_sell_signal_backtest_bar (1220-1278) → resolve_backtest_sell. 포트폴리오: scalping_portfolio_backtest.py:110.


4. Rust 이식 로드맵 (P0 → P3)

Phase P0 — 정합 붕괴 원인 (엔진 코어)

목표: Rust ON 결과가 Python OFF (틱 청산 + 포트폴리오 켜진 상태)와 1원 일치.

  1. 포트폴리오 시각순 시뮬 신규 crate 모듈 portfolio_engine
    • Rust HashMap<time, Vec<Signal>> → 시각순 이터레이션
    • max_stocks, total_budget_krw, 1시각 1매수, slot_money, vol_fill_cap
    • 유니버스 timeline 은 Python에서 미리 계산해 Rust에 Vec<(time, Vec<code>)> 전달 (Rust에서 DB 재조회 없음)
    • 참조: tail_engine.py:1799-1946, backtest_portfolio_common.py:265-310
  2. 틱 청산 통합
    • session_manager.rs:BacktestSessionData.ticks_by_code 를 실제로 소비
    • try_sell_on_ticks Rust 포팅: tick 시간순 → max_price ← tick price, sell_fn
    • use_tick_exit=true 시 봉 OHLC intrabar 폴백 금지 (tick_exit_common.py:357-369)
  3. T1 / T 진입 align 통일
    • 모든 전략 trial 경로: candles[i+1].open 진입 (현재는 tail만 반영, 나머지는 same-bar close)
    • live_backtest_align=True 시나리오 재현
  4. 봉 freeze 존중
    • Rust 진입 시점에 봉이 “확정봉인지” 판정 (freeze ON이면 아직 진행 중인 봉의 high 사용 금지)
    • Python 백테는 과거 데이터라 이미 확정 상태이지만, tick 청산 시 max_price 업데이트 규칙은 “틱 우선, 봉 high 선반영 금지”

검증:

  • 동일 candles + ticks + params 로 Python run_*_backtest_portfolio vs Rust run_engine_trial_*_portfolio 결과 diff = 0 (golden test).

Phase P1 — 청산 우선순위 · 파라미터 완성

목표: 각 전략의 매도 판정이 Python 실매와 스텝 단위로 일치.

  1. 청산 함수 1:1 포팅 (전략별)
    • 꼬리 V4: 위 §3.1 표
    • 모멘텀 10단계: 위 §3.2 표
    • 돌파: _breakout_sl_line 포함 (ATR SL)
    • 스캘핑: 어깨 + 금액손실컷
  2. min_hold_sec 게이트 (전 전략)
    • 매수 시각 대비 초 단위 hold 시간 미달이면 어떤 청산도 발동 안 함
  3. max_loss_krw × qty 금액손실컷 (Rust는 현재 drop_pct 근사)
    • qty 필요 → 포트폴리오 slot_money/entry 로 산출
  4. ATR 계산 활용 (compute_atr Wilder RMA · breakout.rs:131-156)
    • 현재는 atr_entry 저장만 → SL 라인 실제 사용

검증: 각 전략 5-10 종목·1일 골든 케이스로 청산 reason·price·time 일치.


Phase P2 — TRIGGER 진입 필터 (whipsaw / 호가 / 프로그램)

목표: 진입 시점에 whipsaw·호가·프로그램 필터가 걸리는 종목은 Rust도 스킵.

  1. whipsaw 필터 Rust 포팅 (whipsaw_filter.py:449-533 기준)
    • _whipsaw_ticks (Rust SESSION의 ticks 재사용)
    • subbar_sec, lookback_sec, dip_pct 오버레이
    • tick_mode ON 시 OHLC 폴백 스킵
  2. 호가 필터 Rust 포팅 (orderbook_filter.py:79-103)
    • WS_ORDERBOOK_FILTER_MAX_AGE_SEC(판정) vs WS_ORDERBOOK_TICK_MAX_AGE_SEC(저장) 분리 유지
    • 호가 데이터는 Python에서 DB 로드해 Rust SESSION 에 Vec<Orderbook> 전달 (Rust는 DB 안 침)
    • _ob_ask_max_mult, _ob_bid_min_ratio
  3. 프로그램매매 필터 (_program_filter_enabled)
  4. apply_bt_post_filters 역할 재정의
    • 현재는 Rust 진입 후 Python 사후 필터 (엔진 정합 아님)
    • P2 이후엔 Rust 엔진 내부에 이미 필터가 있으므로 post_filter 는 선택적 검증 계층 으로 격하 (기본 OFF or 회귀 테스트 전용)

검증: 웹 백테(휩쏘 ON + 호가 ON) = Rust Optuna best 재실행 1원 일치.


Phase P3 — Optuna 브릿지 · 표준 키 · 운영

  1. param_search_optuna.py:48BACKTEST_USE_RUST=1 강제 제거
    • Optuna 기본은 Python 유지. Rust ON 은 UI 토글 or CLI 명시 인자로만.
    • 이 항목은 P0 진입 전 즉시 실행 가능 (안전조치). B안 이식이 완료되면 다시 Rust 기본으로 복귀 검토.
  2. 브릿지 인자 3건 정정
    • momentum min_hold_sec 누락 (momentum_backtest_common.py:620-653)
    • scalp 브릿지 skip_hts vs Python skip_hts_scan_dupes 키명 통일 (scalping_backtest_common.py:366)
    • tail Rust 브릿지 default skip_hts_scan_dupes=TrueFalse (tail_backtest_common.py:822)
  3. 표준 키 전량 Rust Params 확장 (2026-09-06 통일 키)
    • whipsaw_filter_enabled, whipsaw_subbar_sec, whipsaw_lookback_sec, whipsaw_dip_pct
    • _orderbook_filter_enabled, _ob_ask_max_mult, _ob_bid_min_ratio
    • min_hold_sec, max_loss_krw, sl_mode, atr_*, ratchet_tiers
    • portfolio_mode, max_stocks, slot_money, total_budget_krw
  4. 빌드 · 배포 스크립트 문서화
    • 표준 절차:
      cd /home/hoon/kis_bot/kis_rust_src
      cargo build --release
      cp target/release/libkis_rust_core.so ../kis_rust_core.so
      # venv .so 교체 (site-packages/kis_rust_core/)
      
    • CI 훅 (선택): cargo build --release + python -c "import kis_rust_core" 스모크

5. 각 전략별 이식 체크리스트

5.1 꼬리 (tail)

  • TailParams 필드 확장 (min_hold_sec, ratchet_tiers, whipsaw/호가 표준 키)
  • 청산 순서 V4 (§3.1 표 그대로)
  • _universe_codes_at 로 유니버스 시각별 필터
  • 브릿지 rsi_threshold ↔ Rust rsi_limit 매핑 통일
  • skip_hts_scan_dupes 기본 False
  • entry_mode: align (T-1 신호 / T 시가), limit_atr, tick 진입

5.2 모멘텀 (momentum)

  • MomentumParams 필드 확장 (min_hold_sec 전달·사용, EMA, 호가/whipsaw)
  • 10단계 청산 (§3.2 표)
  • E confirm · vol pulse · high chase · RSI · EMA 진입 로직
  • intrabar BACKTEST_EXIT_CHECKS_PER_BAR 대응

5.3 돌파 (breakout)

  • BreakoutParams 확장 (sl_mode, atr_sl_mult, atr_sl_min_pct, atr_sl_max_pct)
  • _breakout_sl_line Rust 포팅
  • shoulder_min_high 를 trail_armed 로 오용한 부분(breakout.rs:227) 수정
  • 청산 순서 (§3.3)

5.4 스캘핑 (scalp)

  • ScalpParams 확장 (whipsaw/호가/max_loss_krw)
  • intrabar high/low TP/SL (현재는 종가 pnl% only)
  • 어깨 + 금액손실컷 (§3.4)
  • 브릿지 skip_htsskip_hts_scan_dupes
  • trade dict peak_price = 실제 max_price (현재는 sell_price 로 왜곡)

5.5 레인지 (updown_box, 레거시)

  • 우선순위 낮음. 실매 미사용이면 이식 보류.

6. 정합 검증 방법 (golden test)

6.1 데이터 준비

  • 국내: ws_ticks, kis_candles, ls_ws_candles 에서 최근 거래일 5-10 종목 subset 을 파일(json/parquet) 로 export
  • 파라미터: 실매 DB 스냅샷 + Optuna best 몇 개
  • 유니버스: build_universe_timeline 결과를 파일로 저장

6.2 실행

# Python 기준
python3 scripts/golden_run.py --engine python --strategy tail --date 2026-09-04
# Rust 후보
python3 scripts/golden_run.py --engine rust   --strategy tail --date 2026-09-04

6.3 diff 기준

단계 허용 diff
Phase P0 완료 거래 수 / 진입 시각 / 청산 시각 / 청산 reason / entry / exit — 완전 일치
Phase P1 완료 + PnL·MDD·PF — 1원 단위 일치
Phase P2 완료 + 필터 ON 조건도 동일

diff 발생 시 첫 다른 trade 의 candle/tick/params 스냅샷을 남기고 그 케이스만 좁혀 재현.

6.4 웹 백테 정합 (룰 15 · 28)

Optuna best JSON → 웹 백테 재실행 → 1원 단위 일치 (bt-form-postfilter-key-parity.mdc).


7. 빌드 · 배포 절차

7.1 개발 빌드

cd /home/hoon/kis_bot/kis_rust_src
cargo build --release
cp target/release/libkis_rust_core.so /home/hoon/kis_bot/kis_rust_core.so
# venv 반영
cp target/release/libkis_rust_core.so \
   /home/hoon/kis_bot/.venv/lib/python3.12/site-packages/kis_rust_core/kis_rust_core.cpython-312-x86_64-linux-gnu.so

7.2 스모크

python3 -c "import kis_rust_core as k; print(dir(k))"
python3 -u scripts/test_live_execution_validation.py

7.3 롤백

  • 빌드 전 .sokis_rust_core.so.bak 로 백업
  • Optuna는 BACKTEST_USE_RUST=0 로 즉시 Python 폴백 가능
  • UI Rust 뱃지는 use_rust=False job JSON 재저장 시 자동 반영

8. 리스크 · 마일스톤

리스크 대응
Rust 이식 중 실매 사고 실매 코어는 Python. Rust는 백테/Optuna만. 실매 경로에 Rust 미사용 (원칙)
포트폴리오 시각순 큐의 미묘한 순서 차이 Rust BTreeMap<time> 로 시각 정렬 → Python sorted(all_times) 동일 순서
유니버스 timeline DB 재조회 부하 Python에서 미리 계산해 Rust에 넘김 (Rust는 DB 안 침)
틱 데이터 메모리 폭증 (수백만 tick) 종목별 lazy load + LRU 캐시 or 시간 windowing
apply_bt_post_filters 이중 필터 P2 완료 후 사후 필터 기본 OFF, 회귀 테스트 모드에서만 ON
Rust 컴파일 실패 cargo build --release 실패 시 .so 유지 (교체 안 함)

마일스톤 제안

M 산출물 예상 규모
M1 P3(안전조치) 즉시 반영: Optuna 기본 Python 복귀 + 브릿지 3건 정정 소규모 (1턴)
M2 P0 포트폴리오 + 틱 청산 Rust 모듈 + golden test 프레임 중간 (수 턴)
M3 P1 청산 순서 4전략 완성 중간~대규모
M4 P2 whipsaw/호가/프로그램 필터 이식 중간
M5 P3 표준 키 확장 · post_filter 격하 · CI · 문서 갱신 소규모

9. 새 대화 시작 시 사용할 프롬프트 템플릿

목표: docs/rust_engine_parity_port_plan.md 의 Phase P0 부터 시작.

준비:
1. 이 대화의 실매 스모크는 통과 상태 (2026-09-06 완료 로그: logs/test_live_execution_validation_20260906_180432.log)
2. `bt_post_filters.py`, `optuna_web_jobs.py`, `backtest_web.py`, `static/js/backtest.js`, `param_search_scalping.py`, `param_search_apply_snapshot.py` 는 2026-09-06 통일(whipsaw_filter_enabled 표준 키) + 시그니처 정정(orderbook_reject_for_entry current_price=) 완료.
3. 옵투나 잡 삭제 API/UI 신설 완료 (`/api/optuna/delete/<job_id>`)
4. 리아퍼 훅으로 refine 완료 시 phase1/2 결과 JSON 자동 register 완료.

절차:
1. P0-0: `param_search_optuna.py:48` 의 `BACKTEST_USE_RUST=1` 강제를 optional 로 (안전조치)
2. P0-1: golden test 프레임 (`scripts/golden_run.py`) 신설 — Python vs Rust 결과 diff
3. P0-2: `session_manager.rs` 에 tick 저장·조회 확장
4. P0-3: 포트폴리오 시각순 시뮬 Rust 모듈 (`kis_rust_src/src/portfolio_engine.rs`) 신설
5. P0-4: 각 전략 `run_engine_trial_*_portfolio` 신설, 기존 `run_engine_trial_*` 은 종목별 독립 모드로 유지

각 단계 완료 후:
- `cargo build --release`
- `.so` 교체
- `python3 -u scripts/test_live_execution_validation.py` 통과 확인
- golden test diff = 0 확인
- 다음 단계 착수 전 사용자 승인

10. 참고 문서 · 규칙

  • .cursorrules CRITICAL §0 (봉 freeze · skip_hts · OHLC 폴백 금지)
  • .cursor/rules/no-ohlc-fallback-parity.mdc
  • .cursor/rules/live-backtest-optuna-parity.mdc (룰 15)
  • .cursor/rules/bt-form-postfilter-key-parity.mdc (룰 28, 2026-09-06 신설)
  • .cursor/rules/optuna-job-engine-label.mdc (룰 29, 2026-09-06 신설)
  • .cursor/rules/live-execution-validation.mdc
  • .cursor/rules/verify-before-conclude-cross-strategy.mdc §2 (버그 딥다이브 · 근본 3안)
  • docs/정합성.md §8 (봉 정합)
  • docs/호가.md (호가 필터 TTL 분리)

부록 A: 이번 대화(2026-09-06) 에서 완료된 사전 정지 작업

# 항목 파일
A1 bt_post_filters.py:75 orderbook_reject_for_entry 시그니처 정정 (price=current_price=, 튜플 unpack) kis_trader/backtest/bt_post_filters.py
A2 save_job() tmp 파일에 pid 접미 (다중 프로세스 rename 경합 해소) kis_trader/backtest/optuna_web_jobs.py
A3 reaper 훅 — mode_refine 완료 시 phase1/2 JSON 자동 register_result_json_as_job (결과 잡아이디 UI 원복) kis_trader/backtest/optuna_web_jobs.py
A4 옵투나 잡 삭제 API/UI 신설 (/api/optuna/delete/<job_id>, /api/optuna/delete_bulk, JS 🗑 버튼) optuna_web_jobs.py, backtest_web.py, static/js/backtest.js
A5 whipsaw 표준 키 통일 (whipsaw_enabled 옛 alias 완전 제거 → whipsaw_filter_enabled 단일) 6개 파일 (룰 28)
A6 bar_is_garbage wall-clock(recv_ts) 정합 + bt_candle_source 로더 통일 (breakout_tick_loader 위임 · 22.8% → 5.2%) bt_candle_source.py, feed_fallback.py, breakout_tick_loader.py
A7 파케이 초고속 로드 게이트 해제 (BACKTEST_USE_RUST 무관 항상 사용) — I/O 최적화이지 엔진 계산 아님 breakout_tick_loader.py:277-282
A8 4전략 Optuna 파일에서 Rust 세션 초기화·_rust_session_id 세팅을 BACKTEST_USE_RUST==1 로 게이팅 (Python 라디오 = 진짜 Python) param_search_optuna.py, optuna_scalping.py, optuna_breakout.py, optuna_momentum.py
A9 optuna_web_jobs.pyBACKTEST_USE_RUST 세팅 반전 로직 정정 (use_rust=True → "1", False → "0" 명시) optuna_web_jobs.py:3763
A10 annotate_optuna_period_daily_avg 가 결과 JSON 에 use_rust 자동 기록 → auto-register 뱃지 재발 방지 optuna_common.py:112
A11 KIWOOM_TICK_LIVE_MAX_LAG_SEC 폐기 → 3벤더 공통 WS_TICK_DB_SAVE_LAG_CUT_ENABLED 스위치 (기본 OFF=전부 저장, 통계 유지) kiwoom_ws.py, kis_ws.py, ls_ws.py, database.py

부록 B: 발견된 Rust 정합 이슈 12건 (우선순위)

# 항목 리스크 파일:라인 Phase
1 Optuna 기본 = Rust OHLC 단순 엔진 (BACKTEST_USE_RUST=1 강제) 랭킹 왜곡 param_search_optuna.py:48 P3-1
2 모멘텀 MomentumParams 인자 누락 (min_hold_sec) → TypeError trial 전부 빈 trades momentum_backtest_common.py:620-653 P3-2
3 틱 청산 ON 시 Rust c.high 로 max_price 선반영 look-ahead tail.rs:186,199 P0-4
4 청산 우선순위 (EOD·TP 우선 vs 어깨·래칫 우선) reason·PnL 상이 *.rs vs Python 엔진 P1-1
5 꼬리 skip_hts_scan_dupes 기본값 반대 (Rust True vs Python False) TRIGGER ON/OFF 반대 tail_backtest_common.py:822 P3-2
6 꼬리 experimental TailParams 27인자 vs Rust 19인자 TypeError tail_engine.py:2547-2567 P3-2
7 스캘핑 브릿지 skip_hts vs Python skip_hts_scan_dupes 필터 스킵 다름 scalping_backtest_common.py:366 P3-2
8 스캘핑 TP/SL 종가 pnl% (intrabar high/low 미사용) 체결가 차이 scalp.rs:206-235 P1-1
9 돌파 SL Python ATR vs Rust 고정% 손절선 상이 breakout.rs:217 P1-4
10 모멘텀 ATR/래칫/min_hold/time_stop Rust 미구현 고급 청산 무시 momentum.rs P1-1
11 스캘핑 trade dict peak_price = sell_price MFE 왜곡 scalping_backtest_common.py:381 P1-1
12 레인지 updown_box.rs 단순 무지성 실험 수준 레거시 updown_box.rs:65-131 보류

부록 C: D안 — Rust 엔진 데이터 절반짜리 상태 (2026-09-06 발견 · P0 최우선)

C.1 사실 (실측)

Rust 세션 초기화 (kis_rust_core.init_backtest_session_json(session_id, candles_json)) 는 candles(봉) 만 넘긴다. run_engine_trial_scalp/breakout/momentum/tail(session, params) 도 신규 데이터 없이 파라미터만 받는다.

현재 Rust 엔진은 데이터 절반짜리:

데이터 Python 엔진 (engine_params) Rust 세션
봉 (codes_candles)
틱 (ticks_by_code) 미전달
호가 (_bt_orderbook_by_code) 미전달
프로그램매매 (_bt_program_by_code) 미전달
  • 확인 위치: optuna_scalping.py:428-435, optuna_breakout.py:428-435, optuna_momentum.py:492-500, param_search_optuna.py:518-525
  • Rust 소비처: scalping_backtest_common.py:337, breakout_backtest_common.py, momentum_backtest_common.py, tail_backtest_common.py 모두 if rust_session: 분기에서 params 만 넘김

C.2 의미

Python 엔진이 정합 개선(A6~A11) 을 받아도, Rust 세션은 여전히 봉만 보고 판정 → 다음이 그대로 갭:

  1. 틱 청산 (WS 체결가·max_price 갱신·트레일 스톱) → Rust 는 종가 OHLC 기반 근사
  2. 호가 필터 (진입/exit/stop 3구간) → Rust 는 무필터 (post_filter 만 있음)
  3. 프로그램매매 필터 → Rust 완전 없음
  4. whipsaw 필터 (진입 시점) → Rust 는 진입 이후 post_filter (실제 진입 자체를 못 막음)

이 상태에서 Optuna Rust 결과를 실매에 적용하면 실매는 위 4개 필터로 진입 자체가 안 되는 후보 가 Rust 상 최적화 대상이 되어 랭킹 왜곡.

C.3 P0 로드맵 (D안 · 사용자 승인 시 착수)

단계 파일 작업
D-1 kis_rust_src/src/session_manager.rs Session 구조체에 ticks: HashMap<String, HashMap<String, Vec<Tick>>> (code→minute→ticks), orderbook_snaps: HashMap<String, Vec<OBSnap>>, program_ticks: HashMap<String, Vec<PgTick>> 필드 추가
D-2 kis_rust_src/src/lib.rs init_backtest_session_json 시그니처 확장: (session_id, candles_json, ticks_json, orderbook_json, program_json). 옛 시그니처는 wrapper 로 유지 (하위호환)
D-3 kis_rust_src/src/{scalp,breakout,momentum,tail}.rs try_sell_on_ticks Rust 포팅 (Python resolve_backtest_sell 미러). 봉 high 로 max_price 선반영 금지 (룰 §부록 B #3)
D-4 kis_rust_src/src/filters.rs (신설) orderbook_reject_for_entry, whipsaw_filter_hit, program_filter_reject Rust 포팅. Python 과 1원 단위 일치 golden test
D-5 4전략 Optuna 파일 init_backtest_session_json 호출 시 ticks_by_code, orderbook_map, program_map 모두 JSON 직렬화해서 전달
D-6 golden test (scripts/golden_run.py) Rust ON + Python OFF 결과 diff = 0 (같은 필터·같은 데이터에서)

C.4 리스크

  • Rust 크레이트 대공사 (session_manager.rs 필드 대량 추가 · 새 소비 로직)
  • ticks_by_code 전체 JSON 직렬화 → 200종목 1일치 ~100MB. JSON 대신 shared memory 또는 Arrow IPC 고려 필요 (P0-6 서브태스크)
  • 이번 세션에서 착수하지 않음. 이 문서는 P0 최우선 항목 (부록 B #3 대체·확장) 으로 다음 세션 시작점

C.5 임시 대응 (D안 완료 전까지)

  • Optuna 실행은 Python 라디오 기본 유지 (A8 로 이미 가능)
  • Rust 는 봉만 필요한 단순 백테·golden 진행에만 사용
  • Optuna best 를 실매 적용 전 반드시 웹 백테(Python) 로 재검증 (룰 15)

끝. 이 문서 기준으로 새 대화에서 부록 C P0 (D안 · 데이터 완전 전달) 부터 착수.