meta:
  title: "은퇴자산포트폴리오 — 포트폴리오 제약·계좌 라우팅 명세"
  parent_file: "RetirementAssetPortfolio.yaml"
  version: "2026-05-24-F4_portfolio_rules_proposal50"
  language: "ko-KR"
  timezone: "Asia/Seoul"
  role: "derived_adapter"
  has_code_implementation: true
  code_path: "tools/build_rebalance_engine_v2.py"
  purpose: >
    계좌, 납입한도, 비중, 중복노출, 현금 룰을 별도 포트폴리오 규칙으로 제공한다.
    canonical 계산은 기존 risk/account 섹션을 참조하되, LLM 적용 순서를 고정한다.
  authority_rule: "이 파일은 포트폴리오 제약 적용 순서를 제공하는 adapter다. 수치 임계값 충돌 시 canonical_refs 대상 파일을 우선한다."

portfolio_rules:
  priority: "risk_policy 다음, position_sizing 이전에 적용"
  canonical_refs:
    account_policy: "spec/01_objective_profile.yaml:account_policy"
    contribution_policy: "spec/01_objective_profile.yaml:user_profile.contribution_policy"
    exposure_framework: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework"
    cash_floor: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.cash_floor"
    duplicate_exposure: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.duplicate_exposure_rule"
    target_allocation: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.target_allocation_structure"

account_constraints:
  accounts_allowed: ["일반계좌", "ISA", "연금저축"]
  accounts_excluded: ["IRP"]
  contribution_limits:
    ISA:
      monthly_limit_krw: 2000000
      rule: "월 납입 가능액과 기납입액 확인 전 추가 납입 가정 금지"
    pension:
      monthly_limit_krw: 500000
      account_name: "연금저축"
      rule: "연금저축 납입 한도 확인 전 초과 납입 전제 금지"
  integer_quantity_only: true
  fractional_share_prohibited: true

asset_location_rules:
  domestic_tactical_stock:
    preferred_account: "일반계좌"
    rationale: "전술 위성·단기매매는 세제보다 유동성과 실행 자유도가 우선"
  high_dividend_or_yield:
    preferred_accounts: ["ISA", "연금저축"]
    rationale: "세제 혜택과 과세 이연 우선"
  overseas_long_term_etf:
    preferred_accounts: ["연금저축", "ISA"]
    rationale: "장기투자·세제효율 우선"
  cash_or_short_duration:
    preferred_accounts: ["ISA", "연금저축", "일반계좌"]
    rationale: "cash_floor와 대기자금 성격에 따라 배치"

exposure_limits:
  target_buckets:
    core:
      target_pct: 65
      band: "60~72"
      canonical_ref: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.target_allocation_structure.buckets.core_bucket"
    tactical_satellite:
      target_pct: 20
      band: "10~25"
      canonical_ref: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.target_allocation_structure.buckets.tactical_satellite_bucket"
    cash_fc:
      target_pct: 15
      band: "10~22"
      canonical_ref: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.target_allocation_structure.buckets.cash_fc_bucket"
  single_satellite_max_pct: 7
  duplicate_exposure_action_order:
    - "중복 섹터 ETF"
    - "20D 수급 이탈·20일선 하회 위성"
    - "동일 섹터 내 후순위"
    - "시장지배 코어 직접보유"

  # ── M5 V1.1 (proposal_50 2-4): 반도체 클러스터 강제 감축 (2026-05-24) ─────────
  sector_concentration:
    O2_semiconductor_cluster:
      cluster_limit_pct: 25
      single_stock_limit_pct: 30
      regime_limit_pct:
        EVENT_SHOCK: 20
        RISK_OFF: 20
        RISK_ON: 25
        SECULAR_LEADER_RISK_ON: 35
        CONCENTRATED_LEADER_ADVANCE: 60
      mandatory_reduction_trigger: "cluster_pct > cluster_limit * 2.0"
      reduction_plan: "MANDATORY_REDUCTION_PLAN_V1"
      weekly_reduction_target: "ceil((cluster_pct - cluster_limit) / 4)"
      reduction_priority_order:
        1: "rs_verdict=BROKEN 코어 클러스터 종목"
        2: "KODEX반도체 등 ETF (세금·슬리피지 유리)"
        3: "SK하이닉스 APEX_SUPER trailing_stop 발동 분"
        4: "삼성전자 CLA_EXIT_CONFIRMED 이후"
      enforcement:
        - "cluster_limit은 market_regime_state 및 CLA 상태에 따라 20/25/35/60으로 상향 조정 가능"
        - "cluster_pct <= cluster_limit * 2.0이면 MANDATORY_REDUCTION 비활성 (추가 BUY만 금지)"
        - "mandatory_reduction=true이면 weekly_target 미만 매도 시 REDUCTION_BEHIND_SCHEDULE 경고"
        - "is_mandatory=true 상태에서 반도체 종목 신규 BUY/ADD_ON 절대 금지"

