fix: 한투 호가 2키 전용, LS RAM 합집합, 분봉 쓰레기면 다음 소스 통째

메인 시세 41에 H0STASP0를 붙이는 폴백을 없앤다. MINIMAL과 무관하게
후보∪보유∪영구를 LS RAM에 붙인다. 그 분 틱이 없거나 봉끝 대비 늦으면
그 WS 봉을 버리고 2차·LS·REST 봉을 통째로 쓴다.

같은 파일에 직전 커밋 이후 쌓인 시세 폴백/ENV 키 정리도 포함된다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Your Name
2026-08-19 22:09:03 +09:00
parent 6d2a706a48
commit d1dc274f0e
10 changed files with 2146 additions and 459 deletions

View File

@@ -17,6 +17,17 @@
**grep을 생략해도 되는 유일한 경우**: 주석/docstring 텍스트만 바꾸는 경우.
### 실매 코어 스모크 (중요 코드 수정 후 필수 1회)
엔진/호가/WS(구독·해제·spill·갭보정·freeze)/env/스키마를 고친 뒤에는 완료 보고 전:
```bash
python3 -u scripts/test_live_execution_validation.py
# 로그: logs/test_live_execution_validation_YYYYMMDD_HHMMSS.log
```
통과: `최종: 통과` + `👑 [최종 판정] 완결!`. 실패 시 고치고 1회만 재실행. 룰: `.cursor/rules/live-execution-validation.mdc`
---
## 🔍 수정 유형별 필수 grep 목록
@@ -59,7 +70,23 @@ grep -r "E_MIN_CHG_PCT" /home/hoon/kis_bot/kis_trader/engine/momentum_env_keys.p
호가 필터 나이: `WS_ORDERBOOK_FILTER_MAX_AGE_SEC`(기본 0=마지막 RAM) ≠ 저장 TTL `WS_ORDERBOOK_TICK_MAX_AGE_SEC`.
후처리 TPE 진입 격자: `OPTUNA_OB_ENTRY_SPREAD_*` / `RATIO_*` / `ASK_MULT_*` (`optuna_common.ensure_optuna_gate_env_defaults`).
꼬리 TPE 진입모드는 탐색 축이 아님(웹 체크 → 스터디 고정/순차 2회). `*_SKIP_HTS_SCAN_DUPES` true 금지.
꼬리 TPE 진입모드는 탐색 축이 아님(웹 체크 → 스터디 고정/순차 2회).
돌파 TPE 손절모드(fixed/atr)도 탐색 축이 아님(웹 체크 → 스터디 고정/순차 2회).
align 스터디는 `limit_atr_mult` 제외. fixed 스터디는 `atr_sl_*` 제외.
`*_SKIP_HTS_SCAN_DUPES` true 금지.
유령정리: `INQUIRE_PSBL_RVSECNCL_BEFORE_GHOST` · 잔고는 `hldg_qty`(보유)와 `ord_psbl_qty`(매도가능) 분리. 매도가능 0 ≠ ghost.
시세·호가 읽기 폴백 (실매=옵투나 같은 읽기):
- `LIVE_FEED_FALLBACK_MAX_AGE_SEC`(기본 3) = **그 틱의 체결시각(FID20/chetime) vs 이 서버 지금**. 1·2·3차 동일. 벤더끼리 시각 비교 금지.
- 분봉 쓰레기=`CANDLE_GARBAGE_FALLBACK`(기본 true). 그 분 그 소스 틱 0건이거나 전부 **봉 끝시각** 대비 읽기나이 초과면 그 WS 봉을 구멍 → 다음 소스 **봉 통째**. 한 봉 안 키움+한투 혼합 금지. 실매 링이 그 소스·그 분을 커버 못하면 유지.
- 한투 호가=`WS_ORDERBOOK_SAVE_KIS`: **2번째 앱키(`KIS_APP_KEY_OB_REAL`) 전용 세션만**. 키 없거나 start 실패 시 메인에 H0STASP0 붙이지 않음(시세 41 합산 금지).
- `LS_FEED_FALLBACK_SUBSCRIBE`(기본 true) → 후보보유영구grace 를 LS RAM 미러. **MINIMAL ON/OFF 무관** (`sync_targets` + split reconcile 둘 다). DB 틱 `should_persist_ls`(영구만) 이번 범위 밖.
- 키움 RAM/리스너/봉 skip = 읽기 나이. `ws_ticks` 적재는 기본 전부(`KIWOOM_TICK_LIVE_MAX_LAG_SEC=0`). 같이 내리지 말 것.
- 시세 REST 키 = `KIWOOM_WS_FORCE_REAL`(기본 true) 실키. `_get_kiwoom_creds``KIS_MOCK` 모의키로 가면 안 됨 (8001).
- `LS_GAP_FILL_CANDIDATES` 기본 OFF.
- `LS_WS_TICK_SAVE` 기본 false = `ls_ws_ticks` INSERT 만 OFF. `_tick_recorder` 를 같이 끄면 호가 틱동기도 0건. `LS_WS_ALSO_HOGA` 기본 true(UH1 구독). `LS_WS_ORDERBOOK_SAVE` 기본 true(구독 종목). 호가필터 `FILTER_MAX_AGE=0` 을 저장 TTL 과 다시 합치지 말 것.
- 매수 루프 전 종목 REST 금지. 매도 4차만 키움 `ka10007` + `SELL_WS_STALE_REST_COOLDOWN_SEC`. 한투 60초 캐시 금지.
- MM 체결 알림: `시세: kiwoom(1차) | 호가: ls(3차)`.
**확인 후 수정해야 할 곳 체크리스트**:
- [ ] `database.py` — CREATE TABLE에 컬럼 추가
@@ -204,6 +231,13 @@ grep -rn "trigger_e_confirm" /home/hoon/kis_bot --include="*.py"
## 🚨 이 프로젝트 특이 사항 (절대 무시 금지)
### 키움 0B catch-up / REG refresh
```
KIWOOM_TICK_LIVE_MAX_LAG_SEC (기본 5): FID20 vs 수신시각 lag 초과 → RAM·봉·호가동기·ws_ticks 미반영.
KIWOOM_TICK_TIME_MAX_LAG_SEC (기본 120): 같은 FID20 가 이 초 이상일 때만 아침 동결 예외.
KIWOOM_WS_REG_REFRESH (기본 1): 0은 LOGIN 일괄 REG 첫 청크만. 장중 추가 REG는 항상 1.
```
### SKIP_HTS_SCAN_DUPES 규칙
```
`*_SKIP_HTS_SCAN_DUPES` 는 사용자가 언급하기 전까지 false 유지.
@@ -231,11 +265,16 @@ database.py의 CREATE TABLE 컬럼에 있는 키 → 해당 config_* 테이블
database.py의 CREATE TABLE에 없는 키 → env_config_ext (key-value 테이블)
새 env 키 추가 시 두 경로 중 어디에 저장될지 확인 필요.
Optuna 후처리(실매 엔진 비영향): OPTUNA_POST_TOP_N / INCLUDE_MODE / INCLUDE_LIVE /
OB·WHIPSAW trial 수 · OPTUNA_OB_AXIS_TRIALS · 진입/익절/STOP 탐색 범위 —
OB·WHIPSAW trial 수 · OPTUNA_OB_COMBO_TRIALS_* · OPTUNA_OB_AXIS_TRIALS · 진입/익절/STOP 탐색 범위 —
ensure_optuna_gate_env_defaults → env_config_ext.
모듈: kis_trader/backtest/optuna_postprocess_topn.py · optuna_orderbook_recommend.py
· optuna_rerun_postprocess.py (구 JSON 「이 잡 후처리 재실행」)
후처리 앵커: gated TopN + stable TopN(표/적용) + mode + live(참고). 합의·과적합 가점은 gated+mode만.
웹 Optuna: 학습/gated/stable Top5 행에 안정점수(↑·만점없음·원) + 과적합%(↓·0~100). 1위 비교표·mode_combo 실측행은 표시 안 함.
진입 격자(2026-08-15): OPTUNA_OB_ENTRY_SPREAD 0.1~8 / RATIO 0.05~1.5 / ASK_MULT 1~80 / LOOKBACK 30분.
8방 스택(2026-08-16/17): OPTUNA_OB_COMBO_TRIALS_SINGLE=150 / DOUBLE=200 / TRIPLE=250.
적용=방 단위(build_combo_env_patch). 합의=8방 중 median PnL 최고 방(_consensus_from_anchors).
OPTUNA_OB_AXIS_TRIALS=500 은 축독립 잔여키(8방 미사용).
실매 필터 나이: WS_ORDERBOOK_FILTER_MAX_AGE_SEC(0) ≠ WS_ORDERBOOK_TICK_MAX_AGE_SEC(저장).
사진 한 장도 없으면: WS_ORDERBOOK_FILTER_REJECT_IF_EMPTY(기본 true) → 필터 ON일 때 안 삼.
```

