From 56b254b249a61f8c5f62088366ad78750e7b9634 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 6 Sep 2026 19:50:27 +0900 Subject: [PATCH] =?UTF-8?q?fix(=EC=A0=95=ED=95=A9=EC=84=B1):=20Python=20?= =?UTF-8?q?=EB=9D=BC=EB=94=94=EC=98=A4=EC=9D=B8=EB=8D=B0=20Rust=20?= =?UTF-8?q?=EB=A1=9C=20=EB=8F=84=EB=8A=94=20=EC=9B=90=ED=9D=89=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20+=20web=5Fjobs=20env=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 증상: Python 엔진 라디오를 눌러도 Rust 만큼 빠르고 매매 건수가 완전 다름 (380건 등). 잡 JSON 에는 use_rust=False 로 저장됐지만 실제 엔진은 Rust. 근본원인 (룰 19 딥다이브): 1. optuna_web_jobs.py:3759 [완전 반대 오타] (2026-09-06 4dbb138 안티그래비티 커밋에서 유입) `if use_rust: env['BACKTEST_USE_RUST'] = '0'` → Rust 라디오 = '0' (Python) / Python 라디오 = 세팅 안 함 (부모 env 상속) 2. optuna_scalping/breakout/momentum/param_search_optuna: BACKTEST_USE_RUST env 무관하게 무조건 kis_rust_core.init_backtest_session_json() 초기화 + combo['_rust_session_id'] 세팅 → scalping/breakout/momentum/tail_backtest_common 이 _rust_session_id 존재만으로 Rust 실행 → env 오타가 아니어도 어차피 항상 Rust 로 돌던 진짜 원흉 수정 (B안): - optuna_web_jobs.py:3759: `env['BACKTEST_USE_RUST'] = '1' if use_rust else '0'` (항상 명시) - optuna_scalping/breakout/momentum/param_search_optuna: BACKTEST_USE_RUST=='1' 인 경우에만 init_backtest_session_json 호출 + combo['_rust_session_id'] 세팅 - Python 경로에서는 rust_session_id=None, combo 에도 없음 → 순수 Python 엔진 - clear_rust_session 도 세션 있을 때만 호출 (param_search_optuna) env 하나가 진리 소스 (룰 29): - optuna_web_jobs 가 env 를 명시 세팅 → 서브프로세스 상속 - 각 optuna_* 는 env 만 보고 Rust/Python 분기 - 부모 env 오염 방지 실매 스모크: logs/test_live_execution_validation_20260906_195008.log → 최종: 통과 웹 재시작 200. 다음 옵투나 실행부터 Python 라디오는 실제로 Python 엔진. Co-authored-by: Cursor --- kis_trader/backtest/optuna_breakout.py | 36 +++++++++++-------- kis_trader/backtest/optuna_momentum.py | 36 +++++++++++-------- kis_trader/backtest/optuna_scalping.py | 37 ++++++++++++-------- kis_trader/backtest/optuna_web_jobs.py | 6 ++-- kis_trader/backtest/param_search_optuna.py | 40 ++++++++++++++-------- 5 files changed, 96 insertions(+), 59 deletions(-) diff --git a/kis_trader/backtest/optuna_breakout.py b/kis_trader/backtest/optuna_breakout.py index f09f705..094a06e 100644 --- a/kis_trader/backtest/optuna_breakout.py +++ b/kis_trader/backtest/optuna_breakout.py @@ -428,18 +428,25 @@ def run_breakout_optuna( bind_study_trials(study, n_trials=n_trials, log=logger) n_trials = clamp_optimize_n_trials(study, n_trials, log=logger) - import uuid - rust_session_id = f"optuna_break_{uuid.uuid4().hex[:8]}" - try: - import kis_rust_core - import json - candles_json = json.dumps(ctx.codes_candles) - kis_rust_core.init_backtest_session_json( - rust_session_id, - candles_json - ) - except Exception as e: - logger.warning(f"Failed to init_rust_session: {e}") + # 2026-09-06 (docs/rust_engine_parity_port_plan.md · 룰 29): env 게이트로 Rust 세션 초기화 제한. + _use_rust_env = os.environ.get("BACKTEST_USE_RUST") == "1" + rust_session_id: Optional[str] = None + if _use_rust_env: + import uuid + rust_session_id = f"optuna_break_{uuid.uuid4().hex[:8]}" + try: + import kis_rust_core + import json + candles_json = json.dumps(ctx.codes_candles) + kis_rust_core.init_backtest_session_json( + rust_session_id, + candles_json + ) + except Exception as e: + logger.warning(f"Failed to init_rust_session: {e}") + rust_session_id = None + else: + logger.info("🐍 [Python 엔진] Rust 세션 미초기화 (BACKTEST_USE_RUST != 1)") def objective(trial: optuna.Trial) -> float: if ctx.mode == "tpe": @@ -450,8 +457,9 @@ def run_breakout_optuna( ) else: combo = suggest_breakout_params(trial, ctx.mode) - - combo["_rust_session_id"] = rust_session_id + + if rust_session_id is not None: + combo["_rust_session_id"] = rust_session_id result = evaluate_breakout_param_combo( combo, base_fixed=ctx.base_fixed, diff --git a/kis_trader/backtest/optuna_momentum.py b/kis_trader/backtest/optuna_momentum.py index 4623080..269880f 100644 --- a/kis_trader/backtest/optuna_momentum.py +++ b/kis_trader/backtest/optuna_momentum.py @@ -481,18 +481,25 @@ def run_momentum_optuna( bind_study_trials(study, n_trials=n_trials, log=logger) n_trials = clamp_optimize_n_trials(study, n_trials, log=logger) - import uuid - rust_session_id = f"optuna_mome_{uuid.uuid4().hex[:8]}" - try: - import kis_rust_core - import json - candles_json = json.dumps(ctx.codes_candles) - kis_rust_core.init_backtest_session_json( - rust_session_id, - candles_json - ) - except Exception as e: - logger.warning(f"Failed to init_rust_session: {e}") + # 2026-09-06 (docs/rust_engine_parity_port_plan.md · 룰 29): env 게이트로 Rust 세션 초기화 제한. + _use_rust_env = os.environ.get("BACKTEST_USE_RUST") == "1" + rust_session_id: Optional[str] = None + if _use_rust_env: + import uuid + rust_session_id = f"optuna_mome_{uuid.uuid4().hex[:8]}" + try: + import kis_rust_core + import json + candles_json = json.dumps(ctx.codes_candles) + kis_rust_core.init_backtest_session_json( + rust_session_id, + candles_json + ) + except Exception as e: + logger.warning(f"Failed to init_rust_session: {e}") + rust_session_id = None + else: + logger.info("🐍 [Python 엔진] Rust 세션 미초기화 (BACKTEST_USE_RUST != 1)") def objective(trial: optuna.Trial) -> float: if ctx.mode == "tpe": @@ -501,8 +508,9 @@ def run_momentum_optuna( ) else: combo = suggest_momentum_params(trial, ctx.mode, market=getattr(ctx, "market", "KR") or "KR") - - combo["_rust_session_id"] = rust_session_id + + if rust_session_id is not None: + combo["_rust_session_id"] = rust_session_id result = evaluate_momentum_param_combo( combo, base_fixed=ctx.base_fixed, diff --git a/kis_trader/backtest/optuna_scalping.py b/kis_trader/backtest/optuna_scalping.py index 2fa4a5c..2e90b76 100644 --- a/kis_trader/backtest/optuna_scalping.py +++ b/kis_trader/backtest/optuna_scalping.py @@ -415,26 +415,35 @@ def run_scalp_optuna( bind_study_trials(study, n_trials=n_trials, log=logger) n_trials = clamp_optimize_n_trials(study, n_trials, log=logger) - import uuid - rust_session_id = f"optuna_scalp_{uuid.uuid4().hex[:8]}" - try: - import kis_rust_core - import json - candles_json = json.dumps(ctx.codes_candles) - kis_rust_core.init_backtest_session_json( - rust_session_id, - candles_json - ) - except Exception as e: - logger.warning(f"Failed to init_rust_session: {e}") + # 2026-09-06 (docs/rust_engine_parity_port_plan.md · 룰 29): Rust 세션은 BACKTEST_USE_RUST=1 + # 일 때만 초기화하고 combo 에 세팅. 이전엔 env 무관하게 항상 세팅 → Python 선택해도 Rust 로 돌던 버그. + _use_rust_env = os.environ.get("BACKTEST_USE_RUST") == "1" + rust_session_id: Optional[str] = None + if _use_rust_env: + import uuid + rust_session_id = f"optuna_scalp_{uuid.uuid4().hex[:8]}" + try: + import kis_rust_core + import json + candles_json = json.dumps(ctx.codes_candles) + kis_rust_core.init_backtest_session_json( + rust_session_id, + candles_json + ) + except Exception as e: + logger.warning(f"Failed to init_rust_session: {e}") + rust_session_id = None + else: + logger.info("🐍 [Python 엔진] Rust 세션 미초기화 (BACKTEST_USE_RUST != 1)") def objective(trial: optuna.Trial) -> float: if ctx.mode == "tpe": combo = suggest_scalp_params_tpe(trial) else: combo = suggest_scalp_params(trial, ctx.mode) - - combo["_rust_session_id"] = rust_session_id + + if rust_session_id is not None: + combo["_rust_session_id"] = rust_session_id result = evaluate_scalp_param_combo( combo, base_fixed=ctx.base_fixed, diff --git a/kis_trader/backtest/optuna_web_jobs.py b/kis_trader/backtest/optuna_web_jobs.py index ebf73be..d4dfb35 100644 --- a/kis_trader/backtest/optuna_web_jobs.py +++ b/kis_trader/backtest/optuna_web_jobs.py @@ -3756,8 +3756,10 @@ def start_optuna_job( # 사후 report 게이트 거래수 = 탐색과 동일(기간 자동) env["PARAM_SEARCH_OPTUNA_REPORT_MIN_TRADES"] = str(min_trades) env["SORT_BY"] = sort_by - if use_rust: - env["BACKTEST_USE_RUST"] = "0" + # 2026-09-06 정정: 이전 로직 `if use_rust: env["BACKTEST_USE_RUST"] = "0"` 은 완전히 반대. + # use_rust=True → "1" (Rust 활성) / False → "0" (Python 강제). 항상 명시 세팅해 + # 부모 프로세스 env 상속 오염을 차단한다 (룰 29 · docs/rust_engine_parity_port_plan.md). + env["BACKTEST_USE_RUST"] = "1" if use_rust else "0" _labels = { "momentum": "모멘텀", diff --git a/kis_trader/backtest/param_search_optuna.py b/kis_trader/backtest/param_search_optuna.py index d778858..69ce9d4 100644 --- a/kis_trader/backtest/param_search_optuna.py +++ b/kis_trader/backtest/param_search_optuna.py @@ -510,22 +510,30 @@ def run_tail_optuna( bind_study_trials(study, n_trials=n_trials, log=logger) n_trials = clamp_optimize_n_trials(study, n_trials, log=logger) - import uuid - rust_session_id = f"optuna_tail_{uuid.uuid4().hex[:8]}" - try: - from kis_trader.engine.tail_engine import init_rust_session, clear_rust_session - init_rust_session(rust_session_id, ctx.candles_by_code, ctx.ticks_by_code) - except Exception as e: - logger.warning(f"Failed to init_rust_session: {e}") + # 2026-09-06 (docs/rust_engine_parity_port_plan.md · 룰 29): env 게이트로 Rust 세션 초기화 제한. + _use_rust_env = os.environ.get("BACKTEST_USE_RUST") == "1" + rust_session_id: Optional[str] = None + if _use_rust_env: + import uuid + rust_session_id = f"optuna_tail_{uuid.uuid4().hex[:8]}" + try: + from kis_trader.engine.tail_engine import init_rust_session, clear_rust_session # noqa: F401 + init_rust_session(rust_session_id, ctx.candles_by_code, ctx.ticks_by_code) + except Exception as e: + logger.warning(f"Failed to init_rust_session: {e}") + rust_session_id = None + else: + logger.info("🐍 [Python 엔진] Rust 세션 미초기화 (BACKTEST_USE_RUST != 1)") def objective(trial: optuna.Trial) -> float: if ctx.mode == "tpe": combo = suggest_tail_params_tpe(trial, entry_mode=ctx.tpe_entry_mode) else: combo = suggest_tail_params(trial, ctx.mode) - - # Rust 세션 ID를 파라미터에 넘겨서 하위에서 사용할 수 있게 함 - combo["_rust_session_id"] = rust_session_id + + # Rust 세션이 없으면 세팅하지 않음 (Python 경로) + if rust_session_id is not None: + combo["_rust_session_id"] = rust_session_id result = evaluate_tail_param_combo( combo, base_params=ctx.base_params, @@ -748,11 +756,13 @@ def run_tail_optuna( finally: # mode_combo 실측이 ticks 공유뷰를 쓰므로 optimize 직후 unlink 금지 release_shared_tick_store(ctx, log=logger) - try: - from kis_trader.engine.tail_engine import clear_rust_session - clear_rust_session(rust_session_id) - except Exception as e: - pass + # rust_session_id 는 use_rust=True 로 초기화된 경우에만 clear 필요 + if rust_session_id is not None: + try: + from kis_trader.engine.tail_engine import clear_rust_session + clear_rust_session(rust_session_id) + except Exception: + pass def apply_best_trial(study: optuna.Study, ctx: TailSearchContext) -> bool: