schema_version: formula_domain.v1
source: C:\Temp\data_feed\spec\13_formula_registry.yaml
domain: simulation
meta:
  note: >
    governance/todo/v8_9_p0_adoption_plan.yaml P0-3.1.
    source: suggest/quant_investment_engine_v8_9_portfolio_optimizer_canonical_refactored.yaml:forecast_and_simulation_engine_v8_9
    배경: spec/29_backtest_harness_contract.yaml가 이미 "T+20 실현 표본 0건, walk_forward=insufficient_data"를
    명시한다(AGENTS.md §6b). 이 도메인 파일은 분포를 날조하지 않고, 표본이 minimum_sample_rules를 충족할 때만
    실제 bootstrap 계산 경로를 열고 미달이면 DATA_MISSING/WATCH_ONLY를 결정론적으로 반환하는 계약이다.
formulas:
  FORECAST_SIMULATION_ENGINE_V1:
    purpose: >
      개별 종목의 점 추정 기대수익률이 아니라 레짐별 손익분포에서 CE70(30%분위)·CE90(10%분위)·
      CVaR95(95% 신뢰구간 꼬리손실 평균)를 산출한다. 표본 부족 시 가짜 분포를 만들지 않고
      WATCH_ONLY 또는 DATA_MISSING으로 정직하게 반환한다.
    applicable: PORTFOLIO_TRANSITION_UTILITY_V1의 ce70_net_profit_krw 입력 직전.
    inputs:
    - field: net_profit_distribution_after_tax_fee_slippage
      source: spec/29_backtest_harness_contract.yaml:current_metrics
      unit: list_of_KRW
      note: 세후·비용 차감 손익 표본. 현재 t20_op_rate.n_sample=0 (insufficient_data).
    - field: sample_count_total
      unit: count
    - field: sample_count_same_regime
      unit: count
    - field: execution_mode
      unit: enum
      note: AUDIT_ONLY | SHADOW | PILOT | LIVE_LIMITED | LIVE_FULL
    minimum_sample_rules:
      AUDIT_ONLY:
        sample_count_total_min: 0
        sample_count_same_regime_min: 0
        note: no minimum; report missing samples only (documentation only, no live order)
      SHADOW:
        sample_count_total_min: 30
        sample_count_same_regime_min: 10
      PILOT:
        sample_count_total_min: 80
        sample_count_same_regime_min: 20
      LIVE_LIMITED:
        sample_count_total_min: 150
        sample_count_same_regime_min: 30
      LIVE_FULL:
        sample_count_total_min: 300
        sample_count_same_regime_min: 50
      agents_md_cross_check: "AGENTS.md §6b: Live T+20 표본 30건 미만이면 active/PASS_100 승격 금지"
    gate_logic:
    - if: sample_count_total < minimum_sample_rules[execution_mode].sample_count_total_min
      action: WATCH_ONLY
      output: "ce70_net_profit_krw=null, ce90_net_profit_krw=null, cvar95_loss_krw=null"
    - if: sample_count_same_regime < minimum_sample_rules[execution_mode].sample_count_same_regime_min
      action: WATCH_ONLY
      output: "ce70_net_profit_krw=null, ce90_net_profit_krw=null, cvar95_loss_krw=null"
    - if: sample_count_total >= minimum AND sample_count_same_regime >= minimum
      action: COMPUTE
      expression:
        ce70_net_profit_krw: quantile(net_profit_distribution_after_tax_fee_slippage, 0.30)
        ce90_net_profit_krw: quantile(net_profit_distribution_after_tax_fee_slippage, 0.10)
        cvar95_loss_krw: mean(losses beyond 95th percentile loss threshold in net_profit_distribution_after_tax_fee_slippage)
    estimator_allowlist:
    - walk_forward_bootstrap
    - regime_matched_bootstrap
    forbidden_estimators:
    - LLM_guess_return
    - single_point_target_price_without_distribution
    - backtest_without_cost_or_leakage_test
    output:
      field: ce70_net_profit_krw
      unit: KRW_or_null
    additional_outputs:
    - ce90_net_profit_krw
    - cvar95_loss_krw
    - sample_count_total
    - sample_count_same_regime
    - gate
    missing_policy:
      net_profit_distribution_after_tax_fee_slippage: 표본 없으면 모든 출력 null + gate=WATCH_ONLY. 0으로 대체 금지.
    canonical_ref: spec/29_backtest_harness_contract.yaml:current_metrics.walk_forward
    implementation: tools/build_forecast_simulation_engine_v1.py
    owner: quant_team
    lifecycle_state: shadow
    input_fields:
    - net_profit_distribution_after_tax_fee_slippage
    - sample_count_total
    - sample_count_same_regime
    - execution_mode
    output_fields:
    - ce70_net_profit_krw
    - ce90_net_profit_krw
    - cvar95_loss_krw
    - sample_count_total
    - sample_count_same_regime
    - gate
    golden_cases:
    - V89_013_missing_CVaR
    - V89_014_same_regime_sample_low
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  SCENARIO_SHOCK_MATRIX_V1:
    purpose: >
      base_case 분포 하나만으로 위기 취약성을 가리지 않도록, FORECAST_SIMULATION_ENGINE_V1의
      net_profit_distribution_after_tax_fee_slippage에 5개 스트레스 시나리오를 결정론적으로
      적용해 각 시나리오별 CE70/CVaR95를 산출한다. 거짓 분포 생성을 금지하며, base distribution이
      없으면 모든 시나리오가 DATA_MISSING이다.
      (governance/todo/v8_9_p2_adoption_plan.yaml P2-A,
      source: suggest/quant_investment_engine_v8_9_portfolio_optimizer_canonical_refactored.yaml:forecast_and_simulation_engine_v8_9.simulation_grid)
    applicable: FORECAST_SIMULATION_ENGINE_V1의 gate=PASS(분포 산출 성공) 직후.
    scenario_definitions:
      base_case: {shock_multiplier: 1.0, note: current_regime_probability_weighted, source_distribution: as-is}
      adverse_case: {shock_multiplier: 1.5, note: volatility_up_and_breadth_down — 손실 구간 증폭}
      liquidity_drought_case: {shock_multiplier: 1.3, capacity_derate_pct: 40, note: spread_widening_and_capacity_down}
      crisis_case: {shock_multiplier: 2.0, correlation_to_one: true, note: correlation_to_one_and_gap_down}
      fx_shock_case: {shock_multiplier: 1.2, applies_only_to: foreign_assets, note: USDKRW adverse movement}
      tax_cost_case: {shock_multiplier: 1.0, additional_cost_pct: 5, note: realized_gain_tax_and_reentry_cost_stress}
    inputs:
    - field: net_profit_distribution_after_tax_fee_slippage
      unit: list_of_KRW
    - field: scenario_id
      unit: 'enum: base_case | adverse_case | liquidity_drought_case | crisis_case | fx_shock_case | tax_cost_case'
    expression: >
      shocked_distribution = [v * scenario.shock_multiplier if v < 0 else v / scenario.shock_multiplier
      for v in net_profit_distribution_after_tax_fee_slippage] (손실만 증폭, 이익은 보수적으로 축소)
      scenario_ce70_krw = quantile(shocked_distribution, 0.30)
      scenario_cvar95_krw = mean(losses beyond 95th percentile loss threshold in shocked_distribution)
    output:
      field: scenario_results
      unit: 'list_of_{scenario_id, scenario_ce70_krw, scenario_cvar95_krw}'
    missing_policy: net_profit_distribution_after_tax_fee_slippage가 없으면 전체 scenario_results가 DATA_MISSING. 시나리오별로 임의 분포 생성 금지.
    canonical_ref: spec/formulas/domains/simulation.yaml:FORECAST_SIMULATION_ENGINE_V1
    implementation: tools/build_scenario_shock_matrix_v1.py
    owner: quant_team
    lifecycle_state: shadow
    input_fields:
    - net_profit_distribution_after_tax_fee_slippage
    - scenario_id
    output_fields:
    - scenario_results
    golden_cases:
    - V89_010_candidate_good_portfolio_bad
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
  WALK_FORWARD_BOOTSTRAP_V1:
    purpose: >
      FORECAST_SIMULATION_ENGINE_V1이 입력으로 받는 net_profit_distribution_after_tax_fee_slippage를
      실제 historical_returns 표본에서 walk-forward(시간순 비복원, in-sample/out-of-sample 분리) 및
      regime-matched(현재 레짐과 동일한 구간만 필터) 리샘플링으로 생성한다. 가짜 분포 생성을 금지하며
      historical_returns가 없거나 표본이 1건뿐이면 DATA_MISSING.
      (governance/todo/v8_9_p3_adoption_plan.yaml P3-B,
      source: suggest/quant_investment_engine_v8_9_portfolio_optimizer_canonical_refactored.yaml:forecast_and_simulation_engine_v8_9.allowed_estimators)
    applicable: FORECAST_SIMULATION_ENGINE_V1 직전 — 이 엔진의 출력이 FORECAST_SIMULATION_ENGINE_V1의 입력이 된다.
    inputs:
    - field: historical_returns
      unit: list_of_object
      note: 'spec/29_backtest_harness_contract.yaml 연동. [{date, regime_state, net_return_after_cost_pct}]. 현재 t20 n_sample=0.'
    - field: current_regime_state
      unit: string
    - field: bootstrap_method
      unit: 'enum: walk_forward | regime_matched'
    - field: resample_count
      unit: count
      default: 1000
      note: 리샘플링 반복 횟수. historical_returns 표본이 적으면 resample_count와 무관하게 sample_count_total은 historical_returns 길이로 결정.
    estimator_rule:
      walk_forward: >
        historical_returns를 시간순으로 정렬해 첫 70%를 in-sample, 나머지 30%를 out-of-sample로
        고정 분리한다. out-of-sample 구간에서만 비복원 블록 리샘플링(block size=5)으로
        net_profit_distribution을 생성한다. in-sample 재사용 금지(leakage 방지).
      regime_matched: >
        historical_returns 중 regime_state == current_regime_state인 행만 필터링 후
        복원추출(bootstrap with replacement)로 net_profit_distribution을 생성한다.
        필터 결과가 비어 있으면 DATA_MISSING(다른 레짐으로 대체 금지).
    leakage_controls:
    - asof_join_required
    - future_constituent_exclusion
    - calendar_alignment
    output:
      field: net_profit_distribution_after_tax_fee_slippage
      unit: list_of_KRW_or_null
    additional_outputs:
    - sample_count_total
    - sample_count_same_regime
    - leakage_check_status
    missing_policy: historical_returns 결측 또는 표본 1건 이하면 net_profit_distribution=null + gate=DATA_MISSING. 보간·추정 금지.
    canonical_ref: spec/29_backtest_harness_contract.yaml:current_metrics.walk_forward
    implementation: tools/build_walk_forward_bootstrap_v1.py
    owner: quant_team
    lifecycle_state: shadow
    input_fields:
    - historical_returns
    - current_regime_state
    - bootstrap_method
    - resample_count
    output_fields:
    - net_profit_distribution_after_tax_fee_slippage
    - sample_count_total
    - sample_count_same_regime
    golden_cases:
    - V89_014_same_regime_sample_low
    - V89_048_solver_failure
    activation_threshold:
      min_t20_sample: 30
    retirement_condition: performance_degradation
