meta:
  title: "은퇴자산포트폴리오 — 최상위 실행 계약"
  parent_file: "RetirementAssetPortfolio.yaml"
  version: "2026-05-18-F8_p4_keyword_lock"
  language: "ko-KR"
  timezone: "Asia/Seoul"
  role: "canonical"
  has_code_implementation: true
  code_path: ["src/quant_engine/execution_slippage_store_v1.py", "tools/run_snapshot_admin_server_v1.py"]
  purpose: >
    기존 llm_compact_execution_contract에서 제공하던 최상위 안전 계약을
    모듈형 구조에 맞게 복원한 단일 권위 파일.
    이 파일은 모든 spec보다 먼저 적용한다.

source_of_truth:
  priority: "highest"
  rule: "이 파일의 master_prohibitions, hard_stops, capture_read_ledger가 모든 하위 전략·점수·수량·출력 규칙보다 우선한다."
  conflict_resolution:
    risk_vs_strategy: "위험 차단 규칙과 매수/증액 규칙이 충돌하면 위험 차단 규칙만 적용한다."
    data_vs_output: "데이터·수량·현금 검산이 실패하면 출력 형식이 완성되어도 주문은 금지한다."
    table_vs_text: "표/수치/공식과 서술형 설명이 충돌하면 표/수치/공식을 우선한다."
    tie_rule: "동급 규칙 충돌 시 보류하고 다음 확인 출처를 출력한다."

proposal_policy:
  canonical: true
  purpose: "사용자 판단용 제안 레이어와 HTS 즉시 실행 레이어를 분리한다."
  rule:
    - "가격·수량 산출 입력이 충족되면 시장 개장 여부와 무관하게 제안표를 유지한다."
    - "실행 차단은 validation_status와 별도 실행가능여부 컬럼으로만 표현한다."
    - "사용자 판단용 제안표와 HTS 즉시 입력 주문표를 같은 표로 섞지 않는다."
  allow_proposal_when:
    - "holding_quantity 또는 final_qty 입력이 존재"
    - "prices_json 또는 동등한 하네스 가격 입력이 존재"
    - "sell_quantities_json 또는 buy_qty_inputs_json이 존재"
  block_execution_when:
    - "intraday_lock = true"
    - "snapshot_execution_gate != ALLOW_EXECUTION"
    - "validation_status != PASS"
  execution_status:
    proposal_only: "제안 가능 / 즉시 HTS 입력 금지"
    execution_wait: "제안 가능 / 추가 확인 후 실행"
    execution_ready: "제안 가능 / 즉시 실행 가능"
  proposal_price_selection:
    canonical: true
    rule:
      sell_or_trim:
        trigger: "final_action in [SELL_READY, SELL, TRIM, EXIT_100, EXIT_FULL] 또는 sell_quantities_json 존재"
        proposed_limit_price_krw: "prices_json.stop_price 우선"
        proposed_take_profit_price_krw: "prices_json.tp1_price가 유효하면 병기, 없으면 tp2_price"
        note: "방어적 제안가를 우선 표시한다."
      take_profit:
        trigger: "tp_trigger_gate=TRIGGERED 또는 tp1_state/tp2_state=PENDING and final_action includes TAKE_PROFIT"
        proposed_limit_price_krw: "prices_json.tp1_price 우선, 없으면 tp2_price"
        proposed_stop_price_krw: "prices_json.stop_price 병기"
      buy:
        trigger: "final_action includes BUY 또는 buy_qty_inputs_json.final_qty 존재"
        proposed_limit_price_krw: "order_blueprint_json.limit_price_krw 우선, 없으면 buy_qty_inputs_json.entry_price_hint"
        proposed_stop_price_krw: "prices_json.stop_price"
        proposed_take_profit_price_krw: "prices_json.tp1_price 우선"
      watch_or_hold:
        trigger: "final_action in [WATCH, HOLD]"
        proposed_limit_price_krw: "주문가가 아니라 참고 방어가로 prices_json.stop_price 사용"
        note: "WATCH/HOLD는 HTS 주문가가 아니라 판단 참고용 가격임을 명시"
  proposal_quantity_selection:
    canonical: true
    rule:
      sell_or_trim:
        trigger: "final_action in [SELL_READY, SELL, TRIM, EXIT_100, EXIT_FULL] 또는 sell_quantities_json 존재"
        proposed_quantity: "sell_quantities_json.sell_qty 우선"
        note: "보유수량 기반 매도 제안 수량"
      buy:
        trigger: "final_action includes BUY 또는 buy_qty_inputs_json.final_qty 존재"
        proposed_quantity: "buy_qty_inputs_json.final_qty 우선"
        note: "하네스 산출 신규 매수 수량"
      watch_or_hold:
        trigger: "final_action in [WATCH, HOLD]"
        proposed_quantity: "sell_quantities_json.sell_qty 존재 시 참고 수량으로 표시, 없으면 null"
        note: "즉시 주문 수량이 아니라 판단 참고 수량"
  proposal_stop_ladder_selection:
    canonical: true
    rule:
      stop1:
        price: "prices_json.stop_price"
        quantity: "core=보유/제안수량의 50%, satellite=보유/제안수량의 70%"
        rationale: "spec/exit/stop_loss.yaml core/satellite quantity_rule의 1차 손절 물량"
      stop2:
        price: "stop1과 동일한 prices_json.stop_price"
        quantity: "잔여 수량 전부"
        rationale: "종가 회복 실패 시 잔여 청산 규칙을 사용자 판단용 제안표에 명시"
      stop3:
        price: "profit_preservation_json.auto_trailing_stop 우선, 없으면 protected_stop_price"
        quantity: "tp_quantity_ladder_json.tp3_qty 우선, 없으면 잔여 러너 수량"
        trigger: "profit_lock_stage != NORMAL 또는 trailing stop 유효"
        rationale: "수익보전 구간의 러너 보호 스탑을 별도 표기"
    prohibition:
      - "stop2/stop3 가격을 차트 지지선·심리적 가격으로 임의 산출 금지"
      - "profit_preservation/trailing 근거가 없으면 stop3를 비워 둔다"
  prohibition:
    - "실행 차단을 이유로 제안 수량·단가 자체를 숨기지 않는다."
    - "제안표를 HTS 즉시 입력표처럼 오인되게 렌더링하지 않는다."

