Files
QuantEngineByItz/spec/08_scoring_rules.yaml
kjh2064 a4de0505a0 WBS-8.7 1단계: spec-코드 동기화 확장 (12.5%→22.22%)
3개 contract 파일 추가 태깅:
- spec/00_execution_contract.yaml (execution_slippage + snapshot_admin)
- spec/08_scoring_rules.yaml (score_thresholds + qualitative_sell)
- spec/09_decision_flow.yaml (execution_decision + routing_decision)

결과: 36/162 파일 (22.22% coverage)
목표: 50% 이상 (점진적 확장)

CI gate: PASS
2026-06-22 23:30:19 +09:00

425 lines
22 KiB
YAML

meta:
title: "은퇴자산포트폴리오 — 점수화·하드필터 명세"
parent_file: "RetirementAssetPortfolio.yaml"
version: "2026-05-16-F4_peg_scoring"
language: "ko-KR"
timezone: "Asia/Seoul"
role: "derived_adapter"
has_code_implementation: true
code_path: ["formulas/score_thresholds_v1.py", "src/quant_engine/qualitative_sell_strategy_v1.py"]
purpose: >
흩어진 점수화 규칙을 LLM이 일관되게 적용하도록 rule_id 기반으로 재정리한 명세.
기존 strategy/risk/data 규칙을 대체하지 않고, 판단 근거를 구조화해 연결한다.
authority_rule: "이 파일은 rule_id와 적용 순서를 제공하는 adapter다. 수치 임계값 충돌 시 canonical_ref 대상 파일을 우선한다."
scoring_policy:
source_priority:
1: "hard_filters — 실패 시 점수와 무관하게 BUY 금지"
2: "risk_adjustments — 점수 산출 후 등급/수량을 보수적으로 조정"
3: "strategy_score — 종목·섹터·진입 품질 평가"
4: "portfolio_fit_score — 계좌·비중·중복 노출 적합성 평가"
output_rule:
- "모든 BUY/HOLD/SELL/AVOID 판단은 사용한 rule_id를 rules_used에 기록한다."
- "점수는 결론의 보조 근거이며 hard_filter를 override하지 못한다."
- "데이터 누락 축은 지정된 missing_rule에 따라 0점 또는 중립점으로 처리하고, 누락 필드는 별도 출력한다."
hard_filters:
- id: "HF001_DATA_MATRIX_REQUIRED"
name: "데이터 완성도 매트릭스 필수"
condition: "data_completeness_matrix exists AND all_required_status_fields populated"
fail_action: "INSUFFICIENT_DATA"
canonical_ref: "spec/02_data_contract.yaml:quant_feed_contract.data_completeness_gate"
- id: "HF002_ATR20_REQUIRED_FOR_QUANTITY"
name: "ATR20 없으면 정수 매수수량 금지"
condition: "ATR20_Status == OK before buy quantity calculation"
fail_action: "NO_QUANTITY"
canonical_ref: "spec/05_position_sizing.yaml:position_sizing.volatility_targeting.requirements"
- id: "HF003_HOLDINGS_REQUIRED_FOR_SELL_QTY"
name: "보유수량 없으면 매도수량 금지"
condition: "confirmed_holding_quantity exists before sell quantity output"
fail_action: "NO_SELL_QUANTITY"
canonical_ref: "spec/07_output_schema.yaml:output_format.execution_guardrail.order_quantity_4stage_gate.stage_3"
- id: "HF004_FLOW_ROWS_20D_REQUIRED_FOR_A"
name: "20D 수급 기반 A등급 최소 행수"
condition: "Flow_Rows >= 20 when using 20D flow for A grade"
fail_action: "MAX_GRADE_B"
canonical_ref: "spec/02_data_contract.yaml:quant_feed_contract.investor_flow_rules.caution"
- id: "HF005_TOTAL_HEAT_HARD_BLOCK"
name: "Total_Heat 10% 이상 신규매수 차단"
condition: "Total_Heat < 10"
fail_action: "AVOID_NEW_BUY"
canonical_ref: "spec/risk/aggregate_risk.yaml:risk_control.aggregate_risk_cap.threshold.hard_block"
- id: "HF006_BUY_ORDER_SET_REQUIRED"
name: "매수 주문 6개 필드 세트 필수"
condition: "limit_price AND quantity AND stop_price AND stop_quantity AND take_profit_price AND take_profit_quantity"
fail_action: "INVALID_BUY_OUTPUT"
canonical_ref: "spec/07_output_schema.yaml:output_format.buy_proposal_template.validation"
# ── 재무 건전성 하드필터 (2026-05-18_FINANCIAL_HEALTH_V1) ────────────────────
- id: "HF007_OPERATING_LOSS_BLOCK"
name: "영업적자 종목 A등급 차단"
condition: "operating_margin_pct < 0 AND grade == 'A'"
fail_action: "MAX_GRADE_B"
rationale: >
영업이익이 음수인 종목은 본업 경쟁력 상실 상태.
수급·모멘텀이 강해도 A등급 부여 금지. B등급까지만 허용.
exception: "operating_margin_pct == DATA_MISSING → 필터 미발동 (데이터 부재 ≠ 적자)"
canonical_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.FINANCIAL_HEALTH_SCORE_V1"
- id: "HF008_EXTREME_LEVERAGE_WARNING"
name: "극단 부채비율 경고"
condition: "debt_to_equity >= 400 AND sector_type NOT IN ['bank', 'insurance', 'securities']"
fail_action: "ADD_WARNING_FLAG: EXTREME_LEVERAGE"
rationale: >
D/E >= 400%는 재무 구조 임계치. 차단이 아닌 경고 플래그 발동.
에이전트는 출력 시 [주의: 극단 부채비율] 레이블을 반드시 표기.
exception: "금융업(은행·보험·증권)은 레버리지 비즈니스 특성상 제외"
canonical_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.FINANCIAL_HEALTH_SCORE_V1"
- id: "HF009_OVEREXTENSION_BLOCK"
name: "이격도 과열 진입 차단 (Anti-Peak)"
condition: "current_price / ma20 <= 1.15"
caution_condition: "1.10 <= current_price / ma20 < 1.15"
fail_action: "BUY_HARD_BLOCK [MRG001]" # [2026-05-19_ALPHA_SHIELD_V1]
caution_action: "BUY_CAUTION [MRG001_SOFT] -- 신규 매수 강도 50% 이하로 축소"
canonical_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.MEAN_REVERSION_GATE_V1"
rationale: >
주가가 20일선 대비 15% 이상 급등한 상태는 '상투' 위험이 극도로 높음.
수급 점수가 100점이어도 신규 진입을 하드 블록하여 추격 매수를 원천 봉쇄함.
strategy_score:
id: "SS001_SECTOR_MODEL_SCORE"
max_score: 100
formula: "price_strength + volume_quality + flow_quality + earnings_revision + macro_regime + valuation + financial_health"
executable_formula_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.FLOW_CREDIT_V1"
field_dictionary_ref: "spec/12_field_dictionary.yaml:field_dictionary"
canonical_ref: "spec/strategy/sector_model.yaml:sector_model.score_axes_formula"
components:
price_strength:
max_points: 20 # [2026-05-18_FINANCIAL_HEALTH_V1] 25→20 (-5. 재무건전성 축 신설로 재배분)
rule_id: "SS001_P"
scoring: "섹터 내 1M 상대강도 상위 30% 이내=20, 30~60%=12, 60% 초과=0"
volume_quality:
max_points: 10 # [2026-05-18_FINANCIAL_HEALTH_V1] 15→10 (-5)
rule_id: "SS001_V"
scoring: "5D 거래대금/20D 평균 >=120%=10, 80~120%=6, 미만=0"
flow_quality:
max_points: 20 # [2026-05-18_FINANCIAL_HEALTH_V1] 25→20 (-5)
rule_id: "SS001_F"
scoring: "flow_credit >=0.70=20, 0.40~0.70=10, <0.40=0"
earnings_revision:
max_points: 15 # [2026-05-18_FINANCIAL_HEALTH_V1] 20→15 (-5. EPS 방향성은 재무건전성 축에 흡수)
rule_id: "SS001_E"
scoring: "EPS 컨센서스 상향=15, 유지=8, 하향=0"
macro_regime:
max_points: 10
rule_id: "SS001_M"
scoring: "Risk-On=10, Neutral=5, Risk-Off=0"
valuation:
max_points: 5
rule_id: "SS001_VAL"
scoring: "PER/PBR 섹터 평균 이하=5, 평균~1.5배=2, 1.5배 초과=0"
financial_health:
max_points: 20
rule_id: "SS002_FHS"
formula_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.FINANCIAL_HEALTH_SCORE_V1"
scoring: >
ROE(8pt) + 영업이익률(7pt) + 부채비율(5pt) + FCF 양부(5pt).
데이터 전체 결측 시 8pt 중립. 코스닥 결측 시 6pt. 영업적자 시 0pt + HF007.
ROE 음수 시 -5pt 페널티(총점 음수 가능 — clamp -5~20).
purpose: >
수급·모멘텀이 강해도 재무가 취약하면 점수가 낮아져 등급이 하향된다.
특히 ROE<0(적자), 영업적자, D/E>400% 종목을 수급 강세로 오진하는
모멘텀 편향을 정량적으로 차단한다.
score_vs_momentum_note: >
재배분 전: 수급/모멘텀 65점, 재무 0점.
재배분 후: 수급/모멘텀 50점(-15), 재무 20점(+20), 총 100점 유지.
(earnings_revision은 방향성 플래그 성격 — 재무 건전성과 별도 축 유지)
# [proposal_96 / 2026-05-16] 코스닥 종목은 PEG 기반 밸류에이션 점수 적용 (최대 12점)
kosdaq_override:
applicable: "코스닥 종목에만 적용. 기존 SS001_VAL 점수를 대체."
max_points: 12
rule_id: "SS001_VAL_KOSDAQ_PEG"
formula_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.PEG_SCORE_V1"
scoring:
peg_pass_1_0_or_below: 12
peg_pass_1_0_to_1_5: 9
peg_caution_1_5_to_2_0: 5
peg_caution_2_0_to_2_5: 2
peg_reject_above_2_5: 0
fallback_scoring:
per_below_2x_median: 9
per_2x_to_3x_median: 4
per_above_3x_median: 0
scoring_note: "PEG 기반 12점 체계로 총점 범위가 코스닥 107점, KOSPI 100점이 됨. grade_thresholds는 시장 구분 없이 100점 환산 비례 적용."
executable_rules:
- id: "SS001_P_PRICE_STRENGTH"
component: "price_strength"
input_field: "relative_strength_1m_percentile"
data_source: "core_satellite 탭 RS_Pct_20D (2026-05-17 추가). 100-RS_Pct_20D를 percentile로 사용. 예) RS_Pct_20D=80 → relative_strength_1m_percentile=20"
output_field: "price_strength_score"
max_points: 20 # [2026-05-18_FINANCIAL_HEALTH_V1] 25→20
rules:
- {if: "relative_strength_1m_percentile <= 30", points: 20}
- {if: "30 < relative_strength_1m_percentile <= 60", points: 12}
- {if: "relative_strength_1m_percentile > 60", points: 0}
missing_action: 0
- id: "SS001_V_VOLUME_QUALITY"
component: "volume_quality"
input_fields: ["avg_trade_value_5d", "avg_trade_value_20d"]
output_field: "volume_quality_score"
max_points: 10 # [2026-05-18_FINANCIAL_HEALTH_V1] 15→10
derived_field:
name: "volume_surge_ratio"
expression: "avg_trade_value_5d / avg_trade_value_20d"
rules:
- {if: "volume_surge_ratio >= 1.20", points: 10}
- {if: "0.80 <= volume_surge_ratio < 1.20", points: 6}
- {if: "volume_surge_ratio < 0.80", points: 0}
missing_action: 0
- id: "SS001_F_FLOW_QUALITY"
component: "flow_quality"
formula_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.FLOW_CREDIT_V1"
input_field: "flow_credit"
output_field: "flow_quality_score"
max_points: 20 # [2026-05-18_FINANCIAL_HEALTH_V1] 25→20
rules:
- {if: "flow_credit >= 0.70", points: 20}
- {if: "0.40 <= flow_credit < 0.70", points: 10}
- {if: "flow_credit < 0.40", points: 0}
missing_action: 0
- id: "SS001_E_EARNINGS_REVISION"
component: "earnings_revision"
input_field: "eps_revision_status"
output_field: "earnings_revision_score"
max_points: 15 # [2026-05-18_FINANCIAL_HEALTH_V1] 20→15
rules:
- {if: "eps_revision_status == 'UP'", points: 15}
- {if: "eps_revision_status == 'FLAT'", points: 8}
- {if: "eps_revision_status in ['DOWN', 'DATA_MISSING']", points: 0}
missing_action: 0
- id: "SS001_M_MACRO_REGIME"
component: "macro_regime"
input_field: "market_regime_state"
output_field: "macro_regime_score"
max_points: 10
rules:
- {if: "market_regime_state in ['RISK_ON', 'LEADER_CONCENTRATION']", points: 10}
- {if: "market_regime_state == 'NEUTRAL'", points: 5}
- {if: "market_regime_state in ['RISK_OFF', 'EVENT_SHOCK', 'UNKNOWN']", points: 0}
missing_action: 0
- id: "SS001_VAL_VALUATION"
component: "valuation"
input_fields: ["forward_pe", "sector_median_forward_pe", "pbr", "sector_median_pbr"]
data_source:
forward_pe: "data_feed 탭 Forward_PE"
pbr: "data_feed 탭 PBR"
sector_median_forward_pe: "sector_flow 탭 Sector_Median_PE (2026-05-17 추가)"
sector_median_pbr: "sector_flow 탭 Sector_Median_PBR (2026-05-17 추가)"
output_field: "valuation_score"
max_points: 5
rules:
- {if: "forward_pe <= sector_median_forward_pe OR pbr <= sector_median_pbr", points: 5}
- {if: "forward_pe <= sector_median_forward_pe * 1.5 OR pbr <= sector_median_pbr * 1.5", points: 2}
- {if: "forward_pe > sector_median_forward_pe * 1.5 AND pbr > sector_median_pbr * 1.5", points: 0}
missing_action: 0
# [proposal_96 / 2026-05-16] 코스닥 전용 PEG 기반 밸류에이션 점수 계산 규칙
- id: "SS001_VAL_KOSDAQ_PEG"
component: "valuation"
applicable: "코스닥 종목에만 적용 — SS001_VAL_VALUATION 대체"
input_fields: ["forward_pe", "eps_growth_3y_cagr_pct", "sector_median_forward_pe"]
output_field: "valuation_score"
max_points: 12
derived_field:
name: "peg"
expression: "forward_pe / eps_growth_3y_cagr_pct"
missing_condition: "eps_growth_3y_cagr_pct == DATA_MISSING OR eps_growth_3y_cagr_pct <= 0"
rules:
primary_peg:
- {if: "peg <= 1.0", points: 12}
- {if: "1.0 < peg <= 1.5", points: 9}
- {if: "1.5 < peg <= 2.0", points: 5}
- {if: "2.0 < peg <= 2.5", points: 2}
- {if: "peg > 2.5", points: 0}
fallback_per_only:
- {if: "forward_pe <= sector_median_forward_pe * 2.0", points: 9}
- {if: "forward_pe <= sector_median_forward_pe * 3.0", points: 4}
- {if: "forward_pe > sector_median_forward_pe * 3.0", points: 0}
missing_action: 0
output_note: "peg_gate_result도 동시 출력 (PASS/CAUTION/REJECT). formula_registry.PEG_SCORE_V1 참조."
- id: "SS002_FHS_FINANCIAL_HEALTH"
component: "financial_health"
formula_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.FINANCIAL_HEALTH_SCORE_V1"
input_fields: ["roe_pct", "operating_margin_pct", "debt_to_equity", "fcf_b", "sector_type"]
output_field: "financial_health_score"
max_points: 20
sub_components:
profitability_pts:
field: "roe_pct"
rules:
- {if: "roe_pct >= 15", points: 8}
- {if: "10 <= roe_pct < 15", points: 5}
- {if: "5 <= roe_pct < 10", points: 2}
- {if: "0 <= roe_pct < 5", points: 0}
- {if: "roe_pct < 0", points: -5}
missing_action: 4
operating_efficiency_pts:
field: "operating_margin_pct"
rules:
- {if: "operating_margin_pct >= 20", points: 7}
- {if: "10 <= operating_margin_pct < 20", points: 4}
- {if: "0 <= operating_margin_pct < 10", points: 2}
- {if: "operating_margin_pct < 0", points: 0}
missing_action: 3
financial_stability_pts:
field: "debt_to_equity"
financial_sector_skip_value: 3
rules:
- {if: "debt_to_equity < 50", points: 5}
- {if: "50 <= debt_to_equity < 100", points: 3}
- {if: "100 <= debt_to_equity < 200", points: 1}
- {if: "debt_to_equity >= 200", points: 0}
missing_action: 2
cash_generation_pts:
field: "fcf_b"
rules:
- {if: "fcf_b > 0", points: 5}
- {if: "fcf_b <= 0", points: 0}
missing_action: 2
expression: "clamp(profitability_pts + operating_efficiency_pts + financial_stability_pts + cash_generation_pts, -5, 20)"
missing_action: "all_missing → 8pt (코스닥: 6pt)"
- id: "SS001_TOTAL"
output_field: "strategy_score"
expression: "price_strength_score + volume_quality_score + flow_quality_score + earnings_revision_score + macro_regime_score + valuation_score + financial_health_score"
max_points: 100 # 재무건전성 20pt 포함. 총합 여전히 100pt.
normalization:
note: "KOSDAQ 종목은 SS001_VAL 최대 12점으로 원점수 최대 107점. 등급 산출 전 100점으로 정규화."
formula: "normalized_score = raw_score / (is_kosdaq ? 107 : 100) * 100"
output_field: "SS001_Norm_Score" # data_feed 탭 출력 컬럼
grade_thresholds_apply_to: "SS001_Norm_Score (not SS001_Total)"
example_kosdaq: "raw=85/107 → normalized=79.4 → grade=B (not A)"
missing_action: "sum available scores; missing components score 0"
financial_health_gate:
id: "FHG_RECOMMENDATION_ELIGIBILITY"
purpose: >
재무 건전성 점수(FINANCIAL_HEALTH_SCORE_V1)가 기준 미달인 종목을
차세대 유망 종목(신규 매수 추천 후보)에서 배제한다.
수급·모멘텀이 강해도 재무 부실 종목은 예외 없이 제외.
input_field: "financial_health_score"
thresholds:
eligible:
condition: "financial_health_score >= 10"
status: "ELIGIBLE"
label: "추천 가능"
note: "신규 매수 후보 정상 처리. 다른 게이트(HF, PCL) 통과 시 최종 추천."
watch_only:
condition: "8 <= financial_health_score < 10"
status: "WATCH_ONLY"
label: "관찰 대상 (매수 금지)"
note: >
신규 매수 금지. 관찰 목록에만 등재.
재무 지표 개선 확인 후 다음 분기에 재평가.
보유 중 종목에는 적용 안 됨 — 보유 종목은 FTB(fundamental_thesis_break) 기준 적용.
excluded:
condition: "financial_health_score < 8"
status: "EXCLUDED"
label: "재무 부실 제외"
note: >
추천 목록 제외. 보유 중 종목은 FTB1~FTB4 매도 트리거와 연동.
2분기 연속 EXCLUDED → fundamental_thesis_break 가중 검토.
holding_degradation:
purpose: "보유 중 종목의 재무 악화 누적 감지"
condition: "financial_health_score < 8 (2분기 연속)"
action: "fundamental_thesis_break 가중 — FTB 트리거 임계치 10% 완화 적용"
xref: "spec/exit/stop_loss.yaml:stop_loss_rules.fundamental_thesis_break"
missing_action: "financial_health_score == DATA_MISSING → WATCH_ONLY (결측 ≠ 양호)"
exception: "없음 — 수급·모멘텀 강세를 이유로 FHG 우회 금지"
canonical_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.FINANCIAL_HEALTH_SCORE_V1"
grade_thresholds:
canonical_ref: "spec/strategy/sector_model.yaml:sector_model.grade + spec/07_output_schema.yaml:recommendation_grade"
A:
min_score: 80
required:
- "hard_filters all pass"
- "raw data confirmation >= 80%"
- "Expected_Edge >= 1.5 and net_rr >= 2:1 when applicable"
- "integer quantity calculable"
B:
score_range: "65~79 또는 가격/데이터 일부 대기"
C:
score_range: "50~64 또는 핵심 데이터 일부 누락"
D:
score_range: "<50 또는 hard_filter fail"
portfolio_fit_score:
id: "PFS001_PORTFOLIO_FIT"
max_score: 100
formula: "account_fit*0.20 + concentration_fit*0.30 + duplicate_exposure_fit*0.25 + cash_fit*0.25"
components:
account_fit:
scoring: "계좌 세제/납입/자산위치 적합=100, 조건부=60, 부적합=0"
canonical_ref: "spec/01_objective_profile.yaml:account_policy"
concentration_fit:
scoring: "목표밴드 이내=100, +3%p 이내=70, +5%p 이내=40, 초과=0"
canonical_ref: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.target_allocation_structure"
duplicate_exposure_fit:
scoring: "중복노출 없음=100, 관리 가능=60, 상한 초과=0"
canonical_ref: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.duplicate_exposure_rule"
cash_fit:
scoring: "post_trade cash_floor 충족=100, review band=60, 미달=0"
canonical_ref: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.cash_floor"
executable_rules:
- id: "PFS001_TOTAL"
input_fields: ["account_fit_score", "concentration_fit_score", "duplicate_exposure_fit_score", "cash_fit_score"]
output_field: "portfolio_fit_score"
expression: "account_fit_score*0.20 + concentration_fit_score*0.30 + duplicate_exposure_fit_score*0.25 + cash_fit_score*0.25"
missing_action: "missing component = 0"
risk_adjustments:
- id: "RA001_RISK_POLICY_OVERRIDE"
condition: "any hard stop or risk hard block triggered"
action: "final_action cannot be BUY; grade max D or C according to data availability"
- id: "RA002_DATA_STALE_DOWNGRADE"
condition: "any required data_status == DATA_STALE"
action: "new order quantity not calculated; final_action WATCH or INSUFFICIENT_DATA"
- id: "RA003_EXPECTED_EDGE_FLOOR"
condition: "Expected_Edge < 1.5 OR Expected_Edge missing"
action: "A grade and immediate BUY prohibited"
reporting_requirement:
score_table_columns:
- "종목명"
- "strategy_score"
- "portfolio_fit_score"
- "hard_filter_result"
- "risk_adjustment"
- "최종등급"
- "사용 rule_id"
prohibition:
- "rule_id 없는 점수 근거 출력 금지"
- "hard_filter 실패 종목을 total_score만으로 BUY 승격 금지"
# [Work 8 / AFL V2 권고 #1] timing=None CANDIDATE 진입 조건 강화
# 근거: alpha_lead_threshold_optimizer_v1 분석 결과
# - timing=None CANDIDATE가 전체 5%+ 급등 미포착의 58%를 차지
# - timing=None 종목은 alpha_lead만으로 CANDIDATE에 올라 진입 트리거 없음
# 적용: AGENTS.md Direction B1 PULLBACK_ENTRY_TRIGGER_V1 필수화
candidate_entry_conditions:
timing_none_gate:
rule_id: "CEC001_TIMING_NONE_PULLBACK_REQUIRED"
condition: >
lead_entry_state == CANDIDATE_ONLY AND timing == None (timing 조건 미산출 상태)
required_additional_gate: PULLBACK_ENTRY_TRIGGER_V1
gate_condition: >
close <= MA20 * 1.03 (PULLBACK_ZONE)
OR volume >= avgVol5d * 1.2 (거래량 확인 돌파)
action_if_not_met: >
CANDIDATE_ONLY 유지, PILOT_ALLOWED 격상 금지.
timing=WAIT_PULLBACK_TRIGGER로 표기.
rationale: >
timing=None은 타이밍 신호 산출 불가 상태이므로 직전 MA20 근접 또는
거래량 확인 조건을 추가로 요구한다. 이 조건 없이 alpha_lead만으로 진입하면
급등 후 추격 매수가 돼 T5 정확도를 저하시킨다.
spec_ref: "AGENTS.md Direction B1: PULLBACK_ENTRY_TRIGGER_V1"
version: "2026-05-30_Work8"