변경 사항 ---- - _test_kiwoom_condition_list.py: 키움 웹소켓 조건검색 '목록조회' 기능을 단독으로 테스트하는 스크립트 추가 - _test_kiwoom_condition_realtime.py: 'momentum' 조건식을 실시간으로 등록하고 초기 매칭 종목 리스트 및 실시간 편입/이탈을 수신하는 테스트 스크립트 추가 - _verify_columnar_bitid.py, _verify_shared_e2e_breakout.py, _verify_shared_e2e.py: 공유 메모리 및 dict 간의 데이터 일관성을 검증하는 테스트 추가 영향 ---- - 신규 테스트 스크립트 추가로 키움 웹소켓 API의 기능 검증 및 안정성을 높임 - 기존 기능에 대한 영향 없음 Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
꼬리잡기 백테스트 — 웹 vs 파라서치 정리 (2026-05-30)
목적:
backtest_web꼬리 탭,tail_param_search.py, 실매(TailCatchStrategy) 결과가 어긋나던 원인·수정·운영 가이드를 한곳에 모음.
1. 아키텍처: SCAN vs TRIGGER
꼬리 전략은 2단계로 동작한다.
[SCAN] HTS/KIS 조건검색 (널넬하게)
A) 1봉 등락률 -10% ~ -1.5% (직전봉 종가 대비 현재봉 하락)
B) 체결강도 85% ~ 400%
C) 3봉전 대비 0봉 거래량 1.8~20배
→ ConditionSearchManager / KIS 조건식 `tail`
→ target_candidates_history (strategy_id=SHORT) 저장
[TRIGGER] tail_engine (엄격하게 — 매수 직전)
망치 꼬리, 당일 회복률, 3분봉 회복 위치, (선택) RSI·MA20,
고점추격·피뢰침, 시간대/쿨다운, (옵션) HTS A 재검사
| 단계 | 역할 | 백테에서 |
|---|---|---|
| SCAN | 후보 종목·시각 좁히기 | target_candidates_history (저장 이력 ON) |
| TRIGGER | “지금 사도 되나?” 최종 검사 | tail_engine._eval_tail_buy_at_index |
원칙 (.cursorrules SCAN vs TRIGGER 분리):
- 무거운 필터·API 호출은 5분 스캔이 아니라 매수 타점(TRIGGER) 에 둔다.
- 저장 이력 ON이면 SCAN은 이미 HTS로 걸렀으므로, TRIGGER에서 중복 필터를 또 걸면 거래가 사라진다.
2. 공통 코드 경로 (2026-05-30 기준)
| 구성요소 | 파일 | 역할 |
|---|---|---|
| 공통 로더 | kis_trader/backtest/tail_backtest_common.py |
유니버스·캔들·손익·run_tail_backtest_web_aligned |
| 엔진 | kis_trader/engine/tail_engine.py |
진입/청산, get_tail_defaults_from_db, run_tail_backtest |
| 파라서치 | kis_trader/backtest/tail_param_search.py |
fast/coarse 그리드, --apply |
| 웹 API | backtest_web.py → /api/backtest/tail |
꼬리 탭 백테 |
웹·파라서치 모두:
tbc.resolve_tail_universe()— SHORT 저장 이력 (기본)tbc.load_tail_candles_by_code()— ws_candleste.run_tail_backtest()— 동일 엔진
3. 웹 123건 vs 파라서치 6건 — 원인과 수정
3.1 증상
| 조건 | 거래 수 | 손익 (대략) |
|---|---|---|
| 파라서치 / DB base + 저장 이력 | 6건 | +60~69만 |
| 웹 (버그 시) 저장 이력 5772슬롯 | 123건 | +347만 |
| 저장 이력 OFF (전종목) | 133건+ | — |
3.2 근본 원인
웹 API가 폼 숫자만 params에 넣고, DB의 TRIGGER 플래그를 빠뜨렸다.
| 플래그 | DB (파라서치) | 웹 누락 시 엔진 기본값 | 영향 |
|---|---|---|---|
skip_hts_scan_dupes |
False | True (저장 이력 시 setdefault) | 거래 수 결정적 |
use_rsi_filter |
False | True | 소폭 |
use_daily_range_filter |
False | True | 이 기간 거의 없음 |
use_high_chase_filter |
False | True | 이 기간 거의 없음 |
재현 (5/18~5/29, 저장 이력 ON):
| 설정 | 거래 |
|---|---|
| 웹 params만 (플래그 없음) | 135건 |
| DB 플래그 전부 반영 | 6건 |
skip_hts=True + 선택필터 ON (웹 버그와 유사) |
123건 |
skip_hts=True only |
161건 |
3.3 수정 (backtest_web.py)
tail_trigger_flags를 DB _def에서 병합:
skip_hts_scan_dupes,use_intraday_drop,use_ma20_filteruse_rsi_filter,use_daily_range_filter,use_high_chase_filterbar_chg_min_pct,bar_chg_max_pct
응답 JSON params에도 동일 플래그 포함 (디버깅용).
확인: 재시작 후 응답에 skip_hts_scan_dupes: false 등이 보이고, 웹·파라서치 거래 수가 맞아야 함.
4. TAIL_SKIP_HTS_SCAN_DUPES / skip_hts_scan_dupes 상세
4.1 이름
| 이름 | 위치 |
|---|---|
TAIL_SKIP_HTS_SCAN_DUPES |
DB env_config |
skip_hts_scan_dupes |
Python params dict |
같은 스위치 — TRIGGER에서 HTS SCAN 조건을 또 검사할지 여부.
4.2 HTS A만 해당 (B·C 아님)
| HTS | 내용 | TRIGGER 재검사 |
|---|---|---|
| A | 직전봉 종가 대비 -10% ~ -1.5% | skip_hts가 제어 |
| B | 체결강도 | ❌ (SCAN만) |
| C | 3봉 전 거래량 배수 | ❌ (SCAN만) |
코드 (tail_engine.py):
if not skip_hts and i >= 1:
bar_chg = (cl - prev_cl) / prev_cl * 100.0
if bar_chg < bar_chg_min_pct or bar_chg > bar_chg_max_pct:
return ("탈락-봉등락", ...) # TAIL_BAR_CHG_MIN/MAX_PCT 기본 -10 ~ -1.5
| 값 | 의미 | 저장 이력 ON 시 거래 (5/18~5/29) |
|---|---|---|
| True | HTS A 생략 (SCAN 신뢰) | |
| False | HTS A 재검사 (이중 필터) |
4.3 min_drop_rate / 회복률과의 차이
| HTS A | min_recovery_ratio 등 |
|
|---|---|---|
| 비교 | 직전 3분봉 종가 → 현재 종가 | 당일 저점~고가 범위 내 회복 |
| 별도 축 | TAIL_USE_INTRADAY_DROP=true 시 시가→저점 낙폭 (HTS A와 다름) |
항상 TRIGGER에서 검사 |
4.4 엔진 설계 의도
- 저장 이력 ON →
skip_hts=True권장 (중복 필터 방지) - 전종목 백테 (이력 OFF) →
skip_hts=False로 HTS A를 TRIGGER에서 대신 검사
현재 DB는 False → 매우 보수적 (~0.5건/일).
5. 기타 TRIGGER 플래그 (그리드 미포함, DB 고정)
| env 키 | params 키 | True = | False = |
|---|---|---|---|
TAIL_USE_RSI_FILTER |
use_rsi_filter |
RSI≥임계값 탈락 (적음) | RSI 검사 안 함 |
TAIL_USE_DAILY_RANGE_FILTER |
use_daily_range_filter |
일중 변동폭 초과 탈락 | 검사 안 함 |
TAIL_USE_HIGH_CHASE_FILTER |
use_high_chase_filter |
고점 추격 탈락 | 검사 안 함 |
TAIL_USE_MA20_FILTER |
use_ma20_filter |
MA20 필터 | 검사 안 함 |
TAIL_USE_INTRADAY_DROP |
use_intraday_drop |
시가→저점 낙폭 검사 | 검사 안 함 |
use_*_filter: True = 필터 켜짐 → 기회 ↓
skip_hts: True = 검사 생략 → 기회 ↑ (이름만 반대로 느껴짐)
6. tail_param_search.py fast 그리드 (2026-05-30)
6.1 fast 모드 축 (768조합, 12분)
| 축 | 값 |
|---|---|
min_drop_rate |
2%, 3% |
min_recovery_ratio |
40%, 45%, 50% |
tail_ratio_min |
1.0, 1.5 |
max_rec_3m |
85%, 90% |
shoulder_min_high |
0.3%, 0.5% |
shoulder_cut_pct |
0.2%, 0.3% |
stop_atr_mult |
1.5, 2.0 |
target_atr_mult |
1.5, 2.0 |
atr_tp_max_pct |
0.8%, 1.0% |
DB 고정 (그리드 밖): skip_hts_scan_dupes, use_rsi_filter, RSI 기간, 쿨다운, tail_pct_min 등.
6.2 --apply 동작
- 그리드 축만 DB 반영 (전체
apply_cfg덮어쓰기 방지) - 진입·청산 env 키 예:
MIN_DROP_RATE,MIN_RECOVERY_RATIO_SHORT,TAIL_RATIO_MIN,MAX_RECOVERY_RATIO_3M,SHOULDER_*,STOP/TARGET_ATR_MULTIPLIER_TAIL,TAIL_ATR_TP_MAX_PCT
6.3 최근 실행 결과 (2026-05-18 ~ 2026-05-29, 830~1530, 저장 이력)
| 실행 | 조합 | 1위 거래 | 1위 손익 | env_id |
|---|---|---|---|---|
| fast 192 (진입+청산) | 192 | 6건 | +609,166 | 206 |
| fast 768 (+ tail_ratio, max_rec_3m) | 768 | 5건 | +690,252 | 207 |
768조합 대부분 동률 — skip_hts=False 병목 때문에 낙폭·회복·청산축을 바꿔도 거래 수가 거의 안 변함.
회복률 50%만 3건 등으로 더 줄어드는 예외 있음.
1위 (env 207) 적용값:
- 낙폭 2%, 회복 40%, 꼬리/몸통 1.0, 3분 회복 상한 90%
- 어깨 0.5%/0.2%, ATR 2.0/2.0, 익절 상한 1.0%
6.4 표준 실행 명령
python3 kis_trader/backtest/tail_param_search.py \
--start 2026-05-18 --end 2026-05-29 \
--time-start 830 --time-end 1530 \
--min_trades 1 --apply
결과 JSON: kis_trader/backtest/results/search_tail_fast_YYYYMMDD_HHMMSS.json
7. RSI 14
- 고정 아님 —
env_config.RSI_PERIOD(DB 기본 14) - 꼬리 3분봉 + RSI(14) ≈ 42분 창 과열 필터
- 스캘핑 RSI(3)와 별개
8. max_daily 함정
- 종목당 하루 N회 (계좌 전체 합산이 아님)
- 전종목 + 완화 진입 시 거래 수 폭증 가능
9. 가상 거래 내역 (최근 200건) — 정렬 수정
9.1 문제
all_trades[-200:]— 종목 코드 순 append → 최근 200건이 아님- 프론트
.reverse()만으로는 매도 시각 최신순 불가
9.2 수정 (backtest_web.py)
Python: _trades_recent_first(trades, 200)
exit_time/sell_time/sell_date기준 내림차순 후 200건- 적용: 실거래, 스캘핑, 꼬리, 돌파 API
JavaScript: tradesNewestFirst(trades)
- 모든 “가상 거래 내역 (최근 200건)” 테이블에 적용
- 맨 위 = 가장 최근 매도
10. 웹 백테스트 체크리스트 (파라서치와 맞출 때)
| 항목 | 맞춰야 할 값 |
|---|---|
| 기간 | --start / --end 동일 |
| TF | 3분 (ws_candles) |
| 매수 시간 | 830~1530 (웹 tl_ts / tl_te) |
| 저장 이력 | ON/OFF 동일 (`universe=history |
| TRIGGER 플래그 | 응답 JSON skip_hts_scan_dupes 등 확인 |
| 서버 | backtest_web.py 수정 후 재시작 |
경고 문구 (summary.universe_warning):
- 저장 이력 ON인데 거래 > 25건 → 파라미터/플래그 불일치 의심
- 수정 후에는 6건 전후가 정상 (
skip_hts=False기준)
11. 거래 빈도 목표 (주 5~10건) 시 권장
skip_hts만 토글하면 6건 ↔ 123건 cliff — 중간값 없음.
| 방향 | 조치 |
|---|---|
| A (권장) | 저장 이력 ON + TAIL_SKIP_HTS_SCAN_DUPES=true 고정, 진입 수치(min_drop, recovery, tail_ratio)로 조절 |
| B | skip_hts=False 유지 + 진입 수치 완화 (coarse/full) |
| C | fast 그리드에 skip_hts 축 추가 → 768×2=1536조합 (모드 선택용) |
12. 관련 env 키 (config_schema)
TAIL_SKIP_HTS_SCAN_DUPES
TAIL_USE_INTRADAY_DROP
TAIL_USE_MA20_FILTER
TAIL_USE_RSI_FILTER
TAIL_USE_DAILY_RANGE_FILTER
TAIL_USE_HIGH_CHASE_FILTER
TAIL_BAR_CHG_MIN_PCT / TAIL_BAR_CHG_MAX_PCT
MIN_DROP_RATE / MIN_RECOVERY_RATIO_SHORT
TAIL_RATIO_MIN / MAX_RECOVERY_RATIO_3M
SHOULDER_MIN_HIGH_PCT / SHOULDER_CUT_PCT
STOP_ATR_MULTIPLIER_TAIL / TARGET_ATR_MULTIPLIER_TAIL
TAIL_ATR_TP_MAX_PCT
13. 변경 이력 요약
| 날짜 | 변경 |
|---|---|
| 2026-05-30 | tail_backtest_common.py — 웹·파라서치 공통 로더 |
| 2026-05-30 | 웹 API TRIGGER 플래그 DB 병합 (123 vs 6건 버그) |
| 2026-05-30 | fast 그리드: 진입(min_drop, recovery, tail_ratio, max_rec_3m) + 청산, 768조합 |
| 2026-05-30 | 가상 거래 내역 매도 시각 최신순 정렬 |
14. 참고 파일
kis_trader/engine/tail_engine.py— SCAN/TRIGGER 주석,_eval_tail_buy_at_indexkis_trader/backtest/tail_param_search.py—PARAM_GRIDS["fast"]backtest_web.py—/api/backtest/tail,_trades_recent_first,tradesNewestFirst()docs/TAIL_BACKTEST_WEB_VS_PARAM_SEARCH.md— 꼬리 도메인·skip_hts·어깨컷 상세docs/BACKTEST_ALIGNMENT_FINAL.md— 코드 변경·실수 목록·다른 전략 적용 체크리스트 (최종)