canonical_terms:
  immediate_cash: "당일 출금 가능 현금"
  settlement_cash: "D+2 추정현금성자산"
  buy_power_cash: "즉시현금 + D+2 추정현금성자산 - 예약된 주문금액"
  cash_floor: "즉시현금 기준 방어선"
  total_heat: "Σ(entry_price - stop_price) × quantity / 총자산 × 100"
  source_failure: ["TRANSPORT_BLOCKED", "FETCH_FAILED", "PARSE_FAILED", "SCHEMA_MISMATCH", "ROW_NOT_FOUND", "FIELD_MISSING"]
  data_gap: ["DATA_MISSING", "PARTIAL", "NOT_APPLICABLE"]

master_prohibitions:
  canonical: true
  P1_no_quantity_without_holdings:
    rule: "보유수량 미확인 상태에서 매도수량 숫자 기재 금지."
    fail_action: "NO_SELL_QUANTITY"
  P2_no_atr_extrapolation:
    rule: "ATR20 미확인 상태에서 정수 매수수량 산출 금지."
    fail_action: "NO_BUY_QUANTITY"
  P3_no_risk_block_override:
    rule: "목표수익률, 공격슬롯, 주도주 논리를 이유로 cash_floor, Total_Heat, hard stop, circuit breaker를 완화하거나 우회 금지."
    fail_action: "BUY_BLOCKED"
  P4_no_intraday_speculation:
    rule: >
      [Intraday_Analysis_Restriction] 모든 정규 분석과 주문 산출은 16:30 장마감 종가를 기준으로 한다.
      제공된 캡처(account_snapshot)나 데이터의 시각이 15:30 이전(장중)일 경우,
      종가 이탈이 확정되지 않았으므로 '신규 매수'와 '조건부 전량 매도(손절 포함)' 지시를 절대 금지한다.
      장중에는 오직 'TRIM(부분 감축)'이나 '현금 확보' 등 보수적 방어 액션만 허용된다.
    fail_action: "INTRADAY_PROHIBITED"
    keyword_lock:
      trigger_condition: "account_snapshot.timestamp < 15:30 KST OR data_feed.timestamp < 15:30 KST"
      blocked_keywords: ["EXIT_100", "SELL_FULL", "전량매도", "전량 매도", "EXIT_FULL"]
      blocked_actions: ["hard_stop 사유 EXIT_100", "time_exit 사유 EXIT_100", "신규 BUY 주문"]
      allowed_actions_only: ["TRIM_25", "TRIM_33", "TRIM_50", "현금 확보", "CASH_RAISE"]
      auto_action: >
        timestamp < 15:30 KST 확인 즉시 해당 리포트의 모든 EXIT_100·SELL_FULL 주문행을
        validation_status=INTRADAY_PROHIBITED로 강등하고 주문표에서 제거한다.
        hard_stop·time_exit 사유가 있어도 예외 없이 적용.
        사유 컬럼에 "P4:장중캡처 → EXIT_100 차단, TRIM만 허용" 표기 필수.
      cash_floor_trim_auto:  # [2026-05-19_HARNESS_AUDIT_V1] H4
        rule_id: "P4_CASH_TRIM"
        trigger: "intraday_lock=true AND cash_floor_status IN [TRIM_REQUIRED, HARD_BLOCK]"
        purpose: >
          장중 현금 부족 상황에서 종가 대기 없이 즉시 TRIM으로 현금 확보.
          LLM 임의 배정 금지 — 아래 공식으로만 수량 산출.
        formula:
          cash_shortfall_krw: "(target_cash_pct/100 - current_cash_pct/100) × total_asset  [CASH_RATIOS_V1 기반]"
          trim_qty_per_stock: "min(floor(remaining_shortfall_krw / current_price), holding_quantity)"
          assignment_order: "sell_priority_engine 1순위부터 계단식. 잔여 부족 시 2순위로 이동."
        output_format: >
          TRIM {종목명} {qty}주 / 지정가 {현재가} [TICK_OK: {price}원]
          사유: "P4+현금부족 → TRIM 자동배정. 신규BUY/EXIT_100 차단 유지."
          [CASH_RAISE_AUTO: {qty}주 → 예상확보 {proceeds}원 / 부족액 {shortfall}원 중 {recovered}원 해소]
        prohibition:
          - "서사 설명으로 trim_qty 임의 결정 금지"
          - "trim_qty 없이 '현금 확보 권장'만 출력 금지"
          - "1순위 소진 전 2순위 배정 금지"
      violation_example:
        wrong: "15:09 KST 캡처 기반으로 현대로템 280주 EXIT_100 생성 → P4 위반"
        correct: "15:09 KST 캡처 → EXIT_100 BLOCKED. 필요 시 TRIM_50(140주) 생성 후 15:30 이후 재분석 대기"
  P5_no_fractional_share:
    rule: "소수점 매수 금지. 모든 주문 수량은 정수."
    fail_action: "ROUND_DOWN_OR_NO_QUANTITY"
  P6_no_a_grade_without_data:
    rule: "핵심 데이터 완성도 매트릭스 없이 A등급·즉시매수·정수수량 산출 금지."
    fail_action: "MAX_GRADE_C_OR_INSUFFICIENT_DATA"
  P7_price_formula_id_required:
    rule: >
      모든 가격(손절가·익절가·trailing_stop·보호스탑 등) 산출 시
      spec/13_formula_registry.yaml에 등록된 공식 ID(산출공식_ID)를 반드시 명시.
      미등록 레이블(예: "profit_lock_ratchet", "차트 지지선", "심리적 지지선",
      "임의 하향")로 가격을 생성하는 행위 절대 금지.
      등록된 가격 공식 ID: STOP_PRICE_CORE_V1, TRAILING_STOP_PRICE_V1,
      TAKE_PROFIT_LADDER_V1, TAKE_PROFIT_LADDER_V2, PROFIT_LOCK_RATCHET_V1.
    fail_action: "PRICE_FORMULA_REQUIRED — 산출공식_ID 미기재 가격은 HTS 입력 불가"
