Files
kis_bot/docs/BACKTEST_ALIGNMENT_FINAL.md
Hwang 61c72a8a4c feat(tests): 신규 키움 웹소켓 조건검색 및 실시간 조건검색 테스트 추가
변경 사항
----
- _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>
2026-07-06 01:27:00 +09:00

13 KiB
Raw Blame History

백테스트·파라서치·웹 정렬 — 최종 단계 코드 정리 (2026-05-30)

대상: 꼬리(SHORT) 정렬 작업에서 여러 번 잘못 수정한 뒤 도달한 최종 패턴.
목적: 모멘텀·돌파·스캘핑 등 다른 전략에 같은 실수 없이 적용하기 위한 체크리스트.

관련 문서: TAIL_BACKTEST_WEB_VS_PARAM_SEARCH.md (꼬리 도메인 상세)


0. 최종 아키텍처 (한 줄)

단일 엔진 (tail_engine / scalping_engine / breakout)
    ↑
공통 백테 로더 (*_backtest_common.py)  ← 웹 API + param_search 둘 다
    ↑
get_*_defaults_from_db()  ← env 단일 소스

금지: 웹 API만 별도 루프·별도 손익식·폼 숫자만 params에 넣기.


1. 신규/수정 파일 요약

파일 상태 역할
kis_trader/backtest/tail_backtest_common.py 신규 유니버스·캔들·손익·run_tail_backtest_web_aligned
kis_trader/backtest/tail_param_search.py 수정 fast 그리드 확장, tbc 경유, --apply 그리드만 반영
kis_trader/engine/tail_engine.py 기존 get_tail_defaults_from_db, run_tail_backtest, V4 청산
backtest_web.py 수정 TRIGGER 플래그 병합, 거래 정렬, 꼬리 API → tbc

2. 코드 변경 상세

2.1 tail_backtest_common.py (다른 전략 템플릿)

해야 할 것:

함수 책임
date_keys(start, end) YYYY-MM-DDcandle_time
resolve_*_universe(...) target_candidates_history / 전종목 — 웹과 동일 쿼리
load_*_candles_by_code(...) ws_candles SQL — 웹과 byte 단위 동일
attach_*_trade_pnl(...) 수수료·세금·보유분 — 웹 jsonify와 동일식
run_*_backtest_web_aligned(...) engine.run_*_backtest 1회 + 손익 부착
fee_and_slot_from_env_row(...) FEE_RATE_PCT, SLOT_MONEY_DEFAULT

꼬리 구현:

# kis_trader/backtest/tail_backtest_common.py
trades = te.run_tail_backtest(candles_by_code, engine_params, universe_by_slot=...)
attach_tail_trade_pnl(trades, slot_money=..., fee_rate=..., sell_tax=...)

다른 전략 적용 시: momentum_backtest_common.py, breakout_backtest_common.py전략당 1파일, 엔진 import만 교체.


2.2 tail_param_search.py

fast 그리드 (최종)

"fast": {
    "min_drop_rate":      [0.02, 0.03],
    "min_recovery_ratio": [0.4, 0.45, 0.5],
    "tail_ratio_min":     [1.0, 1.5],
    "max_rec_3m":         [0.85, 0.9],
    "shoulder_min_high":  [0.003, 0.005],
    "shoulder_cut_pct":   [0.002, 0.003],
    "stop_atr_mult":      [1.5, 2.0],
    "target_atr_mult":    [1.5, 2.0],
    "atr_tp_max_pct":     [0.8, 1.0],
}
# FAST_MAX_COMBOS = 768  (2×3×2×2×2×2×2×2×2)

base_params (그리드 , DB 고정)

base_params = te.get_tail_defaults_from_db(db)
# + time_start_hm / time_end_hm CLI
# + scan_interval_min, force_eod_exit=False

그리드에 넣지 않은 것 (거래 수·정렬에 치명적):

  • skip_hts_scan_dupes, use_rsi_filter, use_daily_range_filter, …
  • TAIL_SKIP_HTS_SCAN_DUPES 등 env 플래그

백테 호출 (웹과 동일)

trades = tbc.run_tail_backtest_web_aligned(
    candles_by_code, test_params, universe_by_slot,
    slot_money=slot_money, fee_rate=fee_rate, sell_tax=sell_tax,
)

--apply (최종 — 그리드 축만 DB 반영)

# apply_from_json — target["params"] 우선, apply_cfg 전체 덮어쓰기 금지
p = dict(target.get("params") or {})
env_map = { ... min_drop_rate  MIN_DROP_RATE, shoulder_*  SHOULDER_* ... }
snap.update(env_map)  # latest snapshot 위에 merge

과거 실수: apply_cfg 전체를 DB에 저장 → 진입 완화값(예: rec 0.25)까지 실매에 박힘.


2.3 backtest_web.py/api/backtest/tail

(A) TRIGGER 플래그 DB 병합 — 123건 vs 6건 버그 수정

문제: 폼 숫자만 params에 넣음 → 엔진 _eval_* 기본값(True) 적용 → SCAN/TRIGGER 이중 필터 붕괴.

