meta: title: "은퇴자산포트폴리오 — 점수화·하드필터 명세" parent_file: "RetirementAssetPortfolio.yaml" version: "2026-05-16-F4_peg_scoring" language: "ko-KR" timezone: "Asia/Seoul" role: "derived_adapter" 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"