hard_stops:
  canonical: true
  rules:
    - id: "HS001_CAPTURE_LEDGER_REQUIRED"
      rule: "capture_read_ledger 표 없이 계좌·종목별 최종 주문수량 확정 금지."
    - id: "HS002_AUTO_INVEST_SCREEN_NOT_HOLDINGS"
      rule: "자동투자/약정 화면을 보유수량·평단·현금으로 사용 금지."
    - id: "HS003_DATA_MATRIX_REQUIRED"
      rule: "데이터 완성도 매트릭스 없이 매수·매도 결론 확정 금지."
    - id: "HS004_BUY_SET_REQUIRED"
      rule: "매수제안은 지정가·수량·손절가·손절수량·익절가·익절수량 세트가 모두 있어야 유효."
    - id: "HS005_FLOW_ROWS_20D_REQUIRED_FOR_A"
      rule: "Flow_Rows<20이면 20D 수급 기반 A등급·즉시매수 판단 금지."
    - id: "HS006_TOTAL_HEAT_REQUIRED"
      rule: "Total_Heat 미산출 상태에서 신규 매수 수량 산출 금지."
    - id: "HS007_HTS_SINGLE_PRICE_ONLY"
      rule: >
        HTS 조건부 주문은 단일 이탈 가격 1개만 허용.
        "XX원 이탈 또는 YY원 회복 실패 시", "두 조건 중 하나 도달 시",
        "A 조건과 B 조건 동시 충족 시" 등 다중·복합 조건 주문을 플레이북에 산출 금지.
        복수 조건이 필요한 시나리오는 "[수동확인 필요: 다중조건 HTS 미지원]" 표기로만 제시.
      keyword_lock: "주문 조건 텍스트에 '또는', '동시 충족', '실패 시', '회복 실패', '돌파 실패' 포함 시 해당 행 INVALID_MULTI_CONDITION 처리"
    - id: "HS008_TICK_NORMALIZED_REQUIRED"
      rule: >
        플레이북의 모든 지정가(매수·손절·익절·trailing_stop)는 TICK_NORMALIZER_V1을 거친
        KRX 호가 단위 정규화 값이어야 한다.
        소수점 포함 가격 또는 호가 단위 불일치 가격(예: 144,568원, 25,886원)은
        HTS 입력 불가 오류로 판단하고 해당 행을 INVALID_TICK으로 표시한 뒤 정규화 후 재산출한다.
      tick_table_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.TICK_NORMALIZER_V1.tick_table"
    - id: "HS009_TP_VALIDITY_CHECK_REQUIRED"
      rule: >
        prices_json의 tp1_price·tp2_price가 null이거나 tp1_state·tp2_state가
        TP1_ALREADY_TRIGGERED·TP2_ALREADY_TRIGGERED이면 해당 가격은 INVALID_TP_STALE로 처리한다.
        prices_lock=true이더라도 TP_VALIDITY_CHECK_V1이 우선하며, LLM이 대체 TP 가격을
        임의 산출하는 것은 이 규칙으로 절대 금지한다.
      fail_action: "INVALID_TP_STALE — HTS 주문표 기재 금지. GAS 재실행 필요."
      formula_ref: "spec/13_formula_registry.yaml:TP_VALIDITY_CHECK_V1"
    - id: "HS010_WATCH_BLOCKED_NULL_REQUIRED"
      rule: >
        order_blueprint_json에서 validation_status != 'PASS'인 행(WATCH, BLOCKED,
        INSUFFICIENT_DATA 포함)의 stop_price_krw·stop_quantity·take_profit_price_krw·
        take_profit_quantity는 반드시 null이어야 한다.
        LLM이 참고용이라도 WATCH 행과 동일 주문표에 가격·수량 숫자를 기재하면
        HS010 위반으로 해당 표 전체가 BLOCKED_ORDER_TABLE로 처리된다.
      fail_action: "BLOCKED_ORDER_TABLE — WATCH 감시원장을 별도 섹션으로 분리 필수."
    - id: "HS011_NO_LLM_FORMULA_DEFINITION"
      rule: >
        LLM은 spec/13_formula_registry.yaml에 등록된 공식 ID만 인용할 수 있다.
        대화 중 새 알고리즘명(V3, V4, ENGINE, REBALANCE, ACTIONABLE_ 등)을 즉석 정의하고
        이에 기반한 구체적 원화 가격 또는 정수 수량을 산출하는 것을 절대 금지한다.
        하네스 미구현 영역은 "DATA_MISSING — 하네스 업데이트 필요"로만 표시하고 LLM 대체 계산 금지.
      fail_action: "INVALID_UNREGISTERED_FORMULA — HTS 입력 금지. 해당 숫자 전부 무효."
      tag_requirement:  # [2026-05-19_HARNESS_AUDIT_V1] H3
        rule_id: "H3_TICK_TAG"
        rule: >
          TICK_NORMALIZER_V1 통과 후 모든 지정가 옆에 태그 필수 부착.
          정규화 성공 → "[TICK_OK: {price}원]"
          정규화 필요 → "[TICK_INVALID: {raw}원 → {normalized}원 재산출]"
        missing_tag_action: >
          태그 없는 가격은 자동 INVALID_TICK_UNTAGGED 처리. HTS 주문표 기재 금지.
          검산_통과여부 = FAIL_NO_TICK_TAG.


  # ── [2026-05-18_CONFLICT_RESOLUTION_V1] 매도 신호/수량 분리 원칙 ──────────────
  # AGENTS.md Direction-A와 동일 규칙을 spec 레벨로 명시하여 grey zone 해소.
  # 충돌 상황: spec/14_raw_workbook_mapping.yaml에 Sell_Qty 필드가 존재하지만
  # GAS(Google Apps Script)는 이 필드를 의도적으로 blank로 남긴다 — 오류가 아니다.
  signal_quantity_separation:
    canonical: true
    rule_id: "SQS001"
    gas_role: >
      GAS(gas_data_feed.gs)는 매도 신호 생성 전용 엔진이다.
      GAS 출력 = Sell_Signal(종류) + Sell_Price(지정가) + Sell_Ratio_Pct(비율%).
      GAS는 절대 매도수량(Sell_Qty 정수)을 산출하지 않는다.
    agent_role: >
      에이전트(LLM)가 account_snapshot(HTS 이미지 캡처) 잔고를 읽어
      최종 정수 매도수량을 산출한다.
      공식: Sell_Qty = floor(Sell_Ratio_Pct × holding_quantity)
      holding_quantity는 반드시 capture_read_ledger 판독값 사용.
    workbook_sell_qty_field_intent:
      field: "Sell_Qty (spec/14_raw_workbook_mapping.yaml)"
      normal_value: "blank (GAS 미기재가 정상)"
      filled_when: "에이전트가 account_snapshot 판독 후 최종 수량 확인 시 기재 가능"
      prohibition:
        - "Sell_Qty=blank 또는 Sell_Qty=None을 GAS 오류로 처리 금지"
        - "Sell_Qty=blank를 이유로 사용자에게 수동 입력 요구 금지"
        - "GAS가 이미 Sell_Qty를 채웠다고 가정하고 account_snapshot 판독 생략 금지"
    conflict_resolution: >
      AGENTS.md와 이 규칙이 충돌하면 이 spec이 우선한다(source_of_truth.priority=highest).
      AGENTS.md는 보조 운영 지침이고 00_execution_contract는 canonical 실행 계약이다.


  # ── [2026-05-18_ROUTING_OPTIMIZATION_V1] stop_price 단일 조회 체인 ──────────
  # 문제: stop_price를 찾을 때 AI가 account_snapshot → deprecated positions 탭으로
  #       루프하는 판단 지연이 발생. 아래 3단계 체인으로 라우팅을 단일화한다.
  stop_price_routing_chain:
    canonical: true
    rule_id: "SPRC001"
    purpose: >
      보유 포지션의 stop_price 조회는 오직 아래 3단계 순서를 따른다.
      positions 탭 참조는 체인의 어느 단계에도 존재하지 않는다.
    chain:
      step_1_account_snapshot:
        source: "account_snapshot.stop_price (HTS 캡처 원장)"
        condition: "값이 존재하고 > 0 이며 < average_cost"
        result: "이 값을 stop_price로 확정. 추정·계산 없이 직접 사용."
      step_2_atr_calculation:
        source: "ATR 공식 추정"
        condition: "step_1이 없거나(blank/0) ATR20이 data_feed에 존재할 때"
        formula: "stop_price_est = max(average_cost * 0.92, average_cost - ATR20 * 1.5)"
        output_tag: "(ATR추정)"
        result: "추정값임을 명시하고 사용."
      step_3_fallback:
        source: "고정 % 폴백"
        condition: "step_1·step_2 모두 불가(ATR20도 DATA_MISSING)"
        formula: "stop_price_est = average_cost * 0.92"
        output_tag: "(DATA_MISSING 폴백)"
        result: "DATA_MISSING 표기 필수. HTS 입력 불가 표시."
    dead_end:
      positions_tab: >
        DEPRECATED. 이 체인의 어느 단계에서도 positions 탭을 조회하지 않는다.
        (spec/15_account_snapshot_contract.yaml:positions_tab 참조)
    prohibition:
      - "step_1 미확인 상태에서 step_2 추정값을 '확정' stop_price로 표시 금지"
      - "positions 탭에 stop_price를 입력하라고 사용자에게 요구 금지"
      - "account_snapshot에 stop_price가 없다는 이유로 분석 전체를 중단 금지"

