meta:
  title: "은퇴자산포트폴리오 — 데이터 원천·완성도·검증 명세"
  parent_file: "RetirementAssetPortfolio.yaml"
  version: "2026-05-15-F1_modular"
  language: "ko-KR"
  timezone: "Asia/Seoul"
  has_code_implementation: true
  code_path: "tools/validate_specs.py"
  purpose: "메인 manifest에서 로드되는 구조화 규칙 명세 파일."

quant_feed_contract:
  primary_raw_json:
    file: "GatherTradingData.json"
    source_workbook: "GatherTradingData.xlsx"
    role: "provided_raw_analysis_data_json"
    priority: "highest_user_provided_market_data"
    schema_version: "2026-05-18-json-raw-data-v1"
    required_paths:
      - "data.data_feed"
      - "data.sector_flow"
      - "data.macro"
      - "data.event_risk"
      - "data.core_satellite"
    conversion_tool: "tools/convert_xlsx_to_json.py"
    usage_rule:
      - "종목 후보, 가격, 수급, 섹터 흐름, 매크로, 이벤트 위험은 이 JSON을 1차 raw 데이터로 사용한다."
      - "xlsx는 GAS/Google Sheets 원본·감사·JSON 재생성 소스이며 일반 LLM 분석에서 직접 파싱하지 않는다."
      - "공개 웹 조회는 JSON 누락·오래됨·충돌 검산용 보조 소스다."
      - "JSON 값과 웹 조회 값이 충돌하면 data_rule.conflict_action을 적용하고 평균값을 사용하지 않는다."
    validation_tool: "tools/validate_data_sample_json.py"
    mapping_file: "spec/14_raw_workbook_mapping.yaml"
    account_snapshot_contract: "spec/15_account_snapshot_contract.yaml"
    prohibition:
      - "JSON이 제공된 상태에서 웹 데이터만으로 종목 점수·수량을 확정하지 않는다."
      - "JSON에 없는 필드를 LLM이 임의 생성하지 않는다."
      - "JSON 검증 실패 상태에서 BUY·SELL 수량을 확정하지 않는다."
  source_priority:
    0: "사용자 제공 raw 데이터: 'GatherTradingData.json'.data[core_satellite,event_risk,macro,sector_flow,data_feed]"
    1: "AI 직접 공개 데이터 조회: Naver Finance, Yahoo Finance, KRX, OpenDART"
    2: "사용자 제공 원자료: HTS, 증권사, FnGuide, KRX, 네이버금융 화면"
    3: "부록 자동화 산출물: 공개 URL·기준시각·필드 의미 검증된 경우에만"
  account_holdings_snapshot:
    file: "사용자 캡처/원장 자료"
    usage: "계좌별 보유수량·평단·평가금액·현금 raw 원장. Quantity·Avg_Cost·Cash_Available 없이 정수 주문수량 산출 금지."
    prohibition:
      - "공개 데이터로 보유수량·평단·현금을 추정하지 않는다."
      - "ticker_master·data_feed.csv에 있다는 이유로 실제 보유 중이라고 단정하지 않는다."
      - "자동투자 화면 월 적립금액을 잔고·보유수량으로 환산하지 않는다."
    screen_type_separation:
      canonical_ref: "spec/00_execution_contract.yaml:capture_read_ledger.screen_type_rule"
      note: "[XREF / proposal_120] 이 섹션 대신 capture_read_ledger.screen_type_rule을 읽는다."
    image_extraction_rules:
      label_first_principle: "화면 내 숫자를 먼저 읽지 않는다. 숫자 왼쪽 또는 위쪽 라벨을 먼저 확인하고 계좌·항목을 특정한 뒤에만 값을 추출한다."
      exclude_screens:
        if_contains_any: ["투자내역 상세", "회차당 투자금액", "투자 회차", "모으기 신청일", "다음 투자(예정)일", "한도설정금액", "한도사용금액", "납입가능금액", "ISA 가입 정보", "isa 10년투자", "10년 연금저축", "매월 1일 투자"]
        action: "CAPTURE_PROVIDED_BUT_NOT_HOLDINGS로 분류하고 예수금 판독 제외"
      direct_cash_read:
        rule: "총금액 - 평가금액 계산 방식은 사용"
        required_confirmation:
          - "어떤 계좌(일반계좌/ISA/연금저축)의 예수금인지 라벨로 먼저 확인"
          - "'예수금' 또는 '주문가능금액' 라벨 확인 후 값 추출"
          - "원화(원) 단위 확인"
        validation: "예수금 >= 0 확인. 상식 범위 벗어나면 판독 재확인 요청."

  direct_public_lookup:
    steps:
      - "KRX: 상장상태·시장구분·가격·거래대금·시가총액·투자자별 거래"
      - "OpenDART: 공시 원문·실적·계약·증자·CB/BW·지분변동 촉매·리스크"
      - "Naver Finance: 가격 보조·거래대금·외국인·기관 5D/20D 순매매"
      - "Yahoo Finance: OHLC 보조·ATR20·20/60거래일 수익률"
    rule: "모든 숫자에 기준일·기준시각·출처·데이터태그 필수. [웹확인:출처명] 또는 [계산값] 태그."

  investor_flow_rules:
    source: "'GatherTradingData.json'.data.data_feed, 'GatherTradingData.json'.data.core_satellite, Naver Finance frgn 또는 KRX 투자자별 거래 직접 조회"
    accepted_fields: ["Frg_5D(sh)", "Inst_5D(sh)", "Frg_20D(sh)", "Inst_20D(sh)", "Ind_5D(sh)", "Flow_Rows", "Flow_Unit=shares", "Flow_OK"]
    individual_flow:
      field: "Ind_5D(sh) — 개인 5D 순매수(주식수)"
      purpose: "과열 경보 및 수급 우위 확신도 보조 확인. A등급 gating 조건에는 포함하지 않는다."
      scoring_rules:
        high_conviction: "Frg_5D↑ + Inst_5D↑ + Ind_5D↓ → 스마트머니 집중·개인 이탈. 수급 우위 확신도 상향."
        caution_signal: "Frg_5D↓ + Inst_5D↓ + Ind_5D↑ → 개인 쏠림 경계. 신규 진입 시 과열 위험 표시."
        late_cycle: "외인·기관·개인 모두 순매수 → 추세 후반부 가능성. 추격매수 주의."
      not_required: "Ind_5D(sh)가 DATA_MISSING이어도 Flow_OK 판정에는 영향 없음."
    caution:
      - "Flow_OK=Y가 아니면 외국인·기관 수급 점수 0점."
      - "Flow_Rows<20이면 20D 수급 판단 금지."
      - "주식 수 단위 수급을 금액 수급으로 임의 환산 금지."
      - "Frg_20D(M), Inst_20D(M)은 legacy 더미 컬럼이므로 사용 금지."
      - "Ind_5D(sh)는 보조 경보 필드다. 개인 순매수라는 이유만으로 A등급 하향 또는 매도 결정 금지."
    active_quality_gate:
      # [Q4 / 2026-05-15] C1/C2/C3 시간 기준 미명시로 LLM이 모두 5D로 오산출하거나
      # C3의 5D 합산을 "당일" 수급으로 오독하는 할루시네이션 방지.
      formula: "flow_credit = C1 × 0.30 + C2 × 0.30 + C3 × 0.40"
      time_scope_summary: "C1·C2는 당일 단일 바 기준, C3는 최근 5거래일 누적 기준. 혼용 금지."
      components:
        C1_price_action:
          time_scope: "당일 단일 바 (종가 vs 시가, 또는 종가 vs 전일 종가)"
          definition: "당일 종가가 시가 이상(양봉) 또는 전일 대비 상승 시 1점, 미충족 시 0점"
          weight: 0.30
        C2_volume_action:
          time_scope: "당일 거래량 vs 최근 5거래일 평균거래량 (AvgVolume_5D_shares 단위 — 주식 수. AvgTradeValue_5D_M 억원 단위 혼용 금지)"
          definition: "당일 거래량이 최근 5일 평균의 120% 이상 시 1점, 미충족 시 0점"
          weight: 0.30
        C3_flow_action:
          time_scope: "최근 5거래일 누적 순매수 (Frg_5D_sh + Inst_5D_sh 합산. 주식 수 단위)"
          definition: "외국인+기관 합산 5D 순매수 양수 시 1점, 미충족 시 0점"
          weight: 0.40
          tier_threshold:
            대형: "5D평균거래대금 >= 1,000억 → 기관 합산 순매수 50K주/일 이상 확인 권고"
            중형: "5D평균거래대금 100억~1,000억 → 10K주/일 이상"
            소형ETF: "5D평균거래대금 < 100억 → 2K주/일 이상"
      pass_condition:  # [P130] 예외 조건 구조화 — 문자열 매몰 제거
        full_credit:
          threshold: "flow_credit >= 0.70"
          action: "수급 가점 100% 부여"
        partial_credit:
          threshold: "0.40 <= flow_credit < 0.70"
          action: "수급 가점 30%만 인정"
          override_to_reject:
            condition: "C1 == 0 AND C2 == 0"
            action: "reject 처리 (C3 단독 충족 = 물량 받기로 간주)"
        reject:
          threshold: "flow_credit < 0.40"
          also_reject_if: "C1 == 0 AND C2 == 0 (flow_credit 수치 무관)"
          action: "수급 점수 0점. kelly.brake_conditions 발동 검토."
      prohibition:
        - "주가 하락 중 기계적 순매수만 유입되는 '물량 받기' 종목을 A등급으로 추천 금지"
        - "C3만 충족(C1·C2 모두 0점)인 경우 flow_credit 수치에 관계없이 reject 처리"
        - "flow_credit 미산출 상태에서 수급 기반 A등급 부여 금지"

  data_completeness_gate:
    buy_ready: "identity·price_value·investor_flow(20D)·volatility_atr·momentum_value_surge 모두 OK, DATA_CONFLICT 없음 → A등급·즉시매수 검토"
    watch_only: "identity·price_value만 OK → C-관찰까지"
    field_status:
      OK: "원시값·기준일·출처·단위 모두 확인"
      PARTIAL: "일부 행·기간만 확인"
      DATA_MISSING: "Plan A/B/C까지 모두 실패"
      DATA_CONFLICT: "출처 간 수치 불일치"
      NOT_APPLICABLE: "ETF·신규상장 등 구조적 미해당"
    fallback_ladder:
      ATR20: "Naver 일별시세(sise_day.naver) 21거래일 OHLC로 계산. Naver 실패 시 KRX 또는 보조 Yahoo로 전환. ATR20_OK=Y는 21거래일 OHLC 원시행 확인 시에만."
      investor_flow: "Naver 실패 → KRX 투자자별 거래"
      trading_value: "KRX 거래대금 또는 Close×Volume 계산값"
    mandatory_collection_sequence: ["①종목코드·상장상태", "②가격·거래량·거래대금", "③5D 수급", "④20D 수급", "⑤21거래일 OHLC로 ATR20·MA20·Val_Surge 계산", "⑥52주 고저·목표가 참고", "⑦OpenDART 공시 촉매·리스크", "⑧필드 완성도 매트릭스 출력 후 등급·매매 판단"]
    prohibition:
      - "필드 완성도 매트릭스 없이 종목 등급 또는 매수·매도 결론 먼저 작성 금지."
      - "로컬 파일 부재를 분석 중단 사유로 사용 금지."
      - "자동화 실패와 공개 직접 조회 실패를 같은 의미로 사용 금지."

  data_maturity_truth_gate:
    objective: "데이터 형식 완성도와 실전 성숙도를 분리한다."
    required_outputs:
      - "data_integrity_score"
      - "data_maturity_score"
      - "pending_critical_category_count"
      - "pending_critical_categories"
    maturity_penalty_rule: "PENDING 카테고리는 분모 제외가 아니라 maturity penalty로 반영한다."
    report_rule:
      - "보고서 첫 섹션에 data_integrity_score와 data_maturity_score를 함께 표시한다."
      - "data_integrity_score=100이어도 pending_critical_category_count>0이면 PASS_100 문구를 쓰지 않는다."

  json_analysis_protocol:
    purpose: "GatherTradingData.json은 DB 기반 수집 결과를 바탕으로 생성된 파생 보고서 증빙이다. 최종 보고서 렌더링과 data_completeness_matrix 참고용으로만 사용한다."
    python_parsing_baseline:
      shell_rule: "PowerShell에서는 Bash heredoc 금지. '@ ... @ | python -' 형식으로 실행."
      json_load_rule: "json.loads(Path('GatherTradingData.json').read_text(encoding='utf-8'))를 기본값으로 사용하되, 원천 추적은 SQLite DB의 history와 snapshot tables를 우선 확인한다."
      required_top_level: ["metadata", "data"]
      required_schema_version: "2026-05-18-json-raw-data-v1"
      required_paths: ["data.data_feed", "data.sector_flow", "data.macro", "data.event_risk", "data.core_satellite"]
      code_column_rule:
        text_columns: ["Ticker", "ETF_Code", "Proxy_Ticker", "Base_Ticker", "Constituent_Code", "ETF_Ticker", "Symbol", "ticker"]
        normalization: "숫자로 읽힌 91160.0, 5930.0 등은 문자열화 후 6자리 zero-pad 적용."
      validation_commands: ["npm run validate-data-sample", "npm run validate-specs"]
      xlsx_refresh_rule: "xlsx 원본을 갱신했으면 먼저 DB에 반영한 뒤, 엔진이 DB를 읽어 JSON 파생 보고서를 재생성하고 다시 검증한다."

  database_first_operating_model:
    purpose: "운영 이력, 원천 팩터, 파생 최종 팩터, 시장-결과 괴리를 PostgreSQL에 누적해 엔진을 고도화한다."
    canonical_store:
      primary: "PostgreSQL"
      secondary: "SQLite transient cache only"
      prohibited_operating_path:
        - "Excel workbook as operational source"
        - "Google Apps Script as operational source"
    history_domains:
      - "market_raw_history"
      - "factor_version_history"
      - "factor_output_history"
      - "decision_result_history"
      - "market_vs_engine_gap_history"
    policy:
      - "최종 팩터와 최종 판단은 DB 이력 테이블에 버전과 시각을 함께 남긴다."
      - "시장 raw와 엔진 결과의 괴리는 별도 gap history로 적재한다."
      - "엑셀/시트/Apps Script는 더 이상 운영 경로가 아니라, 역사적 import/export 또는 폐기 대상만 허용한다."
      - "새 분석·리포트는 PostgreSQL snapshot을 1차 진실원천으로 사용한다."
  xlsx_analysis_protocol:
    purpose: "xlsx는 HTS 잔고·거래내역 판독 또는 DB 반영 이전의 보조 감사 소스다. 시장 raw 일반 분석과 최종 보고서 생성은 DB 추적 후의 파생 JSON을 우선한다."
    python_parsing_baseline:
      shell_rule: "PowerShell에서는 Bash heredoc 금지. '@ ... @ | python -' 형식으로 실행."
      openpyxl_read_rule: "값 점검은 openpyxl.load_workbook(path, data_only=True, read_only=True)를 기본값으로 사용."
      header_rows:
        gas_output_sheets: "대부분 row1=updated 메타, row2=헤더, row3부터 데이터."
        universe: "row1=헤더, row2부터 데이터."
        settings: "key-value 구조. 일반 row2 헤더 시트로 오인 금지."
      row_count_rule: "ws.max_row는 서식 범위 때문에 3000일 수 있으므로 데이터 행 수로 사용 금지. 첫 컬럼 또는 핵심 키 컬럼 non-empty 행만 센다."
      code_column_rule:
        text_columns: ["Ticker", "ETF_Code", "Proxy_Ticker", "Base_Ticker", "Constituent_Code", "ETF_Ticker", "Symbol"]
        normalization: "숫자로 읽힌 91160.0, 5930.0 등은 문자열화 후 6자리 zero-pad 적용."
      access_rule: "열 번호 하드코딩 금지. 헤더 행에서 header_map을 만든 뒤 컬럼명으로 접근."
      validation_commands: ["npm run validate-xlsx-source", "npm run convert-data-json", "npm run validate-data-sample", "npm run validate-specs"]
      temporary_sheet_rule: "cs_chunk_N은 core_satellite_status.Status=COMPLETE AND Coverage_Pct>=99.9일 때만 삭제 가능."
      sheet_diet_policy:
        canonical_required: ["data_feed", "sector_flow", "macro", "event_risk", "core_satellite"]
        retained_support:
          - "settings"
          - "account_snapshot"
          - "sector_universe"
          - "sector_flow_history"
          - "etf_nav_manual"
          - "universe"
          - "monthly_history"
          - "performance"
        deprecated_sheets:
          - "positions"
          - "chat_input"
          - "etf_raw"
          - "core_satellite_status"
          - "orbit_gap"
          - "asset_history"
        transient_delete_when_complete: ["cs_chunk_N"]
        rules:
          - "orbit_gap·asset_history는 monthly_history(16컬럼)로 통합됐다."
          - "etf_raw는 GAS in-memory map 전환으로 시트 쓰기 중단."
          - "core_satellite_status는 ScriptProperties로 이전."
          - "cs_chunk_N은 core_satellite 병합 완료 후 삭제해 workbook 비대화를 방지한다."
    step_1_file_classification:
      types:
        holdings_xlsx: "보유종목·수량·평단·현금 포함 → CAPTURE_READ_OK"
        transaction_xlsx: "거래내역·체결가·수수료 포함 → CAPTURE_READ_OK (거래 검증용)"
    step_2_column_mapping:
      label_first_principle: "셀 값보다 헤더 행(보통 1~3행)을 먼저 읽어 컬럼 의미를 확정."
      required_columns_for_holdings: ["계좌명 또는 계좌번호", "종목명 또는 종목코드", "보유수량(주)", "평균단가(원/주)", "현재가(원/주)", "평가금액(원)", "예수금 또는 주문가능금액(원)"]
      unit_validation:
        quantity: "정수. 소수 발견 시 PARSE_FAILED."
        price: "원/주. 단위 확인 불가 시 DATA_CONFLICT."
        amount: "원. 총금액과 단가×수량 오차 1% 초과 시 DATA_CONFLICT."
    step_3_data_validation:
      cross_check:
        - "평가금액 = 보유수량 × 현재가. 오차 0.5% 초과 시 DATA_CONFLICT."
        - "예수금 = 총평가금액 - 보유종목 합산 평가금액 (단순 검증)."
      hard_stop:
        - "헤더 행 없거나 빈 파일 → PARSE_FAILED."
        - "수량 열 전체 공백 → unknown_xlsx로 강등."
        - "통화 단위가 달러($) → 환율 확인 전 금액 산출 금지."
    step_4_ledger_integration:
      rule: "유효성 검사 통과 후 capture_read_ledger에 기재."
      ledger_row_format: "[파일화면, 계좌, 화면종류, 읽은값, 확신도, 주문표반영, 판독상태]"
    step_5_prohibited:
      - "research_xlsx의 컨센서스·목표가만으로 보유수량·평단 대체 금지."
      - "xlsx의 수익률 컬럼을 실현수익률로 단정 금지 (미실현 포함 여부 불명)."
      - "여러 xlsx가 동일 종목 다른 수량 제공 시 → DATA_CONFLICT, 최신 파일 우선 후 사용자 확인."
      - "거래내역 xlsx를 잔고 xlsx로 오인 금지."
    step_6_supplementary_types:
      fnguide_consensus_xlsx: "컨센서스 참고용. 보유수량·주문수량 산출 금지."
      trading_history_xlsx: "실현손익 검증용. 잔고 보유수량 추정 사용 금지."

