schema_version: formula_domain.v1
source: C:\Temp\data_feed\spec\13_formula_registry.yaml
domain: portfolio
formulas:
  TOTAL_HEAT_V1:
    purpose: 손절 기준 총 위험노출 계산
    inputs:
    - field: average_cost
      source: account_snapshot
      unit: KRW_per_share
    - field: stop_price
      source: account_snapshot
      unit: KRW_per_share
    - field: quantity
      source: account_snapshot.holding_quantity
      unit: shares
    - field: total_asset
      unit: KRW
    expression: sum((average_cost - stop_price) * quantity for each confirmed account_snapshot
      holding) / total_asset * 100
    output:
      field: total_heat_pct
      unit: percent
    missing_policy:
      stop_price: if atr20 exists use entry_price - atr20*2.0 else assume portfolio
        heat contribution cap breach
      quantity: NO_TOTAL_HEAT
      total_asset: NO_TOTAL_HEAT
    gates:
    - if: total_heat_pct >= 10
      action: BLOCK_NEW_BUY
    - if: 7 <= total_heat_pct < 10
      action: HALVE_NEW_BUY_QUANTITY
    - if: total_heat_pct < 7
      action: ALLOW_CONTINUE
    canonical_ref: spec/risk/aggregate_risk.yaml:risk_control.aggregate_risk_cap
    owner: quant_team
    lifecycle_state: active
    input_fields:
    - average_cost
    - stop_price
    - quantity
    - total_asset
    output_fields:
    - total_heat_pct
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  RISK_BUDGET_CASCADE_V1:
    purpose: base risk budget에 Bayesian, 성과, 국면, Kelly 감액을 순서대로 적용
    inputs:
    - field: base_risk_budget
      unit: ratio
      default: 0.007
    - field: net_return_feedback_multiplier
      unit: ratio
      default: 1.0
    - field: performance_brake_multiplier
      unit: ratio
      default: 1.0
    - field: regime_reset_multiplier
      unit: ratio
      default: 1.0
    - field: bayesian_confidence_multiplier
      unit: ratio
    - field: kelly_brake_multiplier
      unit: ratio
      default: 1.0
    expression: base_risk_budget * net_return_feedback_multiplier * performance_brake_multiplier
      * regime_reset_multiplier * bayesian_confidence_multiplier * kelly_brake_multiplier
    output:
      field: final_risk_budget
      unit: ratio
    floor_rule:
      if: final_risk_budget < 0.001
      action: NO_BET
    canonical_ref: spec/05_position_sizing.yaml:position_sizing.cascade_risk_budget_rule
    owner: quant_team
    lifecycle_state: active
    input_fields:
    - base_risk_budget
    - net_return_feedback_multiplier
    - performance_brake_multiplier
    - regime_reset_multiplier
    - bayesian_confidence_multiplier
    - kelly_brake_multiplier
    output_fields:
    - final_risk_budget
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  POSITION_SIZE_V1:
    purpose: 최종 정수 매수수량 산출
    inputs:
    - field: total_asset
      unit: KRW
    - field: final_risk_budget
      unit: ratio
    - field: atr20
      unit: KRW_per_share
    - field: atr_multiplier
      unit: ratio
      default: 1.5
    - field: available_cash
      unit: KRW
    - field: entry_price
      unit: KRW_per_share
    - field: target_weight_limit_amount
      unit: KRW
    - field: sector_limit_amount
      unit: KRW
    - field: liquidity_limit_amount
      unit: KRW
    intermediate_outputs:
      atr_quantity: floor((total_asset * final_risk_budget) / (atr20 * atr_multiplier))
      cash_limit_quantity: floor(available_cash / entry_price)
      target_weight_limit_quantity: floor(target_weight_limit_amount / entry_price)
      sector_limit_quantity: floor(sector_limit_amount / entry_price)
      liquidity_limit_quantity: floor(liquidity_limit_amount / entry_price)
    expression: min(atr_quantity, cash_limit_quantity, target_weight_limit_quantity,
      sector_limit_quantity, liquidity_limit_quantity)
    output:
      field: final_quantity
      unit: shares_integer
    missing_policy:
      atr20: NO_BUY_QUANTITY
      total_asset: NO_BUY_QUANTITY
      available_cash: NO_BUY_QUANTITY
      entry_price: NO_BUY_QUANTITY
      target_weight_limit_amount: use very large number only if portfolio rule says
        NOT_APPLICABLE
      sector_limit_amount: use very large number only if sector cap says NOT_APPLICABLE
      liquidity_limit_amount: allow PARTIAL only for report; BUY validation_status
        cannot PASS
    canonical_ref: spec/05_position_sizing.yaml:position_sizing.volatility_targeting
    owner: quant_team
    lifecycle_state: active
    input_fields:
    - total_asset
    - final_risk_budget
    - atr20
    - atr_multiplier
    - available_cash
    - entry_price
    - target_weight_limit_amount
    - sector_limit_amount
    - liquidity_limit_amount
    output_fields:
    - final_quantity
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  PORTFOLIO_BAND_STATUS_V1:
    purpose: 현재 비중이 목표 밴드보다 낮은지, 정상인지, 초과인지 판정
    inputs:
    - field: current_weight_pct
      unit: percent
    - field: target_band_min_pct
      unit: percent
    - field: target_band_max_pct
      unit: percent
    rules:
    - if: current_weight_pct < target_band_min_pct
      status: UNDERWEIGHT
      action: ADD_ALLOWED_IF_ALL_GATES_PASS
    - if: target_band_min_pct <= current_weight_pct <= target_band_max_pct
      status: IN_BAND
      action: HOLD_OR_SELECTIVE_ADD
    - if: current_weight_pct > target_band_max_pct
      status: OVERWEIGHT
      action: TRIM_REVIEW
    output:
      field: portfolio_band_status
      unit: enum
    missing_policy: DATA_MISSING. add/trim 결론 보류.
    canonical_ref: spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.target_allocation_structure
    owner: quant_team
    lifecycle_state: active
    input_fields:
    - current_weight_pct
    - target_band_min_pct
    - target_band_max_pct
    output_fields:
    - portfolio_band_status
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  PORTFOLIO_BETA_V1:
    purpose: 보유 포지션의 시가기준 가중평균 베타를 산출하여 팩터 과집중 판단에 사용
    inputs:
    - field: beta_i
      source: data_feed.Beta for each holding i
      unit: ratio
    - field: market_value_i
      source: account_snapshot.holding_quantity × close_price
      unit: KRW
    - field: total_equity_value
      source: sum(market_value_i)
      unit: KRW
    expression: sum(beta_i × market_value_i / total_equity_value) for each holding
      with known beta
    output:
      field: portfolio_beta
      unit: ratio
    missing_policy:
      beta_i_missing_single: '해당 종목 제외 후 부분 산출. 제외 종목 시가 비중이 30% 초과 시 결과에 "(PARTIAL
        — Beta 미확인 {N}개 종목 제외)" 표기.

        '
      beta_i_missing_all: NO_PORTFOLIO_BETA. 팩터 리스크 점검 PARTIAL 표기.
      total_equity_value_zero: NO_PORTFOLIO_BETA
    example:
      holdings:
      - name: 삼성전자
        market_value: 100000000
        beta: 1.1
      - name: SK하이닉스
        market_value: 80000000
        beta: 1.3
      - name: 한화에어로스페이스
        market_value: 40000000
        beta: 1.6
      total_equity: 220000000
      result: (1.1×100 + 1.3×80 + 1.6×40) / 220 = (110 + 104 + 64) / 220 = 278/220
        ≈ 1.26
    canonical_ref: spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.factor_risk_limit
    version: 2026-05-18_ROUTING_OPTIMIZATION_V1
    owner: quant_team
    lifecycle_state: active
    input_fields:
    - beta_i
    - market_value_i
    - total_equity_value
    output_fields:
    - portfolio_beta
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  PORTFOLIO_CORRELATION_GATE_V1:
    purpose: '위성 포지션들 간 20D 수익률 Pearson 상관관계를 계산해 동일 방향 클러스터가 포트폴리오 하락 리스크를 증폭시키는지
      감지한다. 개별 Beta x 상관관계 조정으로 실질 포트폴리오 Beta(satellite_cluster_beta) 산출.

      '
    applicable: calcApexExecutionHarness_ 포트폴리오 집계 단계. SAPG_V1 이후 실행.
    inputs:
    - field: ticker
    - field: price.ret20D
    - field: beta_proxy
    - field: weight_pct
    computed:
      correlation_matrix: 각 위성 쌍 (i,j) Pearson 상관계수. 데이터 부족 시 ret20D/globalKospiRet20D_
        프록시.
      satellite_cluster_beta: sum(weight_i * weight_j * beta_i * beta_j * corr_ij)
        for all i,j pairs
      effective_portfolio_beta: (core_weight * core_beta) + satellite_cluster_beta
    gate_status:
      CORRELATION_BLOCK:
        condition: satellite_cluster_beta > 1.5 AND corr >= 0.70인 위성 쌍이 2쌍 이상
        action: 고상관 약한 위성 ADD 금지, REVIEW 위성 우선 정리, 실질 beta 보고서 표기 의무
      CORRELATION_WARN:
        condition: satellite_cluster_beta > 1.2 OR corr >= 0.70인 위성 쌍이 1쌍
        action: 신규 위성 편입 시 저상관 후보 우선
      CORRELATION_PASS:
        condition: satellite_cluster_beta <= 1.2
        action: 정상. M2 독립 적용.
    output_fields:
    - field: satellite_cluster_beta
    - field: effective_portfolio_beta
    - field: high_corr_pairs
      unit: list [{ticker1,ticker2,corr_coef}]
    - field: correlation_gate_status
      unit: enum [CORRELATION_PASS,CORRELATION_WARN,CORRELATION_BLOCK]
    ground_truth: harness
    llm_allowed: cite_only
    prohibition:
    - LLM이 상관행렬 직접 계산 금지
    - 개별 beta 낮아도 satellite_cluster_beta 높으면 분산 됐다 서술 금지
    output:
      field: satellite_cluster_beta
      additional_fields:
      - effective_portfolio_beta
      - high_corr_pairs
      - correlation_gate_status
    version: 2026-05-21_PCG_V1
    owner: quant_team
    lifecycle_state: active
    input_fields:
    - ticker
    - price.ret20D
    - beta_proxy
    - weight_pct
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  DYNAMIC_HEAT_GATE_V1:
    purpose: '국면별 총 위험노출 임계값을 산출해 신규 매수 차단 여부를 결정한다.

      '
    inputs:
    - field: market_regime
      unit: enum
    - field: total_heat_pct
      unit: pct
    output:
      field: heat_gate_status
    input_fields:
    - market_regime
    - total_heat_pct
    expected_outputs:
    - heat_gate_status
    - heat_gate_threshold_pct
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - heat_gate_status
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  POSITION_SIZE_REGIME_SCALE_V1:
    purpose: '국면별 포지션 크기 스케일을 결정론적으로 산출한다.

      '
    inputs:
    - field: market_regime
      unit: enum
    output:
      field: regime_size_scale
    input_fields:
    - market_regime
    expected_outputs:
    - regime_size_scale
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - regime_size_scale
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  DRAWDOWN_GUARD_V1:
    purpose: '연속 손절/성과 악화 구간에서 신규 매수 수량을 자동 축소하거나 차단한다.

      '
    inputs:
    - field: win_loss_streak_state
      unit: enum
    - field: win_loss_streak_buy_scale
      unit: multiplier
    output:
      field: drawdown_guard_state
    input_fields:
    - consecutive_loss_count
    - recent_win_loss_state
    expected_outputs:
    - drawdown_guard_state
    - drawdown_buy_scale
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - drawdown_guard_state
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  POSITION_COUNT_LIMIT_V1:
    purpose: '동시 보유 종목 수 상한과 초과 여부를 판단한다.

      '
    inputs:
    - field: position_count
      unit: integer
    - field: market_regime
      unit: enum
    output:
      field: position_count_gate
    input_fields:
    - position_count
    - market_regime
    expected_outputs:
    - position_count_gate
    - position_count_max
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - position_count_gate
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  SINGLE_POSITION_WEIGHT_CAP_V1:
    purpose: '단일 종목 비중 상한과 초과 TRIM 필요 여부를 판단한다.

      '
    inputs:
    - field: single_position_weight_json
      unit: json
    - field: market_regime
      unit: enum
    output:
      field: single_position_weight_gate
    input_fields:
    - position_weight_pct
    - market_regime
    expected_outputs:
    - single_position_weight_gate
    - weight_cap_pct
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - single_position_weight_gate
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  REGIME_TRIM_GUIDANCE_V1:
    purpose: '국면별 현금확보용 TRIM 우선순위를 결정한다.

      '
    inputs:
    - field: regime_adjusted_sell_priority_json
      unit: json
    - field: market_regime
      unit: enum
    output:
      field: regime_trim_guidance
    input_fields:
    - market_regime
    - sector_rank
    expected_outputs:
    - regime_trim_guidance
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - regime_trim_guidance
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  HEAT_CONCENTRATION_ALERT_V1:
    purpose: '단일 종목이 총 Heat의 과도한 비중을 차지하는지 경보를 낸다.

      '
    inputs:
    - field: heat_share_pct
      unit: pct
    output:
      field: heat_concentration_gate
    input_fields:
    - heat_share_pct
    expected_outputs:
    - heat_concentration_gate
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - heat_concentration_gate
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  SECTOR_CONCENTRATION_LIMIT_V1:
    purpose: '섹터 편중 한도와 신규 BUY 차단 여부를 판단한다.

      '
    inputs:
    - field: sector_concentration_json
      unit: json
    - field: market_regime
      unit: enum
    output:
      field: sector_concentration_gate
    input_fields:
    - sector_concentration_pct
    - market_regime
    expected_outputs:
    - sector_concentration_gate
    - sector_concentration_limit_pct
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - sector_concentration_gate
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  PORTFOLIO_DRAWDOWN_GATE_V1:
    purpose: '포트폴리오 고점 대비 낙폭을 산출해 신규 BUY 차단 여부를 판단한다.

      '
    inputs:
    - field: portfolio_peak_krw
      unit: KRW
    - field: total_asset_krw
      unit: KRW
    output:
      field: portfolio_drawdown_gate
    input_fields:
    - portfolio_peak_krw
    - total_asset_krw
    expected_outputs:
    - portfolio_drawdown_gate
    - portfolio_drawdown_pct
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - portfolio_drawdown_gate
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  SECTOR_ROTATION_MOMENTUM_V1:
    purpose: '섹터 로테이션 모멘텀 상태와 신규 매수 적합성을 판정한다.

      '
    inputs:
    - field: sector
      unit: string
    - field: momentum_state
      unit: enum
    output:
      field: sector_rotation_momentum_json
    input_fields:
    - sector
    - momentum_state
    expected_outputs:
    - sector_rotation_momentum_json
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - sector_rotation_momentum_json
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  MARKET_WEIGHT_AWARE_CLUSTER_GATE_V1:
    purpose: '시장 반도체 비중을 반영한 동적 클러스터 차단/경고 임계값을 산출한다.

      '
    inputs:
    - field: semiconductor_cluster_json
      unit: json
    - field: market_regime
      unit: enum
    output:
      field: semiconductor_cluster_gate
    input_fields:
    - kospi_semi_weight_pct
    - combined_pct
    - market_regime
    expected_outputs:
    - cluster_gate
    - cap_pct
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - semiconductor_cluster_gate
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  LEADER_POSITION_WEIGHT_CAP_V1:
    purpose: '주도주 종목별 차등 비중 상한과 초과 TRIM 필요 여부를 산출한다.

      '
    inputs:
    - field: single_position_weight_json
      unit: json
    - field: market_regime
      unit: enum
    output:
      field: single_position_weight_gate
    input_fields:
    - ticker
    - position_weight_pct
    - market_regime
    expected_outputs:
    - leader_position_weight_gate
    - weight_cap_pct
    llm_allowed: cite_only
    version: 2026-05-30_PHASE8
    owner: quant_team
    lifecycle_state: active
    output_fields:
    - single_position_weight_gate
    missing_policy: DATA_MISSING. 계산 결과를 추정하지 않는다.
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  REGIME_CONDITIONAL_MACRO_FACTOR_V1:
    purpose: 거시팩터 종목별 FX 민감도 베타 적용 — 단일팩터 전 종목 균일 지배 차단 (Direction SFP1)
    agents_md_ref: 'Direction SFP1: SINGLE_FACTOR_DOMINANCE_CAP_V1'
    inputs:
    - field: base_macro_score
      unit: ratio_0_1
    - field: ticker
      unit: string
    - field: ticker_type
      unit: 'enum: export | domestic | neutral'
    expression: base_macro_score x fx_sensitivity_beta(ticker_type)
    components:
      fx_sensitivity_beta:
        export: 1.2
        domestic: 0.7
        neutral: 1.0
        note: '수출주(삼성전자·SK하이닉스 등): FX 민감도 20% 가중. 내수주: 30% 축소.'
    output:
      field: macro_factor_applied
      unit: ratio_0_1
    gate:
      condition: single_factor_max_share_pct > 50
      result: SINGLE_FACTOR_DEGENERATE
      action: WARN — synthesis_verdict 다양성 확보 실패, 보고서 첫 줄 경고 의무
    missing_policy: ticker_type 미확인 시 fx_beta=1.0(neutral) 적용
    implementation: tools/build_predictive_alpha_dialectic_engine_v2.py:NF1
    calibration_ref: spec/calibration_registry.yaml:NF1 (EXPERT_PRIOR)
    version: 2026-06-04_NF1
    owner: quant_team
    lifecycle_state: active
    input_fields:
    - base_macro_score
    - ticker
    - ticker_type
    output_fields:
    - macro_factor_applied
    golden_cases: []
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  PORTFOLIO_TRANSITION_UTILITY_V1:
    purpose: >
      개별 매수·매도 추천이 아니라 포트폴리오 전체의 사후 상태(전환 후 cash floor, 집중도, CVaR,
      세후비용, 회전율)를 비교해 단일 최선 전환 또는 NO_TRADE를 결정론적으로 선택한다.
      (governance/todo/v8_9_p0_adoption_plan.yaml P0-1.2,
      source: suggest/quant_investment_engine_v8_9_portfolio_optimizer_canonical_refactored.yaml:portfolio_transition_optimizer_v8_9)
    default_action: NO_TRADE
    state_vector_fields:
    - cash_ladder
    - positions
    - sector_exposure_graph
    - tax_lots
    - risk_bucket_weights
    - goal_progress_pct
    - data_quality_scores
    candidate_action_schema:
    - candidate_id
    - asset_id
    - action_type
    - planned_amount_krw
    - source_signal_ids
    - numeric_provenance_status
    hard_veto_order:
    - DATA_INVALID
    - EXECUTION_MODE_BLOCK
    - CASH_FLOOR_BLOCK
    - HARD_CONCENTRATION_BLOCK
    - NEGATIVE_TRANSITION_UTILITY
    inputs:
    - field: ce70_net_profit_krw
      source: Temp/forecast_simulation_engine_v1.json
      unit: KRW
      missing_policy: DATA_MISSING — candidate excluded, not assumed zero
    - field: tax_fee_slippage_krw
      source: Temp/sell_waterfall_engine_v4.json
      unit: KRW
    - field: cash_repair_benefit_krw
      source: Temp/smart_cash_recovery_v9.json
      unit: KRW
    - field: concentration_reduction_benefit_krw
      unit: KRW
    - field: turnover_penalty_krw
      unit: KRW
    expression: >
      transition_utility_krw = ce70_net_profit_krw - tax_fee_slippage_krw - cvar_penalty_krw
      - drawdown_penalty_krw + cash_repair_benefit_krw + concentration_reduction_benefit_krw
      - turnover_penalty_krw
    output:
      field: transition_utility_krw
      unit: KRW
    acceptance_margin:
      formula: acceptance_margin_krw = transition_utility_krw - max(mode_absolute_hurdle_krw, hurdle_multiple * estimated_total_cost_krw)
      reject_if: acceptance_margin_krw <= 0
    deterministic_fallbacks:
      missing_optimizer_inputs: NO_TRADE_AND_QUARANTINE
      solver_failure: NO_TRADE_AND_LOG_SOLVER_FAILURE
      rank_tie: choose_lower_turnover_lower_tax_lower_marginal_risk_contribution
      conflicting_runtime_packets: BLOCK_AND_REQUIRE_MANIFEST_REPAIR
    missing_policy:
      hard_constraint_input_missing: NO_TRADE_AND_QUARANTINE
    canonical_ref: spec/risk/portfolio_exposure.yaml:concentration_caps_v8_9_supplement
    implementation: tools/build_portfolio_transition_optimizer_v1.py
    owner: quant_team
    lifecycle_state: shadow
    input_fields:
    - ce70_net_profit_krw
    - tax_fee_slippage_krw
    - cash_repair_benefit_krw
    - concentration_reduction_benefit_krw
    - turnover_penalty_krw
    output_fields:
    - transition_utility_krw
    - acceptance_margin_krw
    - selected_transition
    golden_cases:
    - V89_002_no_trade_default
    - V89_048_solver_failure
    - V89_049_rank_tie
    - V89_050_conflicting_packets
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  TRANSITION_SET_ENUMERATOR_V1:
    purpose: >
      PORTFOLIO_TRANSITION_UTILITY_V1이 candidate 1건씩 평가하는 것을 넘어, 여러 candidate를
      조합한 transition_set 단위로 hard_constraint_pass와 transition_utility_krw를 평가한다.
      "좋은 후보 하나"가 포트폴리오 전체를 악화시키는 경우(V89_010)를 후보 조합 비교로 차단한다.
      (governance/todo/v8_9_p2_adoption_plan.yaml P2-B,
      source: suggest/quant_investment_engine_v8_9_portfolio_optimizer_canonical_refactored.yaml:portfolio_transition_optimizer_v8_9.selection_algorithm)
    applicable: PORTFOLIO_TRANSITION_UTILITY_V1이 candidate별 transition_utility_krw를 산출한 직후.
    inputs:
    - field: evaluated_candidates
      unit: list_of_object
      note: PORTFOLIO_TRANSITION_UTILITY_V1.candidate_actions 산출(hard_constraint_pass, transition_utility_krw 포함)
    - field: max_set_size
      unit: count
      default: 3
      note: 조합 폭발 방지. v8.9 turnover_budget(주간 5%) 고려 시 동시 실행 후보는 통상 1~3건.
    selection_algorithm:
    - step_1: hard_constraint_pass=false인 candidate는 set 구성에서 제외(개별 veto 우선)
    - step_2: 남은 candidate에서 크기 1..max_set_size의 모든 조합(transition_set)을 생성
    - step_3: 각 transition_set의 post_trade_cash_floor_pct, post_trade_concentration_pct, post_trade_MRC(marginal_risk_contribution), post_trade_CVaR95_krw를 합산 재평가
    - step_3b: post_trade_CVaR95_krw = sum(candidate.cvar95_loss_krw for candidate in set) — SCENARIO_SHOCK_MATRIX_V1.crisis_case 기준 사용(가장 보수적)
    - step_3c: post_trade_MRC = sum(candidate.marginal_risk_contribution for candidate in set) / portfolio_total_risk_budget
    - step_4: set_hard_constraint_pass=false인 set은 제외 (개별 candidate는 PASS했어도 조합 시 cash_floor/concentration/MRC/CVaR cap을 넘을 수 있음)
    - step_5: set_transition_utility_krw = sum(candidate.transition_utility_krw for candidate in set) - combination_penalty_krw
    - step_6: set_transition_utility_krw가 최대인 set 선택. 동률이면 PORTFOLIO_TRANSITION_UTILITY_V1.deterministic_fallbacks.rank_tie 규칙 재사용
    - step_7: 통과하는 set이 하나도 없으면 NO_TRADE (개별 candidate가 전부 PASS여도 조합 검증을 통과 못하면 거부)
    combination_penalty_krw:
      formula: complexity_penalty_rate * (len(transition_set) - 1)
      note: 후보 수가 많을수록 실행 복잡도·동시 슬리피지 리스크 증가를 반영한 페널티.
    output:
      field: selected_transition_set
      unit: list_of_candidate_id
    additional_outputs:
    - set_transition_utility_krw
    - set_hard_constraint_pass
    - rejected_sets_count
    - post_trade_mrc
    - post_trade_cvar95_krw
    missing_policy: evaluated_candidates가 비어 있으면 selected_transition_set=[] + NO_TRADE. 빈 조합을 임의로 채우지 않는다. cvar95_loss_krw가 candidate에 없으면(SCENARIO_SHOCK_MATRIX_V1 미실행) post_trade_cvar95_krw=null이며 해당 set은 set_hard_constraint_pass 판정에서 CVaR 기준을 PARTIAL로 표기.
    canonical_ref: spec/formulas/domains/portfolio.yaml:PORTFOLIO_TRANSITION_UTILITY_V1
    implementation: tools/build_transition_set_enumerator_v1.py
    owner: quant_team
    lifecycle_state: shadow
    input_fields:
    - evaluated_candidates
    - max_set_size
    output_fields:
    - selected_transition_set
    - set_transition_utility_krw
    - set_hard_constraint_pass
    - post_trade_mrc
    - post_trade_cvar95_krw
    golden_cases:
    - V89_010_candidate_good_portfolio_bad
    - V89_048_solver_failure
    - V89_049_rank_tie
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  STATE_VECTOR_CONSTRUCTOR_V1:
    purpose: >
      holdings, cash, tax_lots, sector_graph, factor_exposures, macro_regime_probabilities를
      단일 state_vector로 통합해 PORTFOLIO_TRANSITION_UTILITY_V1과 TRANSITION_SET_ENUMERATOR_V1이
      동일한 포트폴리오 스냅샷을 참조하도록 한다. 부분 입력으로 state_vector를 임의 보완하지 않는다.
      (governance/todo/v8_9_p3_adoption_plan.yaml P3-A,
      source: suggest/quant_investment_engine_v8_9_portfolio_optimizer_canonical_refactored.yaml:implementation_todo_v8_9.P1_optimizer_and_simulation)
    applicable: MODEL_GOVERNANCE_GATE에서 execution_mode 확정 직후, HARD_FILTER_CHECK 이전.
    component_sources:
      cash_ladder: spec/formulas/domains/cash.yaml:CASH_RATIOS_V1
      positions: spec/15_account_snapshot_contract.yaml
      sector_exposure_graph: spec/formulas/domains/sector.yaml:SECTOR_EXPOSURE_GRAPH_V1
      factor_exposures: spec/risk/factor_risk.yaml
      tax_lots: spec/15_account_snapshot_contract.yaml
      risk_bucket_weights: spec/risk/portfolio_exposure.yaml
      macro_regime_probabilities: spec/risk/market_risk_cash.yaml
      goal_progress_pct: spec/13_formula_registry.yaml:formula_registry.formulas.GOAL_RETIREMENT_V1
    inputs:
    - field: cash_ladder
      unit: json
    - field: positions
      unit: list_of_object
    - field: sector_exposure_graph
      unit: list_of_object
    - field: factor_exposures
      unit: list_of_object
    - field: tax_lots
      unit: list_of_object
    - field: risk_bucket_weights
      unit: object
    - field: macro_regime_probabilities
      unit: object
    - field: goal_progress_pct
      unit: percent
    output:
      field: state_vector
      unit: object
    additional_outputs:
    - state_vector_completeness_pct
    - missing_components
    missing_policy: 결측 component는 state_vector에서 null로 유지하고 missing_components에 기록한다. 다른 component로 추정 보완 금지.
    canonical_ref: spec/formulas/domains/portfolio.yaml:PORTFOLIO_TRANSITION_UTILITY_V1
    implementation: tools/build_state_vector_constructor_v1.py
    owner: quant_team
    lifecycle_state: shadow
    input_fields:
    - cash_ladder
    - positions
    - sector_exposure_graph
    - factor_exposures
    - tax_lots
    - risk_bucket_weights
    - macro_regime_probabilities
    - goal_progress_pct
    output_fields:
    - state_vector
    - state_vector_completeness_pct
    - missing_components
    golden_cases:
    - V89_052_goal_far_from_target
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  REBALANCE_CADENCE_GATE_V1:
    purpose: >
      주간(토/일) 및 매월 1/11/21일 점검을 의무 실행하되, 실제 리밸런싱(매수/매도 실행)은
      transition_utility_after_tax_cost가 양수이거나 hard_risk_block이 active일 때만 허용한다.
      점검 자체는 항상 emit되어 "점검을 안 했다"는 누락을 방지하지만, 결과가 기준 미달이면 NO_TRADE.
      (governance/todo/v8_9_p3_adoption_plan.yaml P3-D,
      source: suggest/quant_investment_engine_v8_9_portfolio_optimizer_canonical_refactored.yaml:implementation_todo_v8_9.P3_sell_and_rebalance,
      rebalancing_engine_v8_9.mandatory_schedule)
    applicable: 매주 토/일 또는 매월 1/11/21일. PORTFOLIO_TRANSITION_REVIEW 진입 조건.
    mandatory_schedule:
      weekly_days: [SATURDAY, SUNDAY]
      monthly_mid_check_days: [1, 11, 21]
      event_driven_triggers: [cash_floor_break, crisis_score_red_or_black, hard_concentration_breach, data_quarantine_material]
    inputs:
    - field: today_date
      unit: date
    - field: transition_utility_after_tax_cost_krw
      unit: number_or_null
      source: spec/formulas/domains/portfolio.yaml:PORTFOLIO_TRANSITION_UTILITY_V1
    - field: hard_risk_block_active
      unit: boolean
      source: spec/risk/aggregate_risk.yaml
    cadence_check_rule: >
      today_date가 weekly_days, monthly_mid_check_days, event_driven_triggers 중 하나라도 충족하면
      cadence_check_required=true이며 점검 결과(review_emitted)는 항상 emit한다.
    rebalance_execution_rule: >
      cadence_check_required=true이고 (transition_utility_after_tax_cost_krw > 0 OR hard_risk_block_active=true)
      일 때만 rebalance_execution_allowed=true. 그 외에는 review_emitted=true이지만 rebalance_execution_allowed=false
      (점검은 했지만 NO_TRADE).
    output:
      field: rebalance_execution_allowed
      unit: boolean
    additional_outputs:
    - cadence_check_required
    - review_emitted
    - cadence_trigger_reason
    missing_policy: transition_utility_after_tax_cost_krw가 null이면 hard_risk_block_active만으로 판정. 둘 다 null이면 rebalance_execution_allowed=false + DATA_MISSING.
    canonical_ref: spec/risk/aggregate_risk.yaml
    implementation: tools/build_rebalance_cadence_gate_v1.py
    owner: quant_team
    lifecycle_state: shadow
    input_fields:
    - today_date
    - transition_utility_after_tax_cost_krw
    - hard_risk_block_active
    output_fields:
    - rebalance_execution_allowed
    - cadence_check_required
    - review_emitted
    golden_cases:
    - V89_032_no_trade_band
    - V89_033_hard_block_overrides_band
    - V89_053_weekly_rebalance_required
    - V89_054_mid_check_required
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