capture_read_ledger:
  canonical: true
  purpose: "계좌·보유·현금 판독의 단일 원장. 이 표 없이 최종 주문수량 확정 금지."
  columns: ["파일/화면", "계좌", "화면종류", "읽은값", "확신도", "주문표반영", "판독_상태"]
  status_values:
    CAPTURE_READ_OK: "캡처/원장에서 읽었고 주문 산출에 반영 가능"
    CAPTURE_PROVIDED_BUT_NOT_HOLDINGS: "캡처는 있으나 자동투자/약정 화면이라 잔고 산출 불가"
    CAPTURE_READ_FAILED: "캡처가 있으나 판독 실패 또는 구조화 미완료"
    NOT_PROVIDED: "해당 화면/원장 미제공"
  screen_type_rule:
    auto_investment_screen: "자동투자 설정·약정 이체일·월 적립금액·ISA 한도 화면. 보유수량·평단·현금 판독 금지."
    holdings_screen: "보유종목 목록·수량·평단·현금·평가손익 화면. 주문 산출에 직접 사용 가능."
  hard_stop:
    - "CAPTURE_READ_FAILED 항목이 있으면 해당 계좌 전체 주문수량 산출 보류"
    - "NOT_PROVIDED 계좌는 신규 매수·매도 주문수량 산출 금지"
    - "CAPTURE_PROVIDED_BUT_NOT_HOLDINGS 수치를 잔고·보유수량·현금으로 사용 금지"


  # ── [2026-05-18_CONFLICT_RESOLUTION_V1] T+1 시초가 갭 주문표 재유효성 검사 ──
  # 배경: 모든 분석은 전일 16:30 마감 데이터 기준이나, 주문은 익일 09:00 이후 집행된다.
  # 미 증시 급락·지정학 이벤트 등으로 갭 개장 시 전일 계산된 지정가·손절가는 이미 무의미.
  t1_open_gap_revalidation:
    canonical: true
    rule_id: "T1GAP001"
    purpose: >
      익일 시초가가 전일 종가 대비 ±3% 이상 갭이 발생하면
      전일 산출된 모든 가격·수량 기반 주문표를 자동 무효화(INVALID_STALE)하고
      당일 실시간 데이터 기준 재산출을 의무화한다.
    trigger:
      formula: "gap_pct = (open_price_D1 - close_price_D0) / close_price_D0 * 100"
      threshold_gap_up: "gap_pct >= +3.0%"
      threshold_gap_down: "gap_pct <= -3.0%"
    actions:
      on_gap_up:
        order_table_status: "INVALID_STALE_GAP_UP"
        required_actions:
          - "전일 산출된 손절가·익절가·매수지정가 전량 무효 처리"
          - "당일 시초가·ATR20 기준 가격 재산출 후 새 주문표 생성"
          - "stop_loss.tiered_ladder tier_1 이미 돌파 여부 확인 (gap_up > tier_1_target → 즉시 익절 판단)"
        output_tag: "[T+1 GAP_UP 재산출 필요] — 전일 주문표 무효"
      on_gap_down:
        order_table_status: "INVALID_STALE_GAP_DOWN"
        required_actions:
          - "전일 손절가 이미 하회 여부 확인"
          - "stop_loss.gap_down 규칙 즉시 적용: 09:00~09:15 15~30분 저가·거래대금·회복 여부 기록"
          - "전일 매수 지정가 전량 무효. 당일 갭하락 반응 확인 후 재진입 여부 판단"
        output_tag: "[T+1 GAP_DOWN 재산출 필요] — 전일 주문표 무효"
        holding_price_validity:  # [2026-05-18_ROUTING_OPTIMIZATION_V1] 보유주 가격 유효성
          purpose: >
            갭 발생 시 신규 주문 무효화뿐 아니라 보유주의 전일 산출 stop_price·
            trailing_stop 기준가도 무효화된다. 갭하락 후 전일 가격으로 손절을
            집행하는 오류를 방지한다.
          rule: >
            t1_open_gap_revalidation 발동 시 모든 보유 포지션의
            stop_price·trailing_stop_price·take_profit tier 가격은 STALE_PRICE 상태로
            전환. 재산출 전 HTS 조건부 주문 집행 금지.
          existing_holding_gap_down_procedure:
            xref: "spec/exit/stop_loss.yaml:stop_loss.gap_down"
            summary: >
              갭하락 보유주 → 09:00~09:15 15~30분 관찰 후 판단.
              전일 stop_price 도달 여부와 무관하게 즉시 전량 시장가 매도 금지.
              high_beta_exception 조건(갭 -5%+거래대금 300%) 충족 시만 50% 선 축소 허용.

      on_normal_open:
        condition: "abs(gap_pct) < 3.0%"
        order_table_status: "VALID_WITH_REVIEW"
        note: "1~3% 갭은 유효. 단 ATR20 기준 손절가·익절가가 여전히 의미있는지 확인 후 집행."
    scope:
      applies_to: ["매수 지정가", "손절 지정가", "익절 지정가 (tier_1/tier_2)", "trailing_stop 기준가"]
      does_not_apply_to: ["섹터 진단·국면 판단 (가격 무관한 수급/거시 분석)"]
    gap_measurement_source: "data_feed.Open (당일 시초가) vs data_feed.PrevClose (전일 종가)"
    missing_policy:
      open_price_missing: "GAP_CHECK_SKIPPED — 갭 여부 확인 불가. 전일 주문표를 REVIEW_REQUIRED로 표시."
    prohibition:
      - "open_price 미확인 상태에서 전일 주문표를 유효한 것으로 간주하고 즉시 집행 금지"
      - "갭 발생 시 09:00~09:15 사이 전량 시장가 매도·매수 금지 (stop_loss.gap_down 준수)"
      - "갭 재산출 없이 '어제 계획대로 집행' 지시 금지"
    output_requirement: >
      분석 보고서 상단 블록0(국면 요약) 또는 블록1(주문 검산) 직전에
      [T+1 갭 점검] 행을 출력:
      형식: "시초가 {open}원 / 전일종가 {prev_close}원 / 갭 {gap_pct:+.1f}% → 주문표 상태: {status}"