수정:

tail_trigger_flags = {
    "skip_hts_scan_dupes": _tail_bool_arg(request, "skip_hts_scan_dupes", _def.get(...)),
    "use_intraday_drop": ...,
    "use_ma20_filter": ...,
    "use_rsi_filter": ...,
    "use_daily_range_filter": ...,
    "use_high_chase_filter": ...,
    "bar_chg_min_pct": ...,
    "bar_chg_max_pct": ...,
}
params = { ... 숫자..., **tail_trigger_flags }

헬퍼: _tail_bool_arg(request, key, def_val) — 미전달 시 _def(= get_tail_defaults_from_db).

응답 JSON params에도 **tail_trigger_flags 포함 → 웹·파라서치 diff 디버깅용.

(B) 엔진 경로 + common 모듈

universe_by_slot, universe_source, universe_history_slots, _scan_iv = tbc.resolve_tail_universe(...)
candles_by_code, _, _ = tbc.load_tail_candles_by_code(...)
all_trades = tbc.run_tail_backtest_web_aligned(...)
tail_trades_out = _trades_recent_first(all_trades, 200)

레거시 else 분기 (use_engine=0) — 유니버스·V4 청산 미적용. 기본 use_engine=1 유지.

(C) 가상 거래 200건 정렬

문제: all_trades[-200:] — 종목 코드 순 append → “최근 200건” 아님.

수정 (Python, 전략 공통):

def _trade_exit_sort_key(t):  # exit_time / sell_time / sell_date ...
def _trades_recent_first(trades, limit=200):  # 매도 시각 내림차순 상위 N

수정 (JS, 모든 가상 거래 테이블):

function tradesNewestFirst(trades) { ... }
// .reverse() 대신 명시적 정렬 — API가 최신순이어도 프론트에서 재정렬

적용 API: 실거래 분석, 스캘핑, 꼬리, 돌파, 홀딩, Updow, 모멘텀.

(D) universe_warning (요약)

  • 저장 이력 요청했는데 universe_source=all → 이력 없음 경고
  • 저장 이력 ON + 거래 > 25건 → 플래그/파라미터 불일치 의심 (수정 전 123건 케이스)

3. 잘못 수정했던 것 — 다시 하지 말 것

# 잘못된 접근 왜 틀렸나 최종
1 웹만 레거시 루프 유지 유니버스·엔진 V4와 불일치 tbc + use_engine=1
2 파라서치 --applyapply_cfg 전체 저장 base 진입값까지 DB 오염 params(그리드 축)만 env_map
3 fast 그리드에 진입 완화 하드코딩 (FAST_BASE_OVERRIDES) 웹·실매 env와 불일치 제거, DB base 고정
4 웹 params에 TRIGGER 플래그 생략 skip_hts 등 엔진 default → 123건 tail_trigger_flags merge
5 skip_hts를 그리드에 넣어 768×2 탐색 모드 선택이지 미세 <20>uning 아님 env 고정 또는 정책 결정 후 고정
6 trades[-200:] + .reverse() 종목 순서 기준, 최신 아님 _trades_recent_first
7 UI sl_pct/tp_pct만 맞추면 된다 V4 청산은 어깨+ATR, UI %는 거의 무관 어깨·ATR 축 탐색
8 저장 이력 ON인데 거래 수만 보고 “버그” skip_hts=False면 6건이 정상 플래그·응답 JSON 확인
9 backtest_web 수정 후 서버 미재시작 구 코드로 123건 재현 재시작 필수

4. 조심해야 할 것 (최종 체크리스트)

4.1 웹 API ↔ param_search ↔ 실매 3-way

  • get_*_defaults_from_db() 단일 소스
  • params = 폼 숫자 + DB bool/플래그 전부 (엔진 default에 맡기지 않음)
  • 유니버스: strategy_id, history|all, scan_interval_min 동일
  • time_start_hm / time_end_hm / timeframe 동일
  • slot_money, fee_rate, sell_tax 동일 소스 (fee_and_slot_from_env_row)
  • force_eod_exit 의도 확인 (기본 OFF → 며칠 보유 가능)

4.2 SCAN vs TRIGGER

  • 저장 이력 ON → skip_hts_scan_dupes=True 권장 (HTS A 이중 필터 방지)
  • DB TAIL_SKIP_HTS_SCAN_DUPES=False → 거래 극소(6건) — 정책인지 버그인지 명시
  • 무거운 필터는 param_search 그리드 또는 TRIGGER, SCAN(5분)에는 넣지 않음

4.3 param_search --apply

  • 그리드 keys → env_map 화이트리스트만
  • base_params 전체 저장 금지
  • 총손익 ≤ 0 결과 apply 스kip (기존 유지)

4.4 청산 파라미터 해석

  • UI 손절 3% / 익절 5% ≠ 실제 1순위 청산 (어깨컷 V4)
  • fast/coarse 그리드: shoulder_, stop/target_atr_mult, atr__pct
  • sl_pct는 주로 포지션 사이징 (static_sl_pct)

