Files
kis_bot/TAIL_BACKTEST_VS_LIVE.md
2026-03-17 12:33:30 +09:00

137 lines
10 KiB
Markdown
Raw Permalink 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.
# 꼬리잡기 백테스트 vs 실매매 마이너스 원인
## 요약
- **백테스트**: 플러스 (예: tail2.log 기준 순손익 +19,223,972원, 승률 55.2%)
- **실매매**: 마이너스 (누적손익 -29,571,921원 등)
- **원인**: 계산식·진입/청산 규칙이 백테스트와 실매매에서 **다르게** 적용되거나, **진입가·타이밍·추가 매도 규칙** 차이로 인한 것.
---
## 1. 진입가(Entry Price) 차이 ⭐ 가장 유력
| 구분 | 백테스트 | 실매매 |
|------|----------|--------|
| 진입가 | **신호 봉 다음 봉 시가** (next bar open) | **신호
감지 시점 현재가** (시장가 매수) |
- 백테스트: "이 봉에서 조건 충족" → **다음 봉 시가**에 매수로 가정 (look-ahead 없음).
- 실매매: 조건 충족 시 **그 순간 시장가**로 매수 → 실제 체결가는 다음 봉 시가보다 **불리할 가능성**이 큼 (이미 올라온 뒤 매수, 슬리피지).
- **영향**: 실매매에서 같은 신호라도 **진입가가 더 높아져** 익절 도달이 어렵고, 손절에 더 자주 걸릴 수 있음.
---
## 2. 봉 데이터·타이밍 차이
- **백테스트**: `ws_candles` DB의 **확정된 3분봉**만 사용 (`is_confirmed=1`). 봉이 닫힌 뒤에만 신호 판단.
- **실매매**: WS 메모리 또는 REST 3분봉 사용. **봉이 아직 확정 전**일 때 현재가로 조건을 체크할 수 있음.
- 봉 미확정 시점의 "현재가"는 3분봉 종가와 다를 수 있어, **같은 조건이라도 백테와 다른 시점에 신호가 나오거나** 회복률/낙폭 계산이 달라질 수 있음.
### 매수 허용 시간대 (TIME_START / TIME_END)
- **영향**: 봉 시간이 `time_start_hm`(예: 930) ~ `time_end_hm`(예: 1500) 밖이면 **진입 자체를 하지 않음** → 결과에 영향 있음.
- **백테스트**: `tail_engine.run_tail_backtest`에서 `time_start_hm`, `time_end_hm` 사용 (파라미터로 적용됨).
- **실매매**: `tail_engine.check_buy_signal_live`에서도 동일하게 사용. ver3는 `get_tail_defaults_from_db()`로 params를 채우므로, DB에 `TIME_START`/`TIME_END` 컬럼과 값이 있으면 **실매에도 동일 시간대가 적용**됨. (컬럼이 없으면 기본 930/1500.)
---
## 3. 매도(청산) 규칙 복잡도 차이 ⭐
| 구분 | 백테스트 (tail_engine) | 실매매 (kis_short_ver3) |
|------|------------------------|--------------------------|
| 청산 종류 | 4가지만: **손절 / 익절 / 어깨컷 / 장마감** | 그 위에 **금액손실컷(MAX_LOSS_PER_TRADE_KRW), MIN_HOLD_AFTER_BUY_SEC**, 매수 시 **ATR 기반 손절가/목표가** 등 추가 |
- 백테스트는 **% 손절(sl_pct), % 익절(tp_pct), 어깨컷(shoulder_min_high + shoulder_cut_pct), 장마감** 만 사용.
- 실매매는 **어깨컷 + 금액손실컷(MAX_LOSS_PER_TRADE_KRW) + MIN_HOLD_AFTER_BUY_SEC** 적용. 매수 시 **STOP_ATR_MULTIPLIER_TAIL / TARGET_ATR_MULTIPLIER_TAIL** 로 ATR 기반 손절/목표가를 쓸 수 있어, 백테와 청산 시점이 달라질 수 있음.
---
## 3-1. 실매 env vs 백테스트·웹·파라서치 대응표
**요약**: 웹 백테·파라서치에는 **금액손실컷(MAX_LOSS_PER_TRADE_KRW), MIN_HOLD_AFTER_BUY_SEC, ATR 기반 손절/목표가****없습니다**. 실매에만 있고, 백테 엔진(tail_engine)에는 구현되어 있지 않음. 그 외 꼬리잡기 진입/청산용 env는 아래처럼 백테·웹·파라서치에 반영됨.
| 실매(ver3) env | tail_engine 백테 | 백테스트웹 입력란 | tail_param_search 그리드 |
|----------------|------------------|-------------------|---------------------------|
| **매수** | | | |
| MIN_DROP_RATE, MIN_RECOVERY_RATIO_SHORT | ✅ | ✅ tl_drop, tl_rec | ✅ min_drop_rate, min_recovery_ratio |
| TAIL_RATIO_MIN, TAIL_PCT_MIN | ✅ | ✅ tl_tail, tl_tail_pct | ✅ tail_ratio_min, tail_pct_min (fine/full) |
| RSI_PERIOD, RSI_OVERHEAT_THRESHOLD | ✅ | ✅ tl_rsi_period, tl_rsi | ✅ rsi_threshold (full), rsi_period (full) |
| MAX_RECOVERY_RATIO_3M, HIGH_PRICE_CHASE_THRESHOLD | ✅ | ✅ tl_max_rec_3m, tl_high_chase | ✅ max_rec_3m, high_chase_thr (fine/full) |
| SHOULDER_MIN_HIGH_PCT, SHOULDER_CUT_PCT | ✅ | ✅ tl_smin, tl_scut | ✅ shoulder_cut_pct, shoulder_min_high (fine/full) |
| REENTRY_COOLDOWN_SEC, TIME_START, TIME_END, MAX_STOCKS | ✅ | ✅ tl_cool, tl_ts, tl_te, tl_maxd | DB 고정 (주석 해제 시 그리드 추가 가능) |
| **매도** | | | |
| STOP_LOSS_PCT, TAKE_PROFIT_PCT | ✅ | ✅ tl_sl, tl_tp | ✅ sl_pct, tp_pct |
| MAX_LOSS_PER_TRADE_KRW (금액손실컷) | ❌ | ❌ | ❌ (실매 전용) |
| MIN_HOLD_AFTER_BUY_SEC | ❌ | ❌ | ❌ (실매 전용) |
| STOP_ATR_MULTIPLIER_TAIL, TARGET_ATR_MULTIPLIER_TAIL | ❌ | ❌ | ❌ (실매 전용, ATR 기반 손절/목표가) |
- **백테스트 엔진**에는 위 표에서 ✅ 인 항목만 반영됨. **금액손실컷·MIN_HOLD·ATR 기반은 실매에만 있고**, 웹 백테·파라서치에는 없어서, 백테 결과와 실매 결과 차이의 원인이 될 수 있음.
- **파라미터 서치**는 fine/full 모드에서 **어깨발동(shoulder_min_high), 꼬리최소(tail_pct_min), 3분최대회복(max_rec_3m), 고점추격방지(high_chase_thr)** 를 그리드에 넣어 경우의 수에 포함함.
- **다른 env**: 꼬리잡기(tail) 전략 기준으로 실매에서 쓰는 env 중 위 표에 없는 것은 없음. (리스크/슬롯/키움 등 공통 env는 별도.)
---
## 4. 백테스트웹 vs 파라미터서치(tail_param_search) 결과가 다를 때
**같은** 낙폭/회복/손절/익절/어깨 수치를 넣어도 **거래 건수·손익이 다르게** 나올 수 있음.
| 원인 | 백테스트웹 | 파라미터서치 |
|------|------------|--------------|
| **매수시간대** | UI 매수시작/매수종료 (예: 8301530) | **DB 고정값** (`TIME_START`/`TIME_END` 또는 기본 9301500) — 시간대가 다르면 거래 수·손익이 달라짐 |
| **일일최대매수** | UI에서 직접 입력 (예: 3) | **DB 고정값** (`get_tail_defaults_from_db()` → MAX_STOCKS) — 예: 7 |
| **RSI 기간** | 요청에 없으면 DB 기본 또는 14 | **DB 고정값** (RSI_PERIOD) — 예: 5 |
| **기간** | UI 시작/종료일 | `--start` / `--end` (예: 다른 구간이면 당연히 결과 다름) |
- 파라미터서치는 **그리드에 없는 값**은 전부 **DB(env_config)에서 한 번만 읽어** 모든 조합에 동일 적용함.
→ 당시 DB에 `MAX_STOCKS=7`, `RSI_PERIOD=5` 등이면 **39건, +167,142원** 같은 결과가 나옴.
- 백테스트웹에서 **일일최대매수 3**, RSI 기간은 요청에 없어 14로 두면 **32건, -992원**처럼 달라짐.
**동일 비교하려면**:
- 백테스트웹에서 **시작/종료일**을 파라서치와 똑같이 두고, **매수시작/매수종료**도 맞출 것 (파라서치는 DB 기준 09301500 사용. 웹에서 08301530 등 다르게 두었으면 비교 시 0930·1500으로 맞추면 됨).
- **일일최대매수·RSI 기간**도 파라서치 1위와 맞춤 (예: 7, 5).
- 또는 파라서치 실행 전에 DB를 원하는 값(일일최대 3, RSI_PERIOD 14)으로 맞춘 뒤 돌리면, 백테스트웹과 비슷한 조건으로 비교 가능.
**거래 수 차이(예: 파라서치 39건 vs 웹 19건)**:
- 같은 기간·같은 로직으로 재현하면 **18~19건**이 나오는 경우가 있음. 파라서치 결과 JSON의 39건은 **당시 DB에 더 많은 거래일 데이터가 있었거나**, **매수시간대(9301500 vs 8301530)** 등 설정 차이로 인한 것일 수 있음. 비교 시 **시작/종료일 + 매수시작/매수종료**를 동일하게 두면 차이가 줄어듦.
**실매매는 지금 코드상**:
- **kis_short_ver3**는 `get_tail_defaults_from_db()`로 params를 채워 `tail_engine.check_buy_signal_live` / `check_sell_signal_live`에 넘김.
- 즉 **실매 = 현재 DB(env_config)에 저장된 값**으로 동작.
- 파라미터서치에서 `--apply` 하면 1위 조합이 DB에 들어가므로, 그 다음부터 **실매는 파라서치 1위와 같은 파라미터**(일일최대 7, RSI 5, 손절 1% 익절 6% 등)로 돌아감.
- 백테스트웹에서만 “일일최대 3”으로 두고 실매는 그대로 두면, **웹 백테(32건, -992)와 실매(DB 기준 7/5 등)는 서로 다른 설정**이 됨.
---
## 5. 파라미터 불일치 가능성
- 백테스트 UI/API에서 넣는 값(낙폭 3%, 회복률 30%, 꼬리/몸통 2, 손절 5%, 익절 3% 등)과 **봇 DB(env_config)** 값이 다를 수 있음.
- 봇은 `MIN_RECOVERY_RATIO_SHORT`, `MIN_DROP_RATE`, `TAIL_RATIO_MIN`, `STOP_LOSS_PCT`, `TAKE_PROFIT_PCT` 등을 DB에서 읽음.
- **권장**: 백테스트에서 사용한 파라미터를 그대로 `api/backtest/tail/save_config`로 DB에 저장한 뒤 실매매를 돌려, **동일 수치**로 맞출 것.
---
## 6. 쿨다운(재진입 제한)
- 백테스트: **쿨다운 15분** 등으로 동일 종목 재진입 제한.
- 실매매: `REENTRY_COOLDOWN_SEC` 등이 DB에 있으면 적용되지만, **없거나 다르면** 백테보다 더 자주/덜 진입할 수 있음.
---
## 7. 대응 방향
1. **진입가 통일 (가능한 범위에서)**
- 실매매에서 "다음 봉 시가"에 가깝게 하려면, **3분봉 확정 시점(봉 마감 후)**에만 매수 체크를 하거나, 매수 호가를 다음 봉 시가에 가깝게 넣는 방식 검토 (구현 복잡도는 있음).
2. **매도 규칙을 백테와 동일하게**
- **tail_engine**의 청산 규칙(손절/익절/어깨컷/장마감만)을 실매매에서도 쓰고, **kis_short_ver3**에서는 금액손실컷·ATR·퀵프로핏 등을 빼거나, 백테스트에 없는 규칙은 선택 옵션으로 두어 비교 테스트.
3. **공통 엔진 사용**
- `tail_engine.check_buy_signal_live` / `check_sell_signal_live`를 실매매에서 사용하고, 백테스트는 `tail_engine.run_tail_backtest` 또는 동일 식을 쓰면 **계산식 자체**는 일치.
4. **파라미터 동기화**
- 백테스트 결과 좋은 파라미터를 `save_config`로 DB에 저장 후, 실매매는 **그 DB 값만** 쓰도록 해서 불일치 제거.
---
## 8. tail_engine / kis_short_ver3
- **tail_engine.py**: 꼬리잡기 **진입·청산 공통 계산식** (백테스트·실매매 동일).
- **backtest_web** 꼬리잡기 API: `use_engine=1`(기본)이면 `tail_engine.run_tail_backtest` 사용.
- **kis_short_ver3**: tail_engine을 사용하는 실매매 봇. ver2와 동일 구조이되, **매수/매도 판단만 엔진 호출**로 통일하면 백테와 결과를 맞추기 쉬움.