order_validation_contract:
  output_rendering_gate:
    rule: "사람용 보고서도 주문 산출 전 반드시 capture_read_ledger와 단계 검산 결과를 먼저 표로 출력한다."
    sequence:
      step_pre: >  # [2026-05-19_HARNESS_AUDIT_V1] H1
        formula_audit_trail (QEH_AUDIT_BLOCK) — 모든 주문 출력 전 필수 선행.
        TOTAL_HEAT_V1 / CASH_RATIOS_V1 / SELL_PRIORITY_V1 검산 표 없으면 주문표 전체 BLOCKED (FAT001).
      step_0: "sell_priority_precheck (SELL/TRIM 후보 2개 이상 또는 현금 부족 시 선행 필수)"
      step_1: "capture_read_ledger"
      step_2: "data_completeness_matrix"
      step_3: "order_quantity_4stage_gate"
      step_4: "HTS 입력 가능 주문표 또는 산출금지 사유"
    prohibition:
      - "capture_read_ledger 없이 계좌 총자산·현금·보유수량·평단·매도수량 숫자를 본문 문장에 단독 기재 금지"
      - "4단계 검산 결과 없이 즉시 실행 주문표 출력 금지"
      - "검산 표 밖의 산문 요약으로 주문수량·현금·보유수량 근거를 대체 금지"
      - "sell_priority_decision_table 없이 복수 매도 후보 중 특정 종목을 1차 대상으로 확정 금지"
  stages:
    stage_0_sell_priority_precheck:
      # spec: spec/risk/portfolio_exposure.yaml:sell_priority_engine
      activation:
        - "SELL/TRIM/EXIT 후보가 2개 이상 동시 존재"
        - "현금 < 목표 (cash_floor.trim_required_when 충족)"
        - "REGIME_TRIM_50 발동"
      required_actions:
        - "GAS ?view=sell_priority 결과를 sell_priority_decision_table로 출력한다"
        - "ETF 중복노출 합산 확인: 반도체(SK하이닉스+KODEX반도체+KODEX AI반도체) 총노출 계산"
        - "Sell_Priority_Score 순위표에서 tier 1→2→3→4 순서 확인"
      table_output_required: >
        sell_priority_decision_table은 보고서에 독립된 섹션·표 형태로 먼저 출력해야 한다.
        텍스트 요약("1순위: 삼성E&A, 2순위: ...")으로 대체하는 것은 이 단계를 충족하지 않는다.
      fail: >
        이 단계 없이 개별 종목 SELL/TRIM 수량을 확정하는 행위는
        validation_status=BLOCKED_SELL_PRIORITY_REQUIRED로 처리하고
        주문표의 모든 SELL/TRIM 행을 보류한다.
      text_summary_substitution_ban: "sell_priority_decision_table을 텍스트 서술로 대체 금지. 표가 없으면 SELL 주문표 전체 BLOCKED."
      core_leader_protection: >
        SK하이닉스·삼성전자(직접 코어 주도주)는 sell_priority_engine tier=9(마지막 순위).
        이 종목이 상위 순위에 나타나면 반드시 tier 1~4 후보가 모두 소진된 이후임을 확인한다.
        상승추세 중 core leader를 먼저 매도하려면 hard_stop 또는 명확한 thesis 훼손 근거 필수.
    stage_1_capture_ledger:
      check: "capture_read_ledger 모든 계좌 분류 완료"
      fail: "CAPTURE_READ_FAILED 또는 NOT_PROVIDED 계좌는 주문수량 보류"
    stage_2_cash_check:
      check: "매수 주문금액 합산 <= 주문가능현금 확인값"
      fail: "현금 확인값 없으면 매수금액 산출 금지"
    stage_3_holding_check:
      check: "매도수량 <= 확인된 보유수량"
      fail: >
        보유수량 미확인이면 매도수량 미산출.
        HTS 캡처(보유종목 화면) 미제공 시 수량 칸을 '미산출' 또는 '캡처확인후기재'로만 표시.
        보유수량을 기억·추정·이전 대화에서 재사용하는 행위 금지.
    stage_4_open_order_check:
      check: "동일 계좌·종목 미체결 주문 여부 확인"
      fail: "중복주문 방지 검산 불가 표시 후 수동 확인"
  final_order_table_columns:
    - "계좌"
    - "종목명"
    - "현재보유수량"
    - "평단"
    - "현재가"
    - "주문구분"
    - "지정가"
    - "수량"
    - "손절가"
    - "손절수량"
    - "익절가"
    - "익절수량"
    - "주문금액"
    - "tick_status"        # [TICK_OK: {price}원] 또는 [TICK_INVALID → 재산출] — H3/HS008 의무
    - "검산_통과여부"