4.5 UI/표시

  • trades = _trades_recent_first(..., 200) — 매도 시각 기준
  • 프론트 tradesNewestFirst().reverse()만 쓰지 않음
  • 응답 params에 플래그 노출 — 재현용

4.6 검증 명령 (꼬리)

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

웹 동일 조건 실행 후:

  • total_trades 일치 (±0)
  • params.skip_hts_scan_dupes 일치
  • universe_source / universe_history_slots 일치

5. 다른 전략 적용 로드맵

5.1 공통 패턴 (복사 순서)

  1. {strategy}_backtest_common.py 생성 (§2.1 함수 세트)
  2. param_search_{strategy}.py에서 tbc.run_*_web_aligned 호출
  3. backtest_web.py API route:
    • _get_*_defaults_for_backtest() → 엔진 get_*_defaults_from_db
    • {strategy}_trigger_flags dict (bool 플래그 전부 merge)
    • tbc 로더 + _trades_recent_first
  4. --apply env_map 그리드 키만 매핑 테이블 작성
  5. 문서 + 3-way 검증 1회

5.2 전략별 참고

전략 엔진 strategy_id 저장 이력 플래그 예시
꼬리 SHORT tail_engine SHORT 기본 skip_hts_scan_dupes, use_rsi_filter
모멘텀 scalping_engine (조건식 id) 확인 필요 MOMENTUM_SKIP_HTS_SCAN_DUPES
돌파 breakout BREAKOUT _resolve_backtest_universe HTS H 등
스캘핑 scalping_engine optional use_defense_filters, use_macd_cross

모멘텀/스캘핑: 이미 get_scalping_defaults_from_db() 사용 중 — 웹 API에 bool 플래그 merge 여부만 재점검.

돌파: api/backtest/breakout_trades_recent_first 적용됨, common 모듈 분리는 미완 → 꼬리 패턴 이식 후보.

5.3 env_map 템플릿 (꼬리 --apply)

# 그리드 params key → env_config column
PARAM_TO_ENV = {
    "min_drop_rate": "MIN_DROP_RATE",
    "min_recovery_ratio": "MIN_RECOVERY_RATIO_SHORT",
    "max_rec_3m": "MAX_RECOVERY_RATIO_3M",
    "tail_ratio_min": "TAIL_RATIO_MIN",
    "shoulder_min_high": "SHOULDER_MIN_HIGH_PCT",
    "shoulder_cut_pct": "SHOULDER_CUT_PCT",
    "stop_atr_mult": "STOP_ATR_MULTIPLIER_TAIL",
    "target_atr_mult": "TARGET_ATR_MULTIPLIER_TAIL",
    "atr_tp_max_pct": "TAIL_ATR_TP_MAX_PCT",
    # ... 그리드에 없는 키는 넣지 않음
}

6. 청산(V4) — 전략 공통 개념 (꼬리)

1순위  어깨컷 (shoulder_min_high + shoulder_cut_pct) — 변형 트레일링
2순위  ATR 익절 (target)
3순위  ATR 손절 (stop)
4순위  금액손실컷 (max_loss_krw, 어깨 미발동 시)
5순위  장마감 (force_eod_exit=True 일 때만)
  • UI tp_pct 5% 는 2순위까지 거의 도달 전 1순위에서 대부분 청산.
  • EOD OFF → 며칠 보유 + 어깨 → 소룩스형 +20% (백테만 해당 가능).

7. 변경 이력 (코드 기준)

일자 파일 변경
2026-05-30 tail_backtest_common.py 신규 — 웹·파라서치 단일 진입
2026-05-30 backtest_web.py tail_trigger_flags, _trades_recent_first, tradesNewestFirst
2026-05-30 tail_param_search.py fast 768조합, tbc 경유, apply 그리드만
2026-05-30 docs/TAIL_BACKTEST_WEB_VS_PARAM_SEARCH.md 꼬리 도메인·skip_hts 상세

8. 빠른 디버깅

웹 vs CLI 거래 수 다를 때:

  1. backtest_web 재시작했는가?
  2. 응답 JSON: skip_hts_scan_dupes, universe_source, time_start_hm, timeframe
  3. CLI: get_tail_defaults_from_db() 출력 vs 웹 params diff
  4. 저장 이력 ON/OFF 동일한가?
  5. force_eod_exit 동일한가?

거래 표 순서 이상할 때:

  • API가 _trades_recent_first 쓰는지
  • 프론트가 tradesNewestFirst 쓰는지 (.reverse() 단독 X)

9. 관련 파일 경로

kis_trader/backtest/tail_backtest_common.py
kis_trader/backtest/tail_param_search.py
kis_trader/engine/tail_engine.py
backtest_web.py                    # /api/backtest/tail, 유틸 함수
docs/TAIL_BACKTEST_WEB_VS_PARAM_SEARCH.md
docs/BACKTEST_ALIGNMENT_FINAL.md   # 본 문서

마지막 갱신: 2026-05-30 — 꼬리(SHORT) 정렬 최종 단계 기준.