ticker_master:
  status: "dynamic_reference_from_latest_account_snapshot"
  scope: "첨부 잔고·계좌 원장에서 확인된 보유 종목의 코드 매핑. 후보 유니버스나 고정 보유 목록 사용 금지."
  refresh_rule: "최신 계좌 자료에 없는 종목은 보유종목으로 출력 금지. 최신 자료에 새 종목은 KRX 코드 확인 후 임시 매핑."
  prohibited_use: ["있다는 이유로 보유 중 단정", "없다는 이유로 분석 제외", "위성 후보 추출 유니버스로 사용"]

data_rule:
  canonical: true  # [proposal_122 / 2026-05-15] 투자 판단 시 이 섹션의 rules가 최우선. data_snapshot_precedence·anti_hallucination보다 우선.
  supersedes: ["data_snapshot_precedence.conflict_rule", "anti_hallucination.precedence"]
  required_label: "모든 숫자에 기준시각·출처·데이터유형 표시"
  data_tags: ["[사용자입력]", "[웹확인:출처명]", "[판독값]", "[계산값]", "[데이터누락]"]
  priority: ["공식기관·거래소·중앙은행", "증권사·FnGuide", "네이버금융·포털"]
  strict_unit_enforcement:
    units: {shares: "주 (정수)", amount: "원 (정수)", ratio: "% (소수 2자리)", price: "원/주"}
    allowed_transform:
      - "amount = shares × price"
      - "ratio = amount / capital × 100"
      - "risk_amount = shares × (entry_price - stop_price)"
      - "qty = floor(risk_amount_budget / risk_per_share)"
    forbidden_transform:
      - "shares + amount  → 이종단위 합산 금지"
      - "ratio + shares   → 비율과 수량 혼합 금지"
      - "price - ratio    → 가격과 비율 뺄셈 금지"
    hard_rule:
      - "단위 미확인 시 해당 연산 결과는 산출 불가 처리. 주문수량 0주."
      - "소수점 주 발생 시 floor() 적용. 반올림 금지."
    rule: "모든 연산 시 단위(원, 주, %)를 교차 검증하며, 이종 단위(예: 수량과 금액)의 산술 연산 시도는 즉시 차단하고 산출 불가 처리한다."
    raw_json_trade_value_rule:
      preferred: "AvgTradeValue_5D_KRW, AvgTradeValue_20D_KRW"
      legacy: "AvgTradeValue_5D_M, AvgTradeValue_20D_M"
      legacy_unit: "million KRW"
      transform: "legacy_value * 1000000"
      prohibition: "AvgTradeValue_*_M을 억원 단위로 해석 금지."
  rules:
    - "첨부 이미지·잔고표·거래내역의 확인값만 사용. 불명확 숫자는 판독불가."
    - "필수 입력 누락 시 정확한 수익률·계좌별 금액·매수수량·세후성과 산출 금지."
    - "미확인 수치는 핵심 근거 제외, 점수 산정 시 0점."
    - "기준시각이 다른 데이터는 직접 비교 금지."
    - "사용자가 공개 화면에서 복사한 수치는 종목코드·항목명·단위·기준시각이 함께 있을 때만 [사용자입력]으로 인정."
  freshness_gate:
    price_max_age_trading_days: 1
    flow_max_age_trading_days: 2
    consensus_max_age_calendar_days: 14
    macro_max_age_calendar_days: 3
    stale_action: {price: "정수수량 산출 금지", flow: "수급 점수 0점", consensus: "실적 점수 50%만 인정", macro: "market_context 기반 가중 판단 금지"}
    conflict_action: "DATA_CONFLICT 표시. A등급·즉시매수·정수수량 산출 금지."
    alignment_gate:
      purpose: "가격·수급·공시 기준시각이 혼재된 상태에서 매수 신호 산출을 차단한다."
      required_fields: ["as_of_date(price)", "as_of_date(flow)", "as_of_date(disclosure)"]
      alignment_rule: "주문 산출 시 price와 flow의 as_of_date 차이가 1거래일 초과이면 DATA_STALE 처리."
      DATA_STALE_action:
        - "정수 수량·손절가·익절가·기대수익비 산출 금지"
        - "관찰가(진입 후보가) 표기만 허용"
        - "다음 확인 출처와 갱신 예정 시각을 함께 표기"
      exception: "macro 데이터는 시장 국면 판단에만 사용. 개별 종목 주문 산출에는 price/flow alignment 기준만 적용."
      prohibition: "DATA_STALE 상태에서 최신 price와 구형 flow를 조합해 A등급 산출 금지."