# ── [2026-05-19_HARNESS_AUDIT_V1] H1 — 공식 검산 표 강제화 ─────────────────
formula_audit_trail:
  canonical: true
  rule_id: "FAT001"
  version: "2026-05-19_HARNESS_AUDIT_V1"
  purpose: >
    모든 분석 보고서 최상단에 주문표보다 먼저 출력하는 공식 검산 원장.
    LLM의 '대략' 계산 습성을 차단하고 공식 ID 기반 결정론적 산출을 보장한다.
    QEH_AUDIT_BLOCK이 없으면 주문표 전체 BLOCKED.
  required_output_block:
    id: "QEH_AUDIT_BLOCK"
    columns: ["공식_ID", "입력값_요약", "결과값", "발동게이트"]
    mandatory_formulas:
      - id: "TOTAL_HEAT_V1"
        gate_check: "< 7% → ALLOW / 7~10% → HALVE / >= 10% → BLOCK_NEW_BUY"
        missing_action: "BLOCK_NEW_BUY 자동 발동 + DATA_MISSING 표기"
      - id: "CASH_RATIOS_V1"
        gate_check: "current_cash_pct vs target_cash_pct → PASS / CASH_RAISE_REQUIRED(-Xp)"
        missing_action: "현금 판정 불가 → 매수 보류"
      - id: "SELL_PRIORITY_V1"
        condition: "SELL/TRIM 후보 존재 또는 cash_floor_status != PASS 시"
        gate_check: "1순위 종목 + Score + tier 표시"
    optional_formulas_when_applicable:
      - "POSITION_SIZE_V1 — 매수 수량 산출 시"
      - "STOP_PRICE_CORE_V1 — 손절가 산출 시"
      - "TAKE_PROFIT_LADDER_V2 — 익절 사다리 산출 시"
      - "MARKET_RISK_SCORE_V1 — 국면 진단 시"
      - "RISK_BUDGET_CASCADE_V1 — 리스크 예산 계산 시"
      - "TICK_NORMALIZER_V1 — 모든 지정가 출력 전"
  enforcement:
    - "QEH_AUDIT_BLOCK 없이 주문표 출력 → INVALID_MISSING_AUDIT. 전체 BLOCKED."
    - "TOTAL_HEAT_V1 결과 없이 신규 BUY 주문 → BLOCKED (HS006 연동)"
    - "CASH_RATIOS_V1 결과 없이 현금 판정 → BLOCKED (P3 연동)"
    - "공식 ID 명시 없는 가격·수량 → PRICE_FORMULA_REQUIRED (P7 연동)"
    - "표에 기재된 공식 ID와 실제 사용 공식 불일치 → CRITICAL_FORMULA_MISMATCH"
  llm_role: "공식 결과를 표에 복사·기재. 재계산·재해석·임의 조정 금지."

