## 근본원인 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>
25 KiB
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 |
20–26 |
#[pymodule] |
kis_rust_core |
29–60 |
| 전략 | run_tail_backtest_fast, run_engine_trial_tail |
32–33 |
| 전략 | run_scalp_backtest_fast, run_engine_trial_scalp |
35, 42 |
| 전략 | run_breakout_backtest_fast, run_engine_trial_breakout |
36–37 |
| 전략 | run_momentum_backtest_fast, run_engine_trial_momentum |
38–39 |
| 전략 | run_box_backtest_fast, run_engine_trial_box |
40–41 (레거시) |
| 세션 | init_backtest_session, init_backtest_session_json, clear_backtest_session |
43–45 |
| 로더 | 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_sell → try_sell_on_ticks) |
✅ | ❌ (SESSION 저장만) | P0 |
| T−1 신호 · 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_atr→atr_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원 일치.
- 포트폴리오 시각순 시뮬 신규 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
- Rust
- 틱 청산 통합
session_manager.rs:BacktestSessionData.ticks_by_code를 실제로 소비try_sell_on_ticksRust 포팅: tick 시간순 → max_price ← tick price,sell_fn훅use_tick_exit=true시 봉 OHLC intrabar 폴백 금지 (tick_exit_common.py:357-369)
- T−1 / T 진입 align 통일
- 모든 전략 trial 경로:
candles[i+1].open진입 (현재는 tail만 반영, 나머지는 same-bar close) live_backtest_align=True시나리오 재현
- 모든 전략 trial 경로:
- 봉 freeze 존중
- Rust 진입 시점에 봉이 “확정봉인지” 판정 (freeze ON이면 아직 진행 중인 봉의 high 사용 금지)
- Python 백테는 과거 데이터라 이미 확정 상태이지만, tick 청산 시 max_price 업데이트 규칙은 “틱 우선, 봉 high 선반영 금지”
검증:
- 동일 candles + ticks + params 로 Python
run_*_backtest_portfoliovs Rustrun_engine_trial_*_portfolio결과 diff = 0 (golden test).
Phase P1 — 청산 우선순위 · 파라미터 완성
목표: 각 전략의 매도 판정이 Python 실매와 스텝 단위로 일치.
- 청산 함수 1:1 포팅 (전략별)
- 꼬리 V4: 위 §3.1 표
- 모멘텀 10단계: 위 §3.2 표
- 돌파:
_breakout_sl_line포함 (ATR SL) - 스캘핑: 어깨 + 금액손실컷
min_hold_sec게이트 (전 전략)- 매수 시각 대비 초 단위 hold 시간 미달이면 어떤 청산도 발동 안 함
max_loss_krw× qty 금액손실컷 (Rust는 현재drop_pct근사)- qty 필요 → 포트폴리오 slot_money/entry 로 산출
- ATR 계산 활용 (
compute_atrWilder RMA ·breakout.rs:131-156)- 현재는
atr_entry저장만 → SL 라인 실제 사용
- 현재는
검증: 각 전략 5-10 종목·1일 골든 케이스로 청산 reason·price·time 일치.
Phase P2 — TRIGGER 진입 필터 (whipsaw / 호가 / 프로그램)
목표: 진입 시점에 whipsaw·호가·프로그램 필터가 걸리는 종목은 Rust도 스킵.
- whipsaw 필터 Rust 포팅 (
whipsaw_filter.py:449-533기준)_whipsaw_ticks(Rust SESSION의 ticks 재사용)subbar_sec,lookback_sec,dip_pct오버레이- tick_mode ON 시 OHLC 폴백 스킵
- 호가 필터 Rust 포팅 (
orderbook_filter.py:79-103)WS_ORDERBOOK_FILTER_MAX_AGE_SEC(판정) vsWS_ORDERBOOK_TICK_MAX_AGE_SEC(저장) 분리 유지- 호가 데이터는 Python에서 DB 로드해 Rust SESSION 에
Vec<Orderbook>전달 (Rust는 DB 안 침) _ob_ask_max_mult,_ob_bid_min_ratio
- 프로그램매매 필터 (
_program_filter_enabled) apply_bt_post_filters역할 재정의- 현재는 Rust 진입 후 Python 사후 필터 (엔진 정합 아님)
- P2 이후엔 Rust 엔진 내부에 이미 필터가 있으므로 post_filter 는 선택적 검증 계층 으로 격하 (기본 OFF or 회귀 테스트 전용)
검증: 웹 백테(휩쏘 ON + 호가 ON) = Rust Optuna best 재실행 1원 일치.
Phase P3 — Optuna 브릿지 · 표준 키 · 운영
param_search_optuna.py:48의BACKTEST_USE_RUST=1강제 제거- Optuna 기본은 Python 유지. Rust ON 은 UI 토글 or CLI 명시 인자로만.
- 이 항목은 P0 진입 전 즉시 실행 가능 (안전조치). B안 이식이 완료되면 다시 Rust 기본으로 복귀 검토.
- 브릿지 인자 3건 정정
- momentum
min_hold_sec누락 (momentum_backtest_common.py:620-653) - scalp 브릿지
skip_htsvs Pythonskip_hts_scan_dupes키명 통일 (scalping_backtest_common.py:366) - tail Rust 브릿지 default
skip_hts_scan_dupes=True→ False (tail_backtest_common.py:822)
- momentum
- 표준 키 전량 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_ratiomin_hold_sec,max_loss_krw,sl_mode,atr_*,ratchet_tiersportfolio_mode,max_stocks,slot_money,total_budget_krw
- 빌드 · 배포 스크립트 문서화
- 표준 절차:
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↔ Rustrsi_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_lineRust 포팅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_hts→skip_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 롤백
- 빌드 전
.so를kis_rust_core.so.bak로 백업 - Optuna는
BACKTEST_USE_RUST=0로 즉시 Python 폴백 가능 - UI Rust 뱃지는
use_rust=Falsejob 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. 참고 문서 · 규칙
.cursorrulesCRITICAL §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.py 의 BACKTEST_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 세션은 여전히 봉만 보고 판정 → 다음이 그대로 갭:
- 틱 청산 (WS 체결가·max_price 갱신·트레일 스톱) → Rust 는 종가 OHLC 기반 근사
- 호가 필터 (진입/exit/stop 3구간) → Rust 는 무필터 (post_filter 만 있음)
- 프로그램매매 필터 → Rust 완전 없음
- 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안 · 데이터 완전 전달) 부터 착수.