cash_rules:
  cash_basis: "즉시 인출 가능한 현금성 자산 기준"
  buy_power_formula: "즉시현금 + D+2 추정현금성자산 - 예약된 주문금액"
  post_trade_check_required: true
  prohibition:
    - "D+2 추정현금을 cash_floor 충족 현금으로 오인 금지"
    - "post_trade_immediate_cash_ratio 미확인 상태에서 신규매수 확정 금지"

portfolio_decision_gate:
  sequence:
    1: "계좌 허용 여부 확인"
    2: "납입 한도 및 현금 원천 확인"
    3: "현재 보유/목표 비중/중복 노출 확인"
    4: "cash_floor 및 post-trade cash 확인"
    4_5: "[MRG001] 이격도 과열 체크 -- MEAN_REVERSION_GATE_V1: deviation_ratio >= 1.15 -> BUY_HARD_BLOCK"  # [2026-05-19_ALPHA_SHIELD_V1]
    5: "position_sizing으로 전달할 계좌별 최대 주문 예산 산출"
  pass_output:
    - "account"
    - "max_order_budget_krw"
    - "target_bucket"
    - "current_weight_pct"
    - "post_trade_cash_ratio"
    - "portfolio_constraint_status"
  fail_action:
    cash_floor_fail: "BUY_BLOCKED"
    duplicate_exposure_fail: "TRIM_OR_WAIT"
    account_limit_fail: "ROUTE_TO_OTHER_ACCOUNT_OR_WAIT"
    holdings_unknown: "NO_SELL_QUANTITY"

  executable_rules:
    field_dictionary_ref: "spec/12_field_dictionary.yaml:field_dictionary"
    formula_refs:
      cash_ratios: "spec/13_formula_registry.yaml:formula_registry.formulas.CASH_RATIOS_V1"
      portfolio_band_status: "spec/13_formula_registry.yaml:formula_registry.formulas.PORTFOLIO_BAND_STATUS_V1"
      mean_reversion_gate: "spec/13_formula_registry.yaml:formula_registry.formulas.MEAN_REVERSION_GATE_V1"  # [2026-05-19_ALPHA_SHIELD_V1]
    rules:
      - id: "PG001_ACCOUNT_ALLOWED"
        input_field: "account_type"
        pass_if: "account_type in ['일반계좌', 'ISA', '연금저축']"
        fail_action: "ACCOUNT_NOT_ALLOWED"
      - id: "PG002_CONTRIBUTION_LIMIT"
        input_fields: ["account_type", "monthly_contribution_planned", "monthly_contribution_used"]
        pass_if: "account_type == '일반계좌' OR monthly_contribution_planned + monthly_contribution_used <= account_monthly_limit"
        fail_action: "ACCOUNT_LIMIT_FAIL"
      - id: "PG003_CASH_GATE"
        formula_ref: "CASH_RATIOS_V1"
        pass_if: "post_trade_immediate_cash_ratio >= min_cash_ratio"
        fail_action: "BUY_BLOCKED"
      - id: "MRG001_MEAN_REVERSION_GATE"  # [2026-05-19_ALPHA_SHIELD_V1] X1
        formula_ref: "MEAN_REVERSION_GATE_V1"
        pass_if: "deviation_ratio < 1.10"
        caution_if: "1.10 <= deviation_ratio < 1.15"
        fail_action: "BUY_HARD_BLOCK (deviation_ratio >= 1.15)"
      - id: "PG004_EXPOSURE_BUDGET_OUTPUT"
        formula_ref: "PORTFOLIO_BAND_STATUS_V1"
        output_fields: ["max_order_budget_krw", "target_bucket", "portfolio_constraint_status"]
        fail_action: "NO_POSITION_SIZING_INPUT"

reporting_requirement:
  table_columns:
    - "계좌"
    - "목표버킷"
    - "현재비중(%)"
    - "목표밴드"
    - "중복노출상태"
    - "즉시현금비중(%)"
    - "매수가능현금"
    - "계좌한도상태"
    - "허용행동"
  prohibition:
    - "계좌 한도·현금·보유수량 미확인 상태에서 최종 주문수량 출력 금지"
    - "포트폴리오 제약 실패를 종목 점수로 상쇄 금지"
