PnL/(MDD+ADD) score·legacy 정렬·거래일×min_trades 게이트를 공통화한다. 후처리 ob_modes·study store·4전략 TPE 순차 스크립트와 문서를 갱신한다. Co-authored-by: Cursor <cursoragent@cursor.com>
523 lines
28 KiB
Markdown
523 lines
28 KiB
Markdown
# 호가.md — 진입 필터 · 익절 지정가 · IOC(13) · 수익구간 호가매도 · 손실구간 호가매도
|
||
|
||
이 문서는 대화·코드·DB 기준으로 **호가 관련 기능을 역할별로 구분**해 정리한다.
|
||
이름이 비슷해도 **서로 다른 스위치**이므로 혼동하지 말 것.
|
||
아래에서는 **L3 / SL-OB 같은 줄임말을 쓰지 않고** 한글로 푼다.
|
||
|
||
---
|
||
|
||
## 0-0b. 벤더 공통: 실시간 호가(RAM) vs 틱동기 호가(DB) — 2026-08-25
|
||
|
||
**UH3 없음.** LS 체결=`US3`, 호가=`UH1`. (한투=`H0STCNT0`/`H0STASP0`, 키움=`0B`/`0D`)
|
||
|
||
| | RAM (실매 필터가 봄) | DB 호가 (`*_ORDERBOOK_SAVE_MODE=tick` 기본) |
|
||
|--|----------------------|-----------------------------------------------|
|
||
| **한투** | H0STASP0 올 때마다 | **H0STCNT0(체결) 1건** → 그때 RAM 1장 → `ws_orderbook` |
|
||
| **키움** | 0D 올 때마다 | **0B(체결) 1건** → 그때 RAM 1장 → `ws_orderbook` |
|
||
| **LS** | UH1 올 때마다 | **US3(체결) 1건** → 그때 RAM 1장 → `ls_ws_orderbook` |
|
||
|
||
- `MODE=interval` = 호가 메시지(UH1/0D/ASP0) 쪽에서 간격 저장. **세 증권사 공통 스위치**이지 LS 전용 신기능이 아님.
|
||
- `LS_WS_TICK_SAVE` = `ls_ws_ticks` INSERT. **구독 전체**(영구∪후보∪보유) — 호가와 동일. 봉·VI만 영구.
|
||
- `BT_TICK_LS_THIRD_FALLBACK`(기본 true) = 백테/옵투나 틱: 같은 초 1·2차 없으면 `ls_ws_ticks` (나이=폴백 3초).
|
||
- 실매는 DB를 안 보고 **RAM**. DB는 백테/옵투나 재현용 샘플.
|
||
|
||
### 2026-08-25 LS `ls_ws_orderbook` 0건 — 원인 (코드)
|
||
|
||
의도(US3 틱동기)는 한투·키움과 **같음**. 문제는:
|
||
|
||
1. 장중 **Bye ≈ 매분** → OPEN 직후 구독 복구가 `sends≈1`(JIF만)로 **미완료**.
|
||
2. 서버에 **US3/UH1 REG가 안 붙음** → 체결·호가 수신 없음 → tick 모드라 DB 0.
|
||
3. 미완료인데도 `recovering=False` 로 풀리던 버그 → 워치독이 틱없음으로 또 끊음 (악순환).
|
||
|
||
**수정(동작) 2026-08-27:** `ls_ws.py` + `ls_token.py`
|
||
|
||
1. **OPEN마다 `_ws_reconnect_step=0` 리셋 제거** — Bye 플랩 시 1초 연타 원인.
|
||
2. **조기 CLOSE/Bye 서킷** (`LS_WS_STABLE_OPEN_SEC`/`EARLY_BYE_STREAK`/`BYE_CIRCUIT_SLEEP_SEC`) — streak 도달 시 로컬 토큰 무효화+1회 재발급 후 대기.
|
||
3. **REG 미완료면 `recovering` 유지** — `reg_ok`일 때만 해제·step/streak 리셋.
|
||
4. adhoc에서 `fetch_ls_access_token` 강제 발급 **금지** (봇 프로세스 토큰 무효화).
|
||
|
||
`LS_WS_ENABLED` / TICK·ORDERBOOK_SAVE / 3차 spill **끄지 않음** (옵션1).
|
||
|
||
#### Bye ≈ 매분 — 로그·git으로 좁힌 것 (2026-08-25 journal)
|
||
|
||
| 비교 | Aug24 (정상 수집) | Aug25 (DB 0) |
|
||
|------|-------------------|--------------|
|
||
| 시간대당 `LSWebSocket.*Bye` | **0** (09~15시) | **~54~59**/시간 (≈매분) |
|
||
| 같은 시간 `LS WS watchdog` | 드묾(장초 1회 등) | **장중 시간대 0** |
|
||
| OPEN 복구 | `sends≈15~339` 완료 | 거의 항상 `sends≈1` (JIF만) |
|
||
|
||
- Bye 페이로드: `opcode=8` + `\x03\xe8Bye` = WebSocket **정상종료(1000) + 서버가 보낸 Bye**. 우리 워치독 문구(`틱 없음 → 강제 재연결`)와 **무관**.
|
||
- 패턴: `OPEN(sends≈1)` → 약 **60초** 유지 → `Bye`+`CLOSE` → **같은 초~1초 안**에 다시 `OPEN(sends≈1)`. 침묵 워치독(grace30+silence45≈75초)보다 **먼저** 서버가 끊음.
|
||
- 05:07 봇 재시작 이후 **07:00부터** 이미 동일 루프(장 시작 전). Aug24 18:37은 `KR=169 sends≈339`로 복구 성공 이력 있음.
|
||
- `sends≈1` + KR 수백: REG 루프가 JIF 직후 `_opened` 해제되어 abort된 **형태**(가설).
|
||
검증 키: CLOSE `ws_same=False`가 OPEN/REG 중 끼는지, 복구 로그 `abort_why=opened_cleared`. **미확정**.
|
||
|
||
**아직 단정 금지:** 이중세션(조건식 AFR vs 시세)·계정 한도·서버 idle 정책만의 단독 원인. 위는 “워치독 매분 킥”은 **아님**까지 확정.
|
||
|
||
---
|
||
|
||
## 0-0. 「호가 쌓기」는 설계가 아니다 (헷갈림 방지)
|
||
|
||
**DB에 호가 한 장씩 넣는 것(저장)은 이미 돌아가는 수집 기능이다.**
|
||
새 매매 로직·새 설계 항목이 **아니다.**
|
||
|
||
| 말 | 진짜 뜻 |
|
||
|----|---------|
|
||
| “호가 INSERT / 쌓기” | 봇이 장중 호가를 `ws_orderbook` / `ls_ws_orderbook` 에 **기록**하는 중 (지금 하고 있음) |
|
||
| “필터·호가매도 끄고 쌓기만” | **끄는 건 매매 스위치**다. 저장은 켠 채, **매수 막을지 / 호가로 팔지** 스위치만 OFF |
|
||
| 설계에 적어 둔 이유 | “저장이 새 기능”이 아니라 **스위치 켜서 실험하지 말자**는 **운영 메모** |
|
||
|
||
초등 비유:
|
||
일기장에 날씨를 매일 적는 것 = **쌓기(이미 함)**.
|
||
일기 보고 “비 오면 우산 사기” 규칙을 켜는 것 = **필터·호가매도 ON**.
|
||
규칙은 아직 끄고, 일기만 모으자는 뜻이었다.
|
||
|
||
### 2026-08-03 장일 확인 (틱 동기)
|
||
|
||
| 테이블 | 오늘 | 방식 | 판정 |
|
||
|--------|------|------|------|
|
||
| `ls_ws_orderbook` (`ls_uh1`) | **약 274만 행** / 88종목 | 체결 1건 ≈ 호가 1장 (비율 ≈ **0.999**) | **틱 동기화로 쌓임 ✅** |
|
||
| `ws_orderbook` (`kiwoom_0d`) | **약 12.5만 행** / 38종목 | 간격 중앙값 **약 3초** | 연속 저장 ✅ · **틱 1:1은 아님** |
|
||
| `ws_orderbook` (`filter_eval`) | 29행 | 진입 판정 순간 1장 | 필터 판정 로그용 |
|
||
|
||
모멘텀 실매 매수 **오늘 0건** (유니버스 이슈) → 매도쪽 측정 스크립트는 **오늘 모수 없음**.
|
||
최근 모수: **2026-07-31** 모멘텀 7건 + 키움 호가 (§4 측정).
|
||
|
||
---
|
||
|
||
## 0-1. 운영 메모 (2026-08-15 기준 · DB가 진실)
|
||
|
||
아래는 **옛 “OFF 유지” 메모를 폐기**한 현재 운영이다. 스위치는 웹「운영 설정」·전략탭 DB를 본다.
|
||
|
||
1. **호가 저장** — ON. 시계열 TTL = `WS_ORDERBOOK_TICK_MAX_AGE_SEC`(코드 기본 **30초**. UI에 예전에 “3초”라고 적혀 있던 것은 **저장용 힌트 오류**였음).
|
||
2. **매수 호가필터** `*_ORDERBOOK_FILTER_ENABLED` = **ON** (모멘텀·돌파·스캘프·꼬리). 숫자는 전략별(스프·잔량·매도벽).
|
||
3. **수익구간 호가매도** `MOMENTUM_EXIT_OB_ENABLED` / `BREAKOUT_EXIT_OB_ENABLED` = 운영 **ON**인 적이 있음 → **DB 확인**. 본 문서 옛 “OFF 유지”는 폐기.
|
||
4. **손절호가** `*_STOP_OB_ENABLED` = 마찬가지로 **DB 확인** (코드 배선됨).
|
||
5. **필터 RAM 나이 (B안, 2026-08-15)**
|
||
- `WS_ORDERBOOK_FILTER_MAX_AGE_SEC` 기본 **0** = 마지막 RAM 호가로 **반드시 검사**. REST 없음.
|
||
- 저장 TTL과 **공유 금지**. 예전에 둘 다 `TICK_MAX_AGE`라서 30초 넘으면 `None` → 필터 ON인데 **통과(구멍)**.
|
||
- 한 번도 호가가 없고 필터 ON이면 **안 삼** (`WS_ORDERBOOK_FILTER_REJECT_IF_EMPTY` 기본 true). REST 없음. 다음 매수 검사에서 사진 있으면 다시 봄.
|
||
한산해서 사진이 **안 바뀌는 것**과 다름 — 마지막 사진이 있으면 그걸로 검사.
|
||
6. **Optuna Stage2(후처리)** 는 캔들 TPE와 **별도**. 진입 격자·매도벽 축은 `docs/옵투나.md` · §8. `--apply-best` 없이 후처리만 재실행 가능 (`optuna_rerun_postprocess.py`).
|
||
|
||
---
|
||
|
||
## 0. 한눈에 구분
|
||
|
||
| 구분 | 언제 | 하는 일 | 대표 키 | 상태 |
|
||
|------|------|---------|---------|------|
|
||
| **호가 저장(쌓기)** | 장중 항상 | DB에 호가 기록 | `LS_WS_ORDERBOOK_SAVE` / `WS_ORDERBOOK_SAVE_ENABLED` 등 | **이미 동작 중** · 설계 아님 |
|
||
| **매수 호가필터** | 사기 직전 | 호가 나쁘면 **안 삼** | `{전략}_ORDERBOOK_FILTER_ENABLED` | 코드 있음 · **2026-08 운영 ON** |
|
||
| **익절 호가 ON** | 이미 매도 신호 난 뒤 | 익절을 **지정가**로 냄 | `SELL_USE_ORDERBOOK_ON_PROFIT` | 운영 ON |
|
||
| **매도 시장가 IOC(13)** | 시장가로 팔 때 | 실전만 13 | `USE_MARKET_IOC_SELL` | 운영·코드 분리 |
|
||
| **수익구간 호가매도** | 들고 있을 때 **이득 난 뒤** | 사려는 사람 줄면 **미리 이익 실현** | `MOMENTUM_EXIT_OB_*` / `BREAKOUT_EXIT_OB_*` | 코드 있음 · **시드 OFF / 운영은 DB** |
|
||
| **손실구간 호가매도** | 들고 있을 때 **손해 난 뒤** | 사려는 사람 줄면 **큰 손절 전에 먼저 나옴** | `MOMENTUM_STOP_OB_*` / `BREAKOUT_STOP_OB_*` | 배선 완료 · **시드 OFF / 운영은 DB** |
|
||
|
||
거래내역 **「매수호가」** = 살 때 근처 · **「매도호가」** = 팔 때 근처. 서로 다른 칸.
|
||
|
||
### 수익구간 호가매도 vs 손실구간 호가매도 (초등)
|
||
|
||
둘 다 “호가판에 사려는 사람이 확 줄었다”를 본다.
|
||
**언제 쓰느냐**만 다르다.
|
||
|
||
| | 수익구간 호가매도 | 손실구간 호가매도 |
|
||
|--|------------------|------------------|
|
||
| 내 장부 | **이미 조금 이득**일 때만 | **이미 조금 손해**일 때만 |
|
||
| 하는 일 | “이득 났는데 분위기 깨지면 이익 챙기고 나감” | “손해인데 분위기 더 깨지면, −몇% 손절선까지 안 가고 먼저 나감” |
|
||
| −몇% 손절선 | **안 건드림** | **공식은 그대로** · 그 **앞에서** 따로 한 번 더 봄 |
|
||
| 지금 | 코드 있음 · ENABLED는 **DB** | 코드 있음(`STOP_OB`) · ENABLED는 **DB** |
|
||
| 파람서치 | 캔들 TPE 본체에는 안 넣음. **후처리** Stage2 (`docs/옵투나.md`) | 후처리 STOP 축 |
|
||
|
||
---
|
||
|
||
## 1. 매수 호가필터 (진입)
|
||
|
||
### 역할
|
||
- TRIGGER 단계에서 스프레드·매수/매도 잔량비·얇은 호가·매도벽 등으로 **매수 차단**.
|
||
- **청산 신호가 아님.**
|
||
|
||
### 키 (전략별)
|
||
글로벌 `ORDERBOOK_*` 수치 폐기 → 전략 prefix만 사용.
|
||
|
||
- `SCALP_ORDERBOOK_FILTER_ENABLED`
|
||
- `MOMENTUM_ORDERBOOK_FILTER_ENABLED`
|
||
- `BREAKOUT_ORDERBOOK_FILTER_ENABLED`
|
||
- `TAIL_ORDERBOOK_FILTER_ENABLED` (SHORT → TAIL)
|
||
|
||
임계 예: `*_ORDERBOOK_MAX_SPREAD_PCT`, `*_MIN_BID_ASK_RATIO`, `*_ENTRY_BID_LEVELS` 등.
|
||
|
||
### 코드
|
||
- 판정 공통: `kis_trader/engine/orderbook_filter.py`
|
||
- env: `kis_trader/engine/orderbook_env.py`
|
||
- 웹: 예) 모멘텀 「호가필터 ON」(`mom_ob_filter`) — **이 백테 회차 진입 필터**
|
||
|
||
### 왜 전략별로 갈라졌나
|
||
스캘프·모멘텀·돌파·꼬리는 호가 의미가 달라 **ON/OFF·임계를 전략마다** 둔다.
|
||
판정 함수는 공유, **설정 키만** 분리.
|
||
|
||
### 수집 vs 필터
|
||
- `KIWOOM_WS_ORDERBOOK_ENABLED` — 호가 WS 수신
|
||
- 시계열 `kiwoom_0d` / LS `ls_ws_orderbook` — 저장(필터 OFF여도 저장 가능)
|
||
- 필터 ON = 그 스냅으로 **매수 탈락 적용**
|
||
|
||
### 켜면 매매가 확 줄어드는 이유 (조건식 탓 아님)
|
||
- HTS/조건식 = “살 만한 **이야기**(타점)” 선별
|
||
- 호가필터 = “지금 이 순간 **대기표가 들어가기 좋은가**” 추가 게이트
|
||
- 모멘텀·돌파류는 급등·얇은 종이 많아, 통과 직후 호가가 **스프레드 큼 / 살힘 약함 / 매도벽**인 경우가 흔함
|
||
- 거래내역 「매수호가」에 **간격큼·살힘 0.1x대**가 보여도 **데이터가 이상한 게 아니라** 그 타점 유동성이 그런 것
|
||
→ “조건식이 잘못 뽑았다”로 단정하지 말 것. **유동성 게이트가 한 겹 더 있어서** 건수가 줄어든다.
|
||
|
||
### RAM(실매) vs DB 저장(백테)
|
||
|
||
| | 실매 필터 ON | 백테 필터 ON |
|
||
|--|--------------|--------------|
|
||
| 보는 것 | **RAM** `get_orderbook_snapshot(max_age=FILTER_MAX_AGE)` | DB 스냅 / `filter_eval` 재생 |
|
||
| 나이 | `FILTER_MAX_AGE=0` → **마지막 장**으로 검사 (저장 30초와 무관) | 체결 시각 근처 행 |
|
||
| 스냅 한 번도 없음 | 필터 ON이면 **안 삼**(기본). REST 없음 | 재생·본체 없으면 그 타점 못 탐 |
|
||
|
||
저장 TTL(`TICK_MAX_AGE`, 기본 30)은 **0D 덤프·가짜 채움 방지**용.
|
||
필터가 이걸 같이 쓰면 만료=`None`=통과 구멍이 난다 (2026-08-14 실매에서 확인).
|
||
|
||
거래내역 「매수호가」칸의 `+N초 탈락-매도벽` 은 **근처 스냅 스탬프**이지, 그 초가 필터를 통과했다는 뜻이 아님.
|
||
|
||
### 임계값(0.45 · 0.85 …) — 업계 표준 아님
|
||
`orderbook_env.py` **프로젝트 시드/시작값** (Optuna·웹에서 변경 가능). “보편적으로 다들 0.45”가 **아님**.
|
||
|
||
| 기본 | 의미 | 체감 |
|
||
|------|------|------|
|
||
| 스프레드 **0.45%** (시드) | `(ask−bid)/mid` 상한 | 운영 모멘텀은 **3.0** 근처도 있음. `orderbook_env` 시드 ≠ 실매 DB |
|
||
| 잔량비 **0.85** | 상위 N호가 매수÷매도 | 살 쪽이 거의 비슷해야 통과 |
|
||
| 깊이 **1.2** | 필요수량 ×1.2 ≤ 매수호가 합 | |
|
||
| 매도벽 **3.0** | 매도잔량 과다 컷 | |
|
||
|
||
AND라서 하나라도 깨지면 탈락 → 체감이 셈.
|
||
|
||
### 이 필터가 굳이 필요한가? → **필수 아님**
|
||
| 목표 | 선택 |
|
||
|------|------|
|
||
| 거래 수·백테 건수 유지 | **OFF** 또는 임계 완화 |
|
||
| 얇은 호가·큰 갭 진입을 줄이고 싶다 | **ON** (2026-08 실매는 이 쪽) |
|
||
| 조건식이 이미 유동성 좋은 종만 | 필터 체감↓ |
|
||
|
||
- **진입 필터** = 살까 말까
|
||
- **익절 호가 ON** = 이미 산 뒤 어떻게 팔까 (`SELL_USE_ORDERBOOK_ON_PROFIT`) — **다른 스위치**
|
||
호가가 안 좋게 찍힌다는 사실 자체가 “필터 필수” 근거가 아님.
|
||
안 좋은 호가에 **들어가느냐를 막을지** 선택일 뿐. 과하면 끄거나 숫자만 푼다.
|
||
|
||
### 키움 조건 vs LS 조건 (진입·수익구간 호가매도 공통)
|
||
- `kiwoom_condition` / `condition` → 호가 **키움 0D RAM** · DB `ws_orderbook`
|
||
- `ls_condition` → 호가 **LS UH1 RAM** · DB `ls_ws_orderbook` (키움으로 메우지 않음)
|
||
수익구간·손절호가도 같은 스냅샷 API. **2026-08 운영:** 4전략 유니버스는 키움 조건이 본체. 호가 구독/읽기 체인 **kis(2키 OB 41)→kiwoom→ls(3차)**. LS는 spill 3차지 “LS 전용 종목”이 아님 (`LIVE_OB_PROVIDER=kis`, `WS_OB_SUBSCRIBE_CHAIN=kis,kiwoom,ls`).
|
||
|
||
---
|
||
|
||
## 2. 익절 「호가 ON」— 지정가 매도 (실행)
|
||
|
||
### 역할
|
||
청산 **판정(래칫·어깨·트레일)이 이미 난 뒤**, 주문을 **어떻게 내는지**.
|
||
|
||
| 스위치 | ON | OFF / 호가 없음 |
|
||
|--------|----|-----------------|
|
||
| `SELL_USE_ORDERBOOK_ON_PROFIT` | 익절·어깨·트레일 → 매수1호가 **지정가(`ORD_DVSN=00`)** · 잔량 부족 시 **대기** | 같은 익절 신호 → **시장가** |
|
||
|
||
**손절 · 긴급 · 장마감(EOD)** → 이 스위치와 무관, **항상 시장가**.
|
||
|
||
### 운영 값 (확인 시점 기준)
|
||
저장: `config_short`
|
||
|
||
| 키 | 값 | 의미 |
|
||
|----|----|------|
|
||
| `SELL_USE_ORDERBOOK_ON_PROFIT` | **true** | 익절 지정가 경로 ON |
|
||
| `SELL_ORDERBOOK_BID_LEVELS` | 2 | 매수 1~2호가 잔량 합 검사 |
|
||
| `SELL_ORDERBOOK_DEPTH_MULT` | 1.5 | 필요 잔량 = 수량 × 1.5 |
|
||
|
||
### 코드
|
||
- `kis_trader/execution/order_manager.py` — 매도 전송 분기
|
||
- `kis_trader/execution/orderbook_sell.py` — 잔량·지정가 가격 산출
|
||
|
||
전 전략 공통(OrderManager 한 경로). 진입 필터처럼 전략별 키가 아님.
|
||
|
||
### UI
|
||
라이브 스키마에 진입 「호가필터 ON」은 잘 보이지만,
|
||
`SELL_USE_ORDERBOOK_ON_PROFIT`는 **스키마 전용 체크가 약하고 DB(`config_short`)·get_env로 동작**.
|
||
|
||
---
|
||
|
||
## 3. 매도 주문코드 13 — 시장가 IOC (실전만)
|
||
|
||
### 역할
|
||
**시장가 매도**를 낼 때의 `ORD_DVSN` 선택.
|
||
익절 지정가(00)와는 별개.
|
||
|
||
| 환경 | 시장가 매도 코드 |
|
||
|------|------------------|
|
||
| **모의** | 항상 **`01`** (IOC 13 미지원 → 코드에서 고정) |
|
||
| **실전** + `USE_MARKET_IOC_SELL=true` | **`13`** (시장가 IOC) |
|
||
| **실전** + false | **`01`** (일반 시장가) |
|
||
|
||
매수용은 별도: `USE_MARKET_IOC` (실전 매수 13 / 모의 01).
|
||
|
||
### 코드
|
||
`kis_trader/execution/kis_client.py`
|
||
- `sell_market_order` / `uses_market_sell_ioc()`
|
||
- `sell_limit_order` → 항상 `00`
|
||
|
||
### 운영 UI
|
||
`live_config_schema`: 「매도 시장가 IOC」=`USE_MARKET_IOC_SELL`
|
||
|
||
### 흐름 요약
|
||
```
|
||
익절 신호
|
||
├─ SELL_USE_ORDERBOOK_ON_PROFIT ON + 잔량 OK → 지정가 00
|
||
└─ OFF / 호가없음 / 잔량실패 후 시장가
|
||
├─ 실전 + IOC ON → 13
|
||
└─ 모의 → 01
|
||
|
||
손절·긴급·EOD → 시장가 (위와 동일하게 실전 13 / 모의 01)
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 수익구간 호가매도 (코드 있음 · 기본 OFF)
|
||
|
||
진입 필터·익절 지정가와 **다른 축**:
|
||
이미 산 뒤, **이득이 난 상태에서** “사려는 사람이 줄었다”면 **팔지 말지**를 본다.
|
||
|
||
### 초등으로
|
||
1. 주식을 샀다.
|
||
2. 가격이 조금 올랐다 (이득).
|
||
3. 호가판을 보니 “사고 싶다”는 줄이 확 짧아졌다.
|
||
4. 그때 **이익을 챙기고 나간다** = 수익구간 호가매도.
|
||
5. **아직 이득이 아니면** 이 규칙으로 안 판다. (그건 손실구간 쪽)
|
||
|
||
### 왜 맨 위에 두면 안 되나
|
||
- 장 초반·급등 초입은 원래 파는 사람이 많다 → “분위기 끝”으로 **오해**하기 쉬움
|
||
- 사자마자 팔면 수수료만 나고 기회 날림
|
||
→ **맨 위(1순위) 금지.** 래칫·어깨 **다음**, −% 손절 **앞**.
|
||
|
||
측정(실매 매수 + `ws_orderbook`, 예: 2026-07-27 모멘텀):
|
||
- 가드 없이 순간만 보면: 조기 신호 **많음**
|
||
- 이득·최소보유 가드 후: **크게 줄어듦**
|
||
※ 이 %는 **수익률이 아님** = “산 뒤 N분 안에 이 신호가 뜬 건수 비율”
|
||
|
||
### 합의된 매도 순서
|
||
```
|
||
래칫 → 어깨 → 수익구간 호가매도 → 손실구간 호가매도 → −% 손절 → 트레일 → …
|
||
```
|
||
- 코드: 어깨 다음 · 손절 전 = 수익구간(`호가컷`) + 손절호가 · 코드 시드 OFF(운영 DB)
|
||
- 모멘텀=키움 호가 · 돌파=LS 호가 (교차 메우기 금지)
|
||
- −% 손절은 **가격 하나만**. 호가를 손절 % 숫자에 섞지 않음.
|
||
|
||
### env 키 (모멘텀=키움 · 돌파=LS · 수익구간)
|
||
| 키 | 기본 | 역할 |
|
||
|----|------|------|
|
||
| `MOMENTUM_EXIT_OB_ENABLED` / `BREAKOUT_EXIT_OB_ENABLED` | **false** | 켜기/끄기 |
|
||
| `*_EXIT_OB_RATIO_MIN` | 0.4 | 사려는/팔려는 비율 이동평균이 이하면 “줄었다” |
|
||
| `*_EXIT_OB_MA_WINDOW` | 5 | 몇 장 평균 볼지 |
|
||
| `*_EXIT_OB_MIN_PROFIT_PCT` | 0.005 (+0.5%) | **이만큼 이득일 때만** |
|
||
| `*_EXIT_OB_MIN_HOLD_BARS` | 3 | 산 지 최소 N분 |
|
||
|
||
등록: `database.py` (`config_momentum` / `config_breakout`) · defaults → `exit_ob_*`.
|
||
웹 모멘텀·돌파 탭에 컨트롤 있음. **코드 기본 false** · 실제 ON/OFF는 **DB**.
|
||
|
||
### 발동 조건 (모두 만족)
|
||
| 조건 | |
|
||
|------|--|
|
||
| 스위치 ON | |
|
||
| 비율 이동평균 < 기준 | 스냅 부족 → **안 함** |
|
||
| 최소 이득 | |
|
||
| 최소 보유 | |
|
||
|
||
### 배선 상태 (2026-08)
|
||
실매·백테 OR 히스토리 주입 ✅ · 웹 ✅ · Optuna **캔들 본체 TPE에는 미포함**.
|
||
숫자 탐색은 **후처리** Stage2 (`optuna_orderbook_recommend.py` EXIT/STOP 축). OFF면 예전과 동일.
|
||
|
||
### 측정 스크립트 (매도쪽 · 필터 켜기 전)
|
||
`scripts/measure_ob_exit_early_fire.py`
|
||
- 실매 매수 + 실매 호가 테이블
|
||
- A) 맨 위+가드없음 / B) 맨 위+가드 / C) 3순위+가드(현 설계)
|
||
- 예: `--date 2026-07-31 --ob-table ws_orderbook`
|
||
- **2026-08-03**: 모멘텀 매수 0건 → 측정 불가 (호가는 쌓여 있음)
|
||
|
||
### 2026-07-31 매도쪽 한 줄 (키움 `ws_orderbook`)
|
||
모멘텀 7건 전부 호가 있음.
|
||
A 28.6% → B/C 14.3% (가드가 핵심).
|
||
같은 날 `ls_ws_orderbook`은 그 종목들 스냅 0 → LS로 못 재현 (모멘텀=키움 조건 쪽).
|
||
|
||
### 전략 범위
|
||
모멘텀+돌파 배선. 진입 필터 키와 **혼용 금지**.
|
||
|
||
---
|
||
|
||
## 4-1. 손실구간 호가매도 (배선 완료 · 기본 OFF · 모멘텀+돌파)
|
||
|
||
수익구간과 **같은 호가 눈**, **반대 장부**.
|
||
|
||
### 초등으로
|
||
1. 주식을 샀다.
|
||
2. 가격이 조금 떨어졌다 (손해).
|
||
3. 호가판에 사려는 사람이 확 줄었다.
|
||
4. −3% 같은 **큰 손절선까지 기다리기 전에** 먼저 나온다.
|
||
5. **이득 중이면** 이 규칙으로 안 판다. (그건 수익구간)
|
||
|
||
### 왜 만드나
|
||
−% 손절만 있으면, 호가가 이미 무너진 **뒤에** 가격이 손절선에 닿아 **더 나쁜 가격**에 팔 수 있음.
|
||
수익구간 규칙만 있으면 **손해 날 때**는 안 도와줌.
|
||
|
||
### 원칙
|
||
1. −% 손절 **숫자는 그대로**. 이 규칙은 **앞에서 한 번 더 보는 것**뿐.
|
||
2. **손해일 때만.**
|
||
3. 맨 위 금지 — 래칫·어깨 다음, −% 손절 직전.
|
||
4. 호가 스냅 없으면 **안 함** (가격봉으로 호가 지어내기 금지).
|
||
5. 나갈 때는 **시장가/IOC**(도망). 익절 지정가「호가 ON」과 다름.
|
||
6. 기본 **OFF**. 측정 → 승인 → 코드 → ON.
|
||
|
||
### 목표 순서
|
||
```
|
||
1 래칫
|
||
2 어깨
|
||
3 수익구간 호가매도 ← 이득 + 호가 줄음
|
||
4 손실구간 호가매도 ← 손해 + 호가 줄음 ★배선·기본 OFF
|
||
5 −% 손절 ← 가격만
|
||
6 트레일 · 시간 · …
|
||
```
|
||
|
||
### env (모멘텀 · 배선됨) — 한글 뜻
|
||
|
||
| 키 (영문) | 한글 이름 | 기본 | 한줄 |
|
||
|-----------|-----------|------|------|
|
||
| `MOMENTUM_STOP_OB_ENABLED` / `BREAKOUT_STOP_OB_ENABLED` | 손절호가 켜기 | **false** | 끄면 이 규칙 전부 무시 |
|
||
| `MOMENTUM_STOP_OB_RATIO_MIN` | 호가 붕괴 기준 | 0.4 | 총사÷총팔 평균이 **이보다 작으면** “사람 줄었다” |
|
||
| `MOMENTUM_STOP_OB_MA_WINDOW` | 평균 몇 장 | 5 | 위 비율을 **최근 5장** 평균내서 봄 (순간 깜빡임 방지) |
|
||
| `MOMENTUM_STOP_OB_MIN_LOSS_PCT` | 최소 손해 폭 | 0.003 (−0.3%) | **이만큼은 이미 손해**여야 발동 (수익 중엔 안 씀) |
|
||
| `MOMENTUM_STOP_OB_MIN_HOLD_BARS` | 최소 보유 분 | 2 | 산 지 **2분 전**엔 안 씀 |
|
||
|
||
DDL·컬럼 COMMENT: `scripts/sql/config_momentum_ob_column_comments.sql` · `scripts/sql/config_breakout_ob_column_comments.sql`
|
||
|
||
### 하드 손절 4%일 때 언제 나감? (손절호가 ON 가정)
|
||
|
||
1. **산 지 2분 미만** → 손절호가 **안 함**. (−4% 하드만 가능)
|
||
2. **2분 이상** + 현재가 **−0.3% 이하** + OR평균 **< 0.4** → 사유 **`손절호가`** 로 **즉시** 시장가 (하드 4%까지 **안 기다림**)
|
||
3. 호가는 안 무너졌는데 가격만 **−4%** → 예전처럼 사유 **`손절`**
|
||
4. 지금 DB 기본은 **ENABLED=false** → 2번은 아예 없고 **4% 하드만**.
|
||
|
||
**“얼마나 빨리”** = “−0.3%에 OR만 깨지면 그때”. 4%까지 버티는 규칙이 아님.
|
||
OR 5장 채우는 시간: LS 틱동기면 체결 몇 번(초 단위) · 키움 ~3초 저장이면 대략 **십수 초**면 충분. 최소보유 **2분**이 보통 더 긴 병목.
|
||
|
||
**삼성전자급 두꺼운 호가**: 평소 OR이 0.4 아래로 잘 안 떨어짐 → 손절호가가 **거의 안 울릴 수 있음**. 진짜 수급 붕괴·급락 구간에서야 −0.3%~−4% 사이에서 선제. 얇은 중소형에서 체감이 큼.
|
||
|
||
사유 문자열: **`손절호가`** (수익구간의 `호가컷`과 구분 · 시장가 긴급).
|
||
|
||
### 상태
|
||
| 항목 | |
|
||
|------|--|
|
||
| 설계 | ✅ |
|
||
| 코드·DB·웹 | ✅ 2026-08-03 배선 |
|
||
| 기본값 | 코드 시드 **OFF** · 실제는 **DB** |
|
||
| TPE | 캔들 본체 축 **아님**. 후처리 STOP 축 (`docs/옵투나.md`) |
|
||
|
||
### 측정 (호가 쌓인 뒤 · 모멘텀 매수가 있는 날)
|
||
1. 손실→−%손절(또는 큰 손실) 건 중, 손절 **직전**에 이미 호가가 줄었던 비율
|
||
2. 손해였다가 회복한 건에서 이 규칙이 **너무 일찍** 나갔을 비율
|
||
→ 측정 후 **ON 여부** 결정 (지금 기본 OFF).
|
||
|
||
---
|
||
|
||
## 5. 데이터 · UI 참고
|
||
|
||
| 항목 | 내용 |
|
||
|------|------|
|
||
| 호가 테이블 | `ws_orderbook` (키움), `ls_ws_orderbook` (LS) |
|
||
| 테이블에 **있는** 것 | `best_bid`/`best_ask`, `total_bid_qty`/`total_ask_qty`, `bid_qty_l3`/`ask_qty_l3` 등 **원본** |
|
||
| 테이블에 **없는** 것 | `0.45%`·살힘·OR 같은 **계산된 %/비율** — DB 컬럼이 아님 |
|
||
| 거래내역 「매수호가」「매도호가」 | `trade_orderbook_enrich.py`가 매수/매도 시각 근처 스냅을 붙인 뒤, **웹 JS가 원본으로 계산·색칠** |
|
||
| 기준 숫자(시드) | 매수: 간격≤0.45% · 살힘≥0.85 / 매도 OR: ≈0.40 (수익구간 호가매도 기본) — 업계 표준 아님 |
|
||
| LS 오늘 | **틱 동기** (체결≈호가 1:1) |
|
||
| 키움 오늘 | 연속 저장 · 간격 **약 3초** (틱 1:1 아님) |
|
||
|
||
### UI 칸 — 이상(높을수록 / 낮을수록)
|
||
|
||
| 칸 | 지표 | 이상 | 초등 |
|
||
|----|------|------|------|
|
||
| **매수호가** | 가격간격 % | **↓ 낮을수록** | 사·팔 가격이 가까울수록 들어가기 쉬움 |
|
||
| **매수호가** | 살 힘 (위3칸 사÷팔) | **↑ 높을수록** | 사겠다는 대기가 많을수록 버팀 |
|
||
| **매도호가** | OR (총사÷총팔) | **↑ 높을수록** 수급OK · **↓ 낮으면** 약함 | 팔 때 사려는 사람이 많으면 버팀, 적으면 무너짐 |
|
||
| **매도호가** | 가격간격 % | **↓ 낮을수록** | 팔 때도 간격 작을수록 체결 덜 미끄러짐 |
|
||
|
||
열 헤더: `↓간격 ↑살힘` / `↑OR ↓간격` (마우스 올리면 상세).
|
||
|
||
|
||
---
|
||
|
||
## 6. 하지 말 것 (요약)
|
||
|
||
1. 매수필터 · 익절지정가 · 수익구간호가매도 · 손실구간호가매도를 **한 스위치로** 취급하지 말 것.
|
||
2. “호가 쌓기”를 **새 매매 설계**로 착각하지 말 것 — **이미 있는 저장**.
|
||
3. 호가를 −% 손절 **공식에 섞지** 말 것.
|
||
4. 호가매도를 **1순위**에 두지 말 것.
|
||
5. 스냅 없이 OHLC로 호가 **지어내기** 금지.
|
||
6. 모의에 ORD_DVSN=13 강제 금지.
|
||
7. 필터 TTL과 저장 TTL을 **한 키로 묶지 말 것** (`FILTER_MAX_AGE` vs `TICK_MAX_AGE`).
|
||
8. Optuna 호가: **현재 기본은 본 TPE에 진입 호가 포함** (`OPTUNA_TPE_INCLUDE_ORDERBOOK`). 돌파는 스터디 스위치로 ON/OFF 분리. 상세 `docs/옵투나.md` §1. (구 “캔들 TPE에 호가 넣지 말 것”은 옛 권고.)
|
||
9. 거래대금·시총회전 **만**으로 손절 만들지 말 것.
|
||
10. 필터 ON인데 호가 사진이 없다고 **통과**시키지 말 것. REST로 메우지 말 것 (`REJECT_IF_EMPTY`).
|
||
|
||
---
|
||
|
||
## 7. 관련 파일
|
||
|
||
| 파일 | 내용 |
|
||
|------|------|
|
||
| `kis_trader/engine/orderbook_filter.py` | 매수 호가필터 |
|
||
| `kis_trader/engine/orderbook_env.py` | 전략별 진입 env |
|
||
| `kis_trader/execution/orderbook_sell.py` | 익절 지정가·잔량 |
|
||
| `kis_trader/execution/order_manager.py` | 매도 실행 분기 |
|
||
| `kis_trader/execution/kis_client.py` | 00/01/13 주문 |
|
||
| `kis_trader/backtest/trade_orderbook_enrich.py` | 거래내역 호가 표시 |
|
||
| `kis_trader/engine/momentum_hts_logic.py` | 수익구간·손절호가 판정 (모멘텀·돌파 공용) |
|
||
| `kis_trader/engine/momentum_engine.py` | `exit_ob_*` defaults |
|
||
| `kis_trader/engine/momentum_env_keys.py` · `BREAKOUT_*_OB_*`(database/config_breakout) | 전략별 env |
|
||
| `kis_trader/strategies/momentum.py` | 실매 OR append |
|
||
| `scripts/measure_ob_exit_early_fire.py` | 매도쪽 조기신호 측정 |
|
||
| `docs/layered_exit_design.md` | 옛 설계서 (현행은 본 MD §4·§4-1) |
|
||
| `docs/호가.md` | 본 문서 |
|
||
|
||
---
|
||
|
||
## 8. Optuna / TPE (2026-08-28 · 상세는 `docs/옵투나.md`)
|
||
|
||
> **주의:** 아래 「캔들 본체에 호가 넣지 말 것」은 **옛 권고(2026-08-15)**.
|
||
> **현재 코드 기본**은 `OPTUNA_TPE_INCLUDE_ORDERBOOK=true` → 본 TPE에 진입 호가가 들어간다.
|
||
> 돌파만 **스터디 스위치**(off/on 분리). 꼬리·모멘·스캘은 아직 **같은 스터디 categorical**.
|
||
> 진실·분리 vs trial 수: **`docs/옵투나.md` §1**.
|
||
|
||
### 본 TPE (현재)
|
||
- 기본: 차트 + 호가(및 휩쏘)를 **trial에서 함께** 평가.
|
||
- 돌파: `--orderbook-filter off|on` · 한 스터디에 ON/OFF 안 섞음 (OFF 편애 방지).
|
||
- `INCLUDE_ORDERBOOK=true` 이면 사후 8방 **기본 생략** (성적 사기 방지).
|
||
|
||
### 후처리 (Stage 2) — INCLUDE_ORDERBOOK=false 일 때 / FORCE 시
|
||
`optuna_postprocess_topn.py` → `optuna_orderbook_recommend.py`.
|
||
구 JSON만 다시: `python3 -u kis_trader/backtest/optuna_rerun_postprocess.py --result-json <path>`
|
||
(`--apply-best` 아님. 실매 숫자는 안 바뀜.)
|
||
|
||
진입 탐색 격자 (`ensure_optuna_gate_env_defaults` / `OPTUNA_OB_ENTRY_*`, env_config_ext):
|
||
|
||
| 축 | 범위 (널널 검사, 2026-08-15) | 실매 참고 |
|
||
|----|------------------------------|-----------|
|
||
| 스프레드 % | 0.1 ~ 8.0 step 0.1 | 모멘텀 운영 3.0 포함 |
|
||
| 잔량비 | 0.05 ~ 1.5 step 0.05 | 0.58~1.0 포함 |
|
||
| 매도벽 배수 | 1 ~ 80 step 1 | 운영 3 / 스캘프 8 |
|
||
| lookback | `OPTUNA_OB_LOOKBACK_MIN` 기본 30분 | |
|
||
|
||
건수 하한: 잔여가 원본의 **30% 미만**이면 trial 무효.
|
||
후처리는 **이미 난 체결 + 근처 호가 재생**. 실매 “스냅 없음 통과” 구멍은 **모델링하지 않음** (B안 필터 TTL).
|
||
|
||
DB 반영은 사용자 명시 / `scripts/apply_optuna_ob_consensus.py` 만. 후처리 재실행 ≠ 적용.
|
||
|
||
---
|
||
|
||
## 9. env 등록 주의
|
||
|
||
신규 키는 `database.py` ENV 등록 + `get_env_from_db` 재조회 검증.
|