Files
kis_trader/docs/옵투나.md
Your Name 1387fbdf47 feat(옵투나): score·min_trades·후처리 재탐색 및 웹 job 개선
PnL/(MDD+ADD) score·legacy 정렬·거래일×min_trades 게이트를 공통화한다.
후처리 ob_modes·study store·4전략 TPE 순차 스크립트와 문서를 갱신한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 16:46:27 +09:00

211 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Optuna 파라미터 탐색 및 호가 수급 최적화 구조 가이드 (옵투나.md)
이 문서는 **현재 코드** 기준 Optuna TPE·호가 스터디 스위치·후처리를 정리한다.
관련: `docs/호가.md` §8 · `optuna_tpe_common.py` · `optuna_breakout_tpe_space.py` · `optuna_postprocess_topn.py` · `optuna_orderbook_recommend.py`
최종 갱신: **2026-08-28** (구 2026-08-15 「Stage1=호가 축 없음」서술은 **폐기** — 아래 §1이 진실)
---
## 1. 현재 구조 (코드 진실 · 2026-08-28)
### 1-0. 한 줄
| 구분 | 무엇을 하냐 |
|------|-------------|
| **본 TPE (Stage 1)** | 차트 축 + (기본) **진입 호가·휩쏘를 trial에서 같이 평가** |
| **사후 8방 (Stage 2)** | 본 TPE에 호가축이 **켜져 있으면 기본 스킵**. 구 타점-only 모드일 때만 자동 |
`OPTUNA_TPE_INCLUDE_ORDERBOOK` 기본 **true** (`optuna_tpe_common.optuna_tpe_include_orderbook`).
→ 본 trial이 이미 호가를 쓰므로, 사후 8방으로 또 걸러 “성적 사기” 나지 않게 `_run_ob_whipsaw_full()`**기본 OFF**.
강제만: `OPTUNA_POST_FORCE_OB_WHIPSAW=true`.
구 문서의 「Stage1=캔들만 · 호가ON/OFF 넣지 않음」은 **옛 설계**. 지금은 기본이 반대다.
```
[기본 경로]
Stage 1 TPE — 차트 + 호가 ON/OFF(또는 돌파=스터디 고정) + 임계값 + (전략별) 휩쏘
Stage 2 사후 8방 — INCLUDE_ORDERBOOK=true 이면 생략 (FORCE 만 예외)
[구 타점-only]
OPTUNA_TPE_INCLUDE_ORDERBOOK=false
→ Stage 1 차트만 · Stage 2 사후 8방 기본 ON
```
---
## 1-1. 전략별 호가 ON/OFF 넣는 방식 (핵심)
**문제:** 한 스터디에 호가 ON/OFF를 categorical로 섞으면, ON은 거래수가 줄고 TPE가 **OFF만 편애**하기 쉽다.
(거래수·PnL 랭킹이 OFF에 유리 → ON 임계값은 사실상 미탐색)
| 전략 | 본 TPE 호가 | 방식 | 코드 |
|------|-------------|------|------|
| **돌파** | **스터디 스위치** | `--orderbook-filter off\|on` · 웹 `ob_modes` · **한 스터디에 ON/OFF 안 섞음** · ON 스터디만 스프/잔량/벽 축 | `optuna_breakout_tpe_space.py` |
| **꼬리** | trial **categorical** | `_orderbook_filter_enabled` [False,True] + 임계값 매 trial | `suggest_orderbook_entry_tpe` |
| **모멘텀** | 동일 categorical | 위와 같음 | `optuna_momentum_tpe_space.py` |
| **스캘프** | 동일 categorical | 위와 같음 | `optuna_scalping_tpe_space.py` |
돌파 손절모드(fixed/atr)도 **스터디 스위치**(trial 축 아님). 웹은 손절×호가 **최대 4순차**.
꼬리 진입모드(align / limit_atr)도 **스터디 고정·순차** (한 TPE에 categorical 혼입 없음) — §3.
### 1-2. 분리 vs trial 수 늘리기 (결과 동일 여부)
| | 한 스터디 N trial (ON/OFF 섞음) | 스터디 분리 (off N + on N) |
|--|-------------------------------|----------------------------|
| TPE 예산 | OFF에 몰림 | ON·OFF **각자** 탐색 |
| best | 대개 OFF 쪽 | OFF best / ON best **따로** 비교 |
| 동등성 | **≠** 분리 2×(N/2) | 의도적으로 다른 실험 |
**400번 섞어 돌리기 ≠ off 200 + on 200.**
숫자만 늘려도 ON 공간(임계값)은 거의 안 볼 수 있다.
돌파처럼 **분리가 맞고**, 꼬리·모멘텀·스캘프도 **같은 편향**이 난다 (아직 categorical).
권장:
1. **돌파와 동일** — 전략별 `off` / `on` 스터디 스위치 (또는 웹 순차)
2. 또는 `OPTUNA_TPE_INCLUDE_ORDERBOOK=false` → 차트만 + **사후 8방** (구 Stage1/2 문서 경로)
---
## 2. 진입 후처리 격자 (사후 8방 · Stage 2)
`ensure_optuna_gate_env_defaults``OPTUNA_OB_*`.
**본 TPE 호가축 OFF일 때만** 기본 자동 실행 (§1-0).
`OPTUNA_OB_COMBO_TRIALS_SINGLE`(기본 150) / `DOUBLE`(200) / `TRIPLE`(250) = 8방 중 켜진 축 개수별 trial.
방 000은 TPE 없음. 모멘텀·돌파 합 150×3+200×3+250=1,300. 꼬리·스캘프는 진입 on/off만(150).
`OPTUNA_OB_AXIS_TRIALS`(500)는 축 독립 탐색용 잔여 키 — 8방 경로에서는 안 씀.
| 키 | 널널 검사 기본 (2026-08-15) |
|----|------------------------------|
| `OPTUNA_OB_ENTRY_SPREAD_MIN` / `MAX` | 0.1 ~ 8.0 |
| `OPTUNA_OB_ENTRY_RATIO_MIN` / `MAX` | 0.05 ~ 1.5 |
| `OPTUNA_OB_ENTRY_ASK_MULT_MIN` / `MAX` | 1.0 ~ 80.0 (매도벽, L3 `ask_qty_l3`) |
| `OPTUNA_OB_LOOKBACK_MIN` | 30 (분) |
잔여 체결 &lt; 원본 **30%** → trial 무효. 너무 센 컷은 고르지 못하게 하는 가드.
결과는 `orderbook_filter_enabled=True` 고정(후처리가 “끌지”를 탐색하지 않음).
apply 패치에 벽이 있으면 `{전략}_ORDERBOOK_ENTRY_ASK_MAX_MULT`.
구 JSON만 Stage 2 강제:
```bash
# INCLUDE_ORDERBOOK=true 여도 강제하려면
# OPTUNA_POST_FORCE_OB_WHIPSAW=true 또는 rerun 스크립트 경로 확인
python3 -u kis_trader/backtest/optuna_rerun_postprocess.py \
--result-json kis_trader/backtest/results/optuna_<전략>_tpe_<TS>.json
# --apply-best 없음. 실매 DB 안 바뀜.
```
**후처리 자동 실행 게이트** (`kis_study_trials` / `--study-trials`):
끝난 기준 = **시도 수** `COMPLETE + PRUNED + FAIL` ≥ 목표.
`tp_max < tp` 같은 pruned도 시도로 친다. COMPLETE만 세서 스킵하지 않음 (2026-08-21).
백테/옵투나 호가·틱 **3차 LS** 나이 = `LIVE_FEED_FALLBACK_MAX_AGE_SEC`(기본 3초).
`trigger_snapshot_loader` / `BT_TICK_LS_THIRD_FALLBACK` — 실매 RAM 폴백과 동일 env.
---
## 3. 꼬리 TPE 진입모드 (웹 체크)
엔진 실매 기본은 `TAIL_ENTRY_MODE`**`limit_atr`**.
**Optuna TPE는 진입을 탐색하지 않고 스터디마다 고정.**
웹 Optuna 탭: **align** / **limit_atr** 체크.
- 기본: align만.
- 둘 다: **스터디 2개 순차** (한 TPE에 categorical 혼입 없음). `--apply-best` 없음.
CLI: `--entry-mode align|limit_atr` · 순차 `TAIL_OPTUNA_ENTRY_MODES="align limit_atr"`.
---
## 4. 실매 DB 적용 분리
| 구분 | 차트 캔들 | 호가 |
|------|-----------|------|
| `--apply-best` (기본 미사용) | 차트 축 | 호가 자동 각인 아님 |
| 웹 「이 방 적용」(사후 8방이 있을 때) | 차트 + **8방 중 하나** | 켠 축만 ON |
| 웹 TopN 「적용」upto=full | **본 trial** 타점·익절·손절·**호가·휩쏘** 유지 | 사후 8방으로 OFF 덮어쓰기 **안 함** |
| `whipsaw` | 차트 + 휩쏘 | 8방 밖 |
| 구 upto `entry/exit/stop` | 별칭 → `e` / `ex` / `exs` | 하위호환만 |
**8방 ≠ 표 5열.** 8 = 진입×익절×손절(2³).
후처리 **재실행만** 하면 JSON만 갱신. DB는 안 바뀜.
---
## 5. 웹 표기
`out_data["orderbook_recommend"]` · `postprocess_topn` · `tpe_includes_orderbook` · `post_run_ob_whipsaw`.
### 5-1. 앵커별 8방 표 (사후가 돌았을 때만)
각 Top5 「상세」 아래 **호가 8방** 표 + 방마다 「이 방 적용」. 휩쏘는 8방 밖 별도 행.
| 방 | 켜진 축 |
|---|---|
| 000 | 타점만(호가 OFF) |
| 100 / 010 / 001 | 1축 |
| 110 / 101 / 011 | 2축 스택 |
| 111 | 진입+익절+손절 |
본 TPE 호가축 ON이면 보통 이 표가 **비거나 생략**되고, Top 행에 **본 trial 호가 ON/OFF·임계**가 표시된다.
### 5-2. 합의(consensus) — 8방 기준
상세 위 **`합의(gated+mode)`** 한 줄 (사후 8방이 있을 때):
- 사후합격 Top5 + mode_combo 앵커의 **8방 결과**만 모음 (stable·live 제외)
- **median PnL이 가장 높은 방 1개**
- 구 JSON(8방 없음)이면 축분리 median 폴백 + 「후처리 재실행 권장」
### 5-3. 돌파 웹 스위치
Optuna 탭: 돌파 **호가 off / on** 체크(스터디 스위치). 손절 fixed/atr × 호가 → 최대 4순차.
study 이름 예: `…_fixed_ob_off_…` / `…_atr_ob_on_…`.
### 5-4. 레거시(정리·미삭제)
| 항목 | 상태 |
|------|------|
| `OPTUNA_OB_AXIS_TRIALS`(500) | 8방 미사용 잔여키 |
| `entry`/`exit`/`stop` JSON 중첩 | 구 JSON·apply 호환용 |
| 모멘텀 `pattern_*`·시가컷 | 그리드/UI 잔여 |
| 「Stage1=호가 축 없음」문구 | **폐기** — §1 참고 |
| `docs/layered_exit_design.md` | → `docs/호가.md` |
---
## 6. env 스위치 요약
| 키 | 기본 | 의미 |
|----|------|------|
| `OPTUNA_TPE_INCLUDE_ORDERBOOK` | true | 본 TPE에 호가 축(꼬리·모멘·스캘 categorical / 돌파는 별도 스위치) |
| `OPTUNA_TPE_INCLUDE_WHIPSAW` | true | 본 TPE 휩쏘 축 |
| `OPTUNA_POST_RUN_OB_WHIPSAW` | true | INCLUDE_ORDERBOOK=**false** 일 때 사후 8방 |
| `OPTUNA_POST_FORCE_OB_WHIPSAW` | false | INCLUDE_ORDERBOOK=true 여도 사후 8방 강제 |
| `LIVE_FEED_FALLBACK_MAX_AGE_SEC` | 3 | 실매·옵투나 호가/틱 3차 나이 |
| `BT_TICK_LS_THIRD_FALLBACK` | true | 옵투나 틱 3차 LS |
| `PARAM_SEARCH_OPTUNA_N_JOBS` | 1 | Optuna 병렬 **고정** 정수 (비율 아님) |
| `OPTUNA_MIN_TRADES_PER_DAY` | **2** | 모멘·돌파·스캘 등: `min_trades` = 거래일×2 |
| `OPTUNA_TAIL_MIN_TRADES` | **1** | **꼬리만** 탐색·gated 공통 min_trades (기간 무관) |
| `OPTUNA_SCORE_MDD_ADD` | **10000** | 새 score 분모 `MDD + ADD` |
| `OPTUNA_SCORE_MDD_FLOOR` | **10000** | (구) score 분모 `max(MDD, FLOOR)` — sort_by=score_legacy |
| `OPTUNA_SCORE_TRADE_SOFT_DAYS` | **2** | soft_n 기본 = 하루최소건×이 값 (기본 2×2=4). `OPTUNA_SCORE_TRADE_SOFT_N` 이 있으면 그쪽 우선 |
### 6-1. min_trades · 일평균 · 승리식 (2026-08-28)
- **웹 Optuna** `min_trades` = `resolve_optuna_min_trades`**꼬리=1 고정**, 그 외 거래일×하루2. CLI `--min_trades` 직접 지정 시 유지.
-**일평균** = `total_pnl ÷ 기간 거래일` (`period_daily_avg_pnl`).
#### 승리식 (전 전략 공통 · 웹 셀렉트)
| 화면 | `--sort-by` | 식 |
|------|-------------|-----|
| **수익·낙폭·표본 (기본)** | `score` | `(PnL / (MDD + ADD)) × √(min(거래수, soft_n) / soft_n)` |
| **(구) PnL/MDD하한** | `score_legacy` | `PnL / max(MDD, FLOOR)` — 전략별 `*_SCORE_MDD_FLOOR` 또는 `OPTUNA_SCORE_MDD_FLOOR` |
| 일평균 | `daily_avg` | `PnL ÷ 거래일수` |
| 총손익 | `pnl` | `PnL` |
- **구 score** 는 웹 승리식 `(구) PnL/MDD하한` / CLI `--sort-by score_legacy` 로 선택.
- 웹에 승률 단독 승리식 없음 (CLI `win_rate`만).
- 이미 끝난 study는 옛 목적함수 → **새 study-name으로 다시** 돌려야 새 식이 적용됨.