137 lines
10 KiB
Markdown
137 lines
10 KiB
Markdown
# 꼬리잡기 백테스트 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 매수시작/매수종료 (예: 830–1530) | **DB 고정값** (`TIME_START`/`TIME_END` 또는 기본 930–1500) — 시간대가 다르면 거래 수·손익이 달라짐 |
|
||
| **일일최대매수** | 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 기준 0930–1500 사용. 웹에서 0830–1530 등 다르게 두었으면 비교 시 0930·1500으로 맞추면 됨).
|
||
- **일일최대매수·RSI 기간**도 파라서치 1위와 맞춤 (예: 7, 5).
|
||
- 또는 파라서치 실행 전에 DB를 원하는 값(일일최대 3, RSI_PERIOD 14)으로 맞춘 뒤 돌리면, 백테스트웹과 비슷한 조건으로 비교 가능.
|
||
|
||
**거래 수 차이(예: 파라서치 39건 vs 웹 19건)**:
|
||
- 같은 기간·같은 로직으로 재현하면 **18~19건**이 나오는 경우가 있음. 파라서치 결과 JSON의 39건은 **당시 DB에 더 많은 거래일 데이터가 있었거나**, **매수시간대(930–1500 vs 830–1530)** 등 설정 차이로 인한 것일 수 있음. 비교 시 **시작/종료일 + 매수시작/매수종료**를 동일하게 두면 차이가 줄어듦.
|
||
|
||
**실매매는 지금 코드상**:
|
||
- **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와 동일 구조이되, **매수/매도 판단만 엔진 호출**로 통일하면 백테와 결과를 맞추기 쉬움.
|