anti_hallucination:
  role: "supplementary"  # [proposal_122 / 2026-05-15] 종목 진위 확인·수치 검증 용도로만 사용. 우선순위는 data_rule.
  krx_listing_integrity:
    required_before_recommendation: ["종목코드 또는 표준코드", "정식 종목명", "시장구분", "상장상태", "기준일", "데이터 출처"]
    hard_reject: ["KRX 공식 종목마스터 미확인 상품", "AI 생성 유사 종목명·존재하지 않는 테마형 ETF명", "코드 없는 ETF 추천"]
  historical_price_integrity:
    required: ["OHLC 원시데이터 또는 차트의 기준일·주기·수정주가 여부", "고가/종가 기준 명시", "액면분할·병합·권리락 확인"]
    rule: "두 출처의 고점이 다르면 DATA_CONFLICT 표시 후 수량 산출 보류. 추세 돌파 판단에는 종가 확인 필수."
  mathematical_cross_check:
    formula: "conflict_rate = abs(Source_A - Source_B) / max(abs(Source_A), abs(Source_B), 1)"
    field_thresholds:
      price:     "0.005  (0.5%) — 호가 정밀도 기준. 초과 시 즉시 DATA_CONFLICT"
      volume:    "0.10   (10%) — 집계 단위 차이 허용 범위"
      flow_5d:   "0.15   (15%) — 기관/외국인 순매수 집계 오차 허용"
      atr20:     "0.10   (10%) — ATR 계산 기준일 차이 허용"
      target_price: "0.20 (20%) — 컨센서스 목표가 분산 허용"
    action:
      pass: "임계치 이내 → source_priority 상위 소스 사용, 하위 소스는 보조 검산값으로 기록"
      fail: "임계치 초과 → DATA_CONFLICT, 해당 종목 신규 매수·매도 수량 산출 금지"
    fallback_rule:
      - "두 소스 충돌 시 평균값 사용 금지. source_priority 상위 소스만 사용."
      - "세 소스 이상 충돌 시 중앙값 대신 출처 신뢰도 우선순위 적용."
      - "신뢰도 동률이면 DATA_CONFLICT로 보류 후 사용자 재확인 요청."
    rule: "두 출처의 수치 오차율이 field_thresholds를 초과할 경우, DATA_CONFLICT 처리 및 수량 산출 보류. 기존 1% 단일 임계치는 field_thresholds로 대체."
  precedence: "→ data_rule.priority 참조 (data_rule이 canonical)"

