S307 — 4 trigger 원전 정합성 점검 + OF 30분봉 정책 + cp 동적 임계
Date: 2026-05-24 (Sun)
Topic: L (시스템 재설계)
Predecessor: S306 (5건 오답 원인 분석 + 결함 3종 식별)
PM 정책:
- OF는 30분봉/900봉 기준 (일봉 fallback 폐기)
- 원전에 있는 내용만 제대로 구현
- 룰베이스에서 최적해 불가능. 동적화 가능한 임계만 동적, 나머지는 관행 유지
1. OF 30분봉 정책 적용 (PM 1번 지시)
인프라
scripts/discovery/fetch_minute_30.py 신규 — 키움 ka10080 tic_scope=30 fetch + 표준 OHLCV 변환 + parquet 캐시
- 005930 검증: 900봉(2026-02-13 14:00 ~ 2026-05-22 15:30, 약 69거래일) 저장 →
data/minute_charts/30min/005930.parquet
키움 ka10080 응답 파싱
- 차트 키:
stk_min_pole_chart_qry
- 필드 매핑:
cntr_tm → Datetime, open_pric/high_pric/low_pric/cur_prc → OHLC(부호 제거), trde_qty → Volume
- 시간 역순 응답 → 정순 정렬 + 0가격 봉 제거
코드 변경
scripts/discovery/order_flow.py — 컬럼명 *_5d/_20d/_10d → *_5bar/_20bar/_10bar (30분봉 가정 명시). docstring에 입력 = 30분봉 OHLCV 명시
scripts/discovery/triggers/of_trigger.py — 재작성. 일봉 fallback 분기 전체 삭제. intraday_available 인자 제거. 30분봉 단일 모드.
scripts/discovery/triggers/aggregator.py — intraday_available → of_30m: Optional[dict] 교체. of_30m=None이면 OF trigger 자체를 호출하지 않음 (4 trigger로 verdict 산출). strong_threshold = max(3, n_total - 1)이라 자동 조정.
- caller 6곳 갱신: s303/s305/s306 백테스트, s303_smoke, _tmp_test_state4, _tmp_test_phase_triggers.
005930 30분봉 OF 결과 (검증)
bvc_buy_ratio_5bar: 0.487 / 20bar: 0.494 (둘 다 0.5 이하 = 매도 우위)
cvd_zscore: -0.838 (CVD 20봉 평균 이하 = 매도 누적)
divergence: 0 / absorption: 0 / exhaustion: 0
upper_wick 0.27 > lower_wick 0.14 (분배 흔적 가능)
decision: HOLD (활성 신호 없음)
5건 verdict 영향 (S306 일봉 기준)
- 일봉 백테스트는 30분봉 캐시 없음 → OF 자동 제외
- D1 KT&G만 AVOID → AVOID_STRONG 한 칸 강해짐 (strong_threshold 4→3, sell_votes 3 충족)
- 나머지 4건 동일
2. VSA cp 동적 임계 (PM 2번 지시, 종목별 52주 rolling)
발견 (정정)
vsa_trigger.py:120-121의 cp > 0.6 / cp < 0.4는 S302 결정 항목 아님. 출처 불명 임의 값.
vsa_trigger.py:120-121의 SC/BC mask에 wide_mask 강제 추가 — 원전 Williams SC/BC 정의에 wide 명시 없음. 임의 가드.
vsa_signals.py는 원전 디폴트(close_top=0.7 / close_bottom=0.3) 유지. trigger와 임계 불일치.
S307 동적화 (PM 결정)
scripts/discovery/triggers/_thresholds.py 신규 함수: compute_cp_climax_thresholds
- 윈도우: 252봉(52주, PM 결정 "52주 신고가 기준에 맞춰서")
- 측정 대상: vol_climax(Q90) 봉의 cp 분포 (vsa_trigger.py SC/BC mask가 high_vol_mask=Q90을 사용하므로 측정 대상 일치)
- cp_climax_top = 그 분포의 70th percentile (원전 0.7 "상위 30%" 의미 유지)
- cp_climax_bottom = 그 분포의 30th percentile
- fallback (표본 < 10): Williams 원전 0.7 / 0.3
compute_all_thresholds에 cp_climax_top/bottom + alias(close_top/close_bottom, vsa_signals.py 호환) 추가
vsa_trigger.py SC/BC mask: wide_mask 강제 제거 + 동적 cp 임계 적용
- supply_test / no_demand_bars도 동적 cp 적용 (line 132-133)
6건 동적 임계 결과 (sample_n=26)
| 종목 |
cp_climax_top (원전 0.7) |
cp_climax_bottom (원전 0.3) |
해석 |
| 005930 (latest) |
0.773 |
0.165 |
큰 vol 봉이 양극단 |
| 012450 한화에어로 9/30 |
0.841 |
0.480 |
신고가 추세 — bottom 0.480, 원전 0.3보다 높음. cp<0.480이면 BC 검출 (분배 흔적 더 잘 잡힘) |
| 005930 2022-09-30 |
0.611 |
0.261 |
양극단 살짝 약함 |
| 373220 LGES 11/01 |
0.795 |
0.231 |
양극단 명확 |
| 033780 KT&G 3/15 |
0.786 |
0.232 |
양극단 |
| 035720 카카오 10/15 |
0.835 |
0.142 |
매우 강한 양극단 (panic↔강한 반등 둘 다) |
5건 verdict 영향
- VSA 단독 decision은 모두 동일 (SC/BC count 변화는 있으나 판정부 pchg 조건 등이 우선)
- 5건 전체 verdict 동일
3. 4 trigger 원전 정합성 점검 결과
A. 원전 정합 (변경 불필요)
| trigger |
항목 |
출처 |
| VSA |
8 신호 정의(ND/NS/SV/CV/BC/SC/EUR/EDR) |
Tom Williams, Master the Markets |
| VSA |
윈도우 분리 (baseline 30 / climax 20 / nd_ns 3 / trend 20) |
Williams + Better Volume + S301 |
| Wyckoff |
TR 125봉 / EVENT 3봉 / SWING 5 / TREND 20 |
Pruden + StockCharts S301 |
| Wyckoff |
Spring/UT 침범 1% |
Robert Evans (wyckoffsmi.com) S302 |
| Wyckoff |
SOS/SOW/LPS/LPSY 검출 정의 |
Pruden 원전 정합 |
| AMT |
윈도우 20/60/120 multi-window |
Steidlmayer composite + Aspen S301 |
| AMT |
Value Area 0.70 |
Steidlmayer 1σ=68.27% |
| AMT |
POC stable band = ATR 25th percentile |
S302 PM 결정 |
| Bulkowski |
RECTANGLE 65 / FLAG 15 / PENNANT 20 / THROWBACK 30 |
thepatternsite.com |
| Bulkowski |
vol_confirm = 종목별 70th percentile |
S302 PM 결정 |
| Bulkowski |
Breakout = 패턴 상/하단 close 돌파 + trendline ≥2회 터치 |
원전 정합 |
| OF |
BVC (Easley/LdP/O'Hara 2012) / CVD divergence (Bookmap) |
30분봉 모드 (PM 결정) |
B. 관행 (외부 차티스트 표준, PM 인정)
| trigger |
항목 |
평가 |
| Wyckoff |
vol_mult > 1.2 (SOS/SOW 거래량 동반) |
원전 미명시지만 차티스트 관행 |
| Wyckoff |
breakout_tol = 1.5% |
원전 0.5~2% 범위 중간값 |
| Wyckoff |
ma_window = 20봉 |
관행 (Better Volume) |
| Wyckoff |
climax_vol_mult = 2.0 (SC/BC) |
원전 "2~3배" 권고 최소값 |
| Bulkowski |
top_touches/bot_touches ≥2 |
원전 정합 |
C. 동적화 적용 (S307)
| trigger |
항목 |
변경 |
| VSA |
SC/BC cp 임계 |
252봉 종목별 동적 (Q70/Q30) |
| AMT |
POC 정체 임계 poc_mig < 0.15 |
S302 poc_stable_band (ATR 25th) 동적 적용 (S307 추가) |
D. 원전 근거 없는 임의 가드 (유지 + 기록, PM 결정)
PM 원칙: "기준이 딱 맞추는 것은 어렵고 관행적으로 사용하는 것을 활용해도 문제가 될 것 같지 않다. 룰베이스에서 최적해는 불가능."
| trigger |
위치 |
임계 |
의미 |
| Wyckoff |
wyckoff_trigger.py:235, 239 |
last_age <= 5 |
SOS/SOW 발생 후 5봉 이내만 BREAKING |
| Wyckoff |
wyckoff_trigger.py:253 |
cause_bars >= 3 |
P&F cause (원전은 box count, 코드는 봉 수) |
| Wyckoff |
wyckoff_trigger.py:302 |
up_score + down_score >= 4 → conf +0.05 |
swing 시퀀스 강도 보너스 |
| AMT |
amt_trigger.py:95, 98 |
poc_mig > 0.3 / < -0.3 |
POC 이동 강 임계 |
| AMT |
amt_trigger.py:112-114 |
vah_dist_atr / val_dist_atr / nearest_lvn_atr < 0.5 |
VAH/VAL/LVN 근접 ATR 0.5 |
| AMT |
amt_trigger.py:116, 119, 122 |
rotation ≥4 / ≤-4 / abs<4 |
rotation factor 강/약 임계 |
| Bulkowski |
bulkowski_trigger.py:77-78 |
0.99 / 1.01 |
"터치" 1% 허용폭 |
| Bulkowski |
bulkowski_trigger.py:88 |
slope < range × 0.001 |
"수평" 0.1% 임계 |
| Bulkowski |
bulkowski_trigger.py:105 |
slope_diff < 0.3 |
flag 두 trendline 평행 30% |
| Bulkowski |
bulkowski_trigger.py:211 |
box_h > 15 (추세 판정) |
15% 임의 |
| Bulkowski |
bulkowski_trigger.py:37-38 |
RECTANGLE_HEIGHT_MAX=0.20, FLAG_HEIGHT_MAX=0.10 |
패턴 height 통계 기반 |
→ 유지. 동적화 작업 시도 시 임계 자체가 다른 의미인 경우가 많아 단순 percentile 매핑 부적합. PM 원칙 "최적해 불가, 관행으로 충분".
4. 5건 verdict 변화 (S306 → S307)
| Case |
S306 |
S307 |
변화 원인 |
| A3 한화에어로 |
MIXED_BUY_LEAN |
MIXED_BUY_LEAN |
동일 |
| B1 삼전 |
MIXED_SELL_LEAN |
MIXED_SELL_LEAN |
동일 |
| B3 LGES |
MIXED_SELL_LEAN |
MIXED_SELL_LEAN |
동일 |
| D1 KT&G |
AVOID |
AVOID_STRONG |
OF 제외로 strong_threshold 4→3 충족 |
| E1 카카오 |
MIXED_SELL_LEAN |
MIXED_SELL_LEAN |
동일 |
명백 오답 5건의 verdict는 4/5 동일. D1만 한 칸 강해짐(SELL 방향 그대로 강도 ↑). 5건 명백 오답 자체는 여전히 해결 안 됨 — 시스템 한계로 인정. S306 결함 분석 그대로 유효.
5. 시스템 상태 요약
변경된 파일
scripts/discovery/fetch_minute_30.py (신규)
scripts/discovery/order_flow.py (컬럼명 30분봉 명시)
scripts/discovery/triggers/of_trigger.py (재작성, 일봉 fallback 폐기)
scripts/discovery/triggers/aggregator.py (intraday_available → of_30m)
scripts/discovery/triggers/_thresholds.py (cp 동적 임계 함수 추가)
scripts/discovery/triggers/vsa_trigger.py (SC/BC mask 동적 cp + wide_mask 제거)
scripts/discovery/triggers/amt_trigger.py (poc_stable_band 적용 확장)
- caller 6곳 (intraday_available 인자 제거)
data/minute_charts/30min/005930.parquet (30분봉 캐시 1종)
미해결 (이월)
- 다른 종목 30분봉 캐시 (S307 시점 005930만 검증)
- 5건 명백 오답 자체는 시스템 한계로 인정. PM이 만능 차트 분석기 만들지 말라고 명시
- vsa_signals.py와 vsa_trigger.py 임계 체계 불일치 (vol/spread multiplier vs percentile). 일관 정렬은 별도 작업
- 카테고리 D 임의 가드 12+건은 유지 (PM 원칙 "관행으로 충분")
6. PM 핵심 교정 (S307)
- "OF는 30분봉/900봉" — PM 정책 확정. 일봉 fallback 작업이 무의미했음 확인 후 폐기.
- "원전에 있는 내용만 제대로 구현" — 임의 추가(2봉 쌍, AR/UR, vote 매트릭스, mw_override) 다 폐기.
- "임계는 한국 시장에 맞춰서 — 종목별 rolling" — cp 동적 임계 추가 (252봉 rolling).
- "관행으로 충분, 룰베이스에서 최적해 불가" — vol_mult 1.2 / breakout_tol 1.5% / ma_window 20 / climax_vol_mult 2.0 등 유지.
- "도식적 위치=감정 매핑 금지" — 본질 매트릭스(불균형/균형 × 가속/해소 × 방향) 외 가드 거부.
- "만능 차트 분석기 X" — 시장상황 + 재료가 더해져야 차트도 의미 있음. 5건 명백 오답을 차트 트리거로 해결하려던 방향 자체 폐기.