# ── [2026-05-20_HARNESS_V5] LLM 계산 화이트리스트 ──────────────────────────
llm_computation_whitelist:
  canonical: true
  rule_id: "LCW001"
  version: "2026-05-20_HARNESS_V5"
  purpose: >
    LLM이 수행할 수 있는 계산과 절대 수행할 수 없는 계산을 명시한다.
    하네스(GAS 확정 값)가 존재하는 영역에서 LLM의 재계산·재해석을 원천 차단한다.
    이 규칙은 P7, HS011, FAT001보다 상위에서 계산 행위 자체를 규율한다.

  allowed:
    text_and_narrative:
      - "공식 결과값에 대한 텍스트 요약 및 해석 (숫자 재산출 없이)"
      - "하네스 출력 JSON 값을 인용·복사해 보고서에 표시"
      - "spec/13_formula_registry.yaml에 등록된 공식 ID 인용 및 적용 조건 서술"
      - "게이트 상태 레이블(PASS/BLOCKED/WATCH 등) 인용 및 조치 권고"
      - "여러 게이트 결과를 조합해 종합 판단 서술 (개별 숫자 재산출 없이)"
    simple_lookups:
      - "가격·수량 비교 (단순 대소 비교: close >= tp1_price 등)"
      - "비율 단순 적용: floor(Sell_Ratio_Pct × holding_quantity) — holding_quantity는 반드시 캡처 판독값"
      - "정수 반올림 처리: ROUND_DOWN for share quantities"

  forbidden:
    price_calculations:
      rule: "모든 가격은 GAS 확정값 또는 등록된 공식 ID 기반. LLM 임의 계산 절대 금지."
      examples:
        - "stop_price, trailing_stop_price 직접 계산"
        - "take_profit tier 가격 직접 계산"
        - "ATR 기반 가격 추정 (GAS ATR20 미제공 시 DATA_MISSING으로만 표시)"
        - "rebound_trigger_price = prevClose + 0.5×ATR20 직접 계산"
      fail_action: "FORBIDDEN_LLM_PRICE_CALC — 해당 가격 무효. HTS 입력 불가."

    quantity_calculations:
      rule: "포지션 사이즈, 매수 수량, 분할 트랜치 수량은 GAS POSITION_SIZE_V1 결과만 사용."
      examples:
        - "ATR 기반 포지션 사이즈 직접 계산"
        - "리스크 예산 기반 수량 직접 계산"
        - "smart_cash_raise_qty 직접 계산"
        - "트랜치 수량 임의 산출"
      fail_action: "FORBIDDEN_LLM_QTY_CALC — 해당 수량 무효. GAS 재실행 필요."

    cash_and_ratio_calculations:
      rule: "현금 비율, Heat 수치, 포트폴리오 가중치는 GAS 확정값만 사용."
      examples:
        - "total_heat_pct 직접 계산"
        - "cash_shortfall_krw 직접 계산"
        - "position weight % 직접 계산"
        - "settlement_cash_d2_krw 추정"
      fail_action: "FORBIDDEN_LLM_CASH_CALC — 해당 수치 무효. GAS 재실행 후 재인용."

    harness_gate_overrides:
      rule: "GAS 확정 게이트 상태를 LLM이 재판단·완화·우회 절대 금지."
      examples:
        - "BLOCKED_LATE_CHASE → 서사로 BUY 허용 유도"
        - "WHIPSAW_SUSPECTED → 매도 진행 서술"
        - "ROUTE_D 이외 경로에서 전량매도 권고"
        - "CRITICAL health_label을 CAUTION으로 임의 완화"
      fail_action: "FORBIDDEN_GATE_OVERRIDE — 해당 분석 전체 BLOCKED. 재실행 필요."

    score_recalculation:
      rule: "모든 스코어(breakout_quality_score, anti_whipsaw_score, t1_forced_sell_risk_score 등)는 GAS 계산 결과만 인용."
      examples:
        - "breakout_quality_score 직접 계산"
        - "distribution_risk_score 재계산"
        - "late_chase_risk_score 조정"
        - "portfolio_health_score 재산출"
      fail_action: "FORBIDDEN_SCORE_RECALC — 스코어 무효. 공식 ID 인용 또는 DATA_MISSING 표기."

  enforcement:
    - "FORBIDDEN 항목 위반 발견 시 해당 숫자·결론 전체 BLOCKED으로 처리"
    - "LCW001 위반이 주문표에 반영되면 주문표 전체 INVALID_LCW_VIOLATION"
    - "위반된 계산 대신 'GAS 재실행 필요' 또는 'DATA_MISSING — 하네스 업데이트 필요' 표기"
  cross_refs:
    - "master_prohibitions.P7_price_formula_id_required"
    - "hard_stops.HS011_NO_LLM_FORMULA_DEFINITION"
    - "formula_audit_trail.FAT001"
    - "spec/13b_harness_formulas.yaml:ANTI_WHIPSAW_HOLD_GATE_V1"
    - "spec/13b_harness_formulas.yaml:SMART_CASH_RAISE_V2"
    - "spec/13_formula_registry.yaml:BREAKOUT_QUALITY_GATE_V2"


compatibility_aliases:
  old_paths:
    "llm_compact_execution_contract.non_negotiable_tables.capture_read_ledger.screen_type_rule": "spec/00_execution_contract.yaml:capture_read_ledger.screen_type_rule"
    "llm_compact_execution_contract.master_prohibitions": "spec/00_execution_contract.yaml:master_prohibitions"
    "llm_compact_execution_contract.hard_stops": "spec/00_execution_contract.yaml:hard_stops"
  rule: "기존 경로가 문서에 남아 있으면 위 alias로 해석하되, 신규 수정 시 새 경로로 교체한다."