View File

@@ -232,7 +232,8 @@ graph TD
| `optuna_common.py` | 672 | 🔬 백테스트 | `database`, `optuna_briefing`, `env` |
| `optuna_daily_trail_recommend.py` | 315 | 🔬 백테스트 | `param_search_apply_snapshot`, `env` |
| `optuna_mode_combo.py` | 278 | 🔬 백테스트 | `optuna_postprocess_topn`, `optuna_tpe_common`, `env` |
| `optuna_postprocess_topn.py` | — | 🔬 백테스트 | `optuna_orderbook_recommend`, `optuna_whipsaw_recommend` |
| `optuna_post_progress.py` | — | 🔬 백테스트 | (후처리 진행 로그, 웹 바) |
| `optuna_postprocess_topn.py` | — | 🔬 백테스트 | `optuna_orderbook_recommend`, `optuna_whipsaw_recommend`, `optuna_post_progress` |
| `optuna_rerun_postprocess.py` | — | 🔬 백테스트 | `optuna_postprocess_topn`, `optuna_momentum` |
| `optuna_search_space.py` | 89 | 🔬 백테스트 | `param_search_breakout`, `param_search_momentum`, `param_search_scalping` +1 |
| `optuna_tpe_common.py` | 218 | 🔬 백테스트 | |
@@ -258,9 +259,11 @@ graph TD
| `indicator_cache.py` | 412 | ⚙️ 엔진 | `ema_trend_filter`, `momentum_engine`, `env` |
| `limit_entry_common.py` | 196 | ⚙️ 엔진 | |
| `ls_feed_gate.py` | 20 | ⚙️ 엔진 | `env`, `ls_ws` |
| `feed_fallback.py` | — | ⚙️ 엔진 | `env` |
| `mid_enroll_entry_gate.py` | 241 | ⚙️ 엔진 | `env` |
| `orderbook_env.py` | 187 | ⚙️ 엔진 | `env` |
| `orderbook_filter.py` | 276 | ⚙️ 엔진 | `env`, `orderbook_cache`, `trigger_eval_recorder` |
| `live_sell_price.py` | — | ⚙️ 엔진 | `env` |
| `post_sell_candle_backfill.py` | 439 | ⚙️ 엔진 | `database`, `candle_rollup`, `env` +2 |
| `program_filter.py` | 229 | ⚙️ 엔진 | `env`, `program_cache`, `trigger_eval_recorder` |
| `strategy_eod.py` | 149 | ⚙️ 엔진 | |
@@ -728,6 +731,8 @@ graph TD
| `kis_trader/engine/momentum_tick_replay.py` | 620 | `kis_trader.backtest.shared_ticks`, `kis_trader.engine.momentum_engine`, `kis_trader.engine.whipsaw_filter`, `kis_trader.utils.env`, `kis_trader.utils.trade_time` | - |
| `kis_trader/engine/orderbook_env.py` | 187 | `kis_trader.utils.env` | - |
| `kis_trader/engine/orderbook_filter.py` | 276 | `kis_trader.utils.env`, `kis_trader.ws.orderbook_cache`, `kis_trader.ws.trigger_eval_recorder` | `orderbook_env`, `trigger_eval_collect` |
| `kis_trader/engine/live_sell_price.py` | — | `kis_trader.utils.env` | `base` 매도 현재가 |
| `kis_trader/engine/feed_fallback.py` | — | `kis_trader.utils.env` | 실매·옵투나 읽기 2초 폴백 |
| `kis_trader/engine/post_sell_candle_backfill.py` | 439 | `database`, `kis_trader.engine.candle_rollup`, `kis_trader.utils.env`, `kis_trader.utils.logger`, `kis_trader.ws.kis_ws` | - |
| `kis_trader/engine/program_filter.py` | 229 | `kis_trader.utils.env`, `kis_trader.ws.program_cache`, `kis_trader.ws.trigger_eval_recorder` | `trigger_eval_collect` |
| `kis_trader/engine/range_break_engine.py` | 460 | `database`, `kis_trader.backtest.range_break_portfolio_backtest`, `kis_trader.engine.range_break_env_keys`, `kis_trader.strategies.breakout`, `kis_trader.utils.trade_time` | - |
@@ -1027,7 +1032,17 @@ graph TD
> AST 재생성 시 이 섹션 유지.
- 판정: `kis_trader/engine/orderbook_filter.py` `_fetch_snapshot``WS_ORDERBOOK_FILTER_MAX_AGE_SEC`(기본 0). 사진 없음+필터 ON → `WS_ORDERBOOK_FILTER_REJECT_IF_EMPTY`(기본 true).
- 실매 매수·매도 현재가: `WSManager.get_price()` 생략=읽기 폴백 나이(`LIVE_FEED_FALLBACK_MAX_AGE_SEC` 기본 3초)로 메인→2차→3차. 한투 호가는 `kis_ws_ob`(2키)만 — 메인 `H0STASP0` 폴백 금지. 분봉 pick: `CANDLE_GARBAGE_FALLBACK` + `dedupe_by_read_pairs`(메인 WS→2차→LS→REST→rollup). `max_age_sec=None` 만 마지막 RAM(`get_price_last`). 매도 4차는 `live_sell_price.py` 키움 ka10007 + 쿨다운(시세 실키, `KIS_MOCK` 모의키 금지). 한투 `inquire_price` 60초 캐시 금지. EOD는 마지막 WS→REST→매수가 폴백. 호가 `get_orderbook_snapshot` 도 같은 폴백 체인 — `FILTER_MAX_AGE=0` 과 저장 TTL 을 합쳐 만료=`None`=필터 ON 통과 구멍을 만들지 말 것.
- 저장: `trigger_snapshot_recorder``WS_ORDERBOOK_TICK_MAX_AGE_SEC`(기본 30). **공유 금지.**
- 캐시: `orderbook_cache.get(..., max_age_sec)``<=0` 이면 나이 무시.
- 후처리 격자: `optuna_common.ensure_optuna_gate_env_defaults` `OPTUNA_OB_ENTRY_*` / `ASK_MULT_*`.
- 구 JSON 후처리만: `optuna_rerun_postprocess.py`.
## 수동 노트: 루트 레거시 파일 (2026-08-17)
> AST 재생성 시 이 섹션 유지.
실매 `kis_trader.main` 이 안 쓰는 파일은 `remove/` (**나중에 통째 삭제**).
웹·알람이 **아직 쓰는** `holding_bot` / `news_analyzer` / `kis_long_ver*``legacy/` (`ensure_legacy_root`).
`remove` 를 지워도 `legacy/` 는 남긴다.
상세 `legacy/README.md`, `remove/README.md`.