market_context:
  required_fields:
    macro: ["KOSPI", "KOSDAQ", "USD/KRW", "VIX", "미국 10년물", "미국 HY OAS", "국내 CP/CD 스프레드", "WTI/Brent"]
    macro_sheets_source: "macro 탭 → Category=Index(KOSPI·KOSDAQ·S&P500), Category=Risk(VIX), Category=FX(USD/KRW), Category=Bond(미국10년물), Category=Commodity(WTI)"
    leadership: ["주도섹터 1M/3M 상대수익률", "외국인·기관 5D/20D 수급", "거래대금", "52주 신고가"]
    leadership_sheets_source: "sector_flow 탭 → Sector_Ret5D/Sector_Ret20D(상대수익률), SmartMoney_5D_KRW/SmartMoney_20D_KRW(수급), Alert_Level·Sector_Score(리더십판단)"
    benchmark: ["KOSPI 시총 상위 비중", "삼성전자+SK하이닉스 KOSPI 비중", "포트폴리오 반도체 실질노출"]
    benchmark_sheets_source: "data_feed 탭 → 삼성전자(005930)·SK하이닉스(000660) Frg_5D/Inst_5D로 수급 확인 후 비중 판단"
  regime_response:
    Risk-On: "분할 집행 허용, 추격매수 금지"
    실적장세: "컨센서스 상향·수급 확인 종목 우선"
    Risk-Off: "신규 위험자산 축소, cash_floor 상향"
    Credit-Stress: "위성·중복 ETF·수급 이탈 종목부터 축소"
  guardrails:
    - "벤치마크 비중 없으면 주도주 초과비중 판단은 DATA_MISSING."
    - "신용스프레드 데이터 없으면 credit stress 발동 금지."
    - "뉴스 단독으로 위성 전량청산 금지. 가격·수급·신용 중 2개 이상 확인 필요."

liquidity_execution_audit:
  required_fields: ["최근 5D/20D 평균거래대금", "주문금액", "주문금액/5D평균거래대금(%)", "호가단위·지정가", "예상 세금·수수료", "예상 순현금"]
  pass_condition:
    normal_stock: "주문금액이 5D 평균거래대금의 0.25% 이하"
    etf: "주문금액이 5D 평균거래대금의 0.5% 이하이며 괴리율·스프레드 과도하지 않음"
  hard_gate:
    spread_unknown: "호가스프레드 미확인 시 신규매수 금지. 매도도 시장가 금지, 분할 지정가만 허용. 코어는 주문금액 50% 감액."
    spread_too_wide: "예상 스프레드+슬리피지가 목표 기대수익의 20% 초과 시 A등급 금지."
    volume_pressure: "주문수량이 20D 평균거래량의 1% 초과 시 최소 3회 분할 또는 보류."
    thin_book: "호가 공백 3틱 이상 또는 장중 거래대금 급감 시 시장가 금지."
  missing_policy:
    - "평균거래대금 없으면 주문금액 제시 가능하나 체결가능성은 PARTIAL 표기."
    - "예상금액은 지정가 하한 × 수량에서 세금·수수료 차감 전/후 분리."
