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

10 KiB
Raw Permalink Blame History

꼬리잡기 백테스트 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_ver3get_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와 동일 구조이되, 매수/매도 판단만 엔진 호출로 통일하면 백테와 결과를 맞추기 쉬움.