Files
QuantEngineByItz/spec/09_decision_flow.yaml
T
kjh2064 ee3e799de1 feat: 리밸런싱 엔진 V1 + GAS 버그 수정 (2026-06-13)
주요 변경:
- tools/build_rebalance_engine_v1.py: REBALANCE_ENGINE_V1 신규
  * account_snapshot 직접 합산(_build_snap_position_map) → 소수주 분리 행 병합
  * 레짐 소스 macro.REGIME_PRELIM 최우선 (GAS 와 동일)
- src/gas_adapter_parts/gdf_06_rebalance.gs: runRebalanceSheet_() 신규
  * Logger.log / getSpreadsheet_() 로 run_all 연동 수정
- src/gas_adapter_parts/gdc_01_fetch_fundamentals.gs
  * _mergePositionRecord_(): 소수주 중복 행 합산 신규
  * parseInt → parseFloat (qty, availQty)
- src/gas_adapter_parts/gdf_01_price_metrics.gs
  * 미보유 종목 SELL_READY → WATCH_EXIT_SIGNAL
- spec/41_release_dag.yaml: build_rebalance_sheet 노드 추가 (step_count 63)
- spec/51_formula_lifecycle_registry.yaml: REBALANCE_ENGINE_V1 등록

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:20:14 +09:00

261 lines
14 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
meta:
title: "은퇴자산포트폴리오 — 의사결정 상태 머신"
parent_file: "RetirementAssetPortfolio.yaml"
version: "2026-05-15-F3_decision_flow"
language: "ko-KR"
timezone: "Asia/Seoul"
purpose: >
LLM이 투자 판단을 임의 순서로 수행하지 않도록 상태 머신으로 절차를 고정한다.
각 상태는 통과 조건, 실패 시 행동, 참조 파일을 가진다.
decision_flow:
initial_state: "INPUT_VALIDATION"
terminal_states: ["FINAL_DECISION", "INSUFFICIENT_DATA", "BLOCKED"]
deterministic_execution_control:
purpose: "텍스트 해석 차이로 매번 다른 결론이 나오는 것을 줄이기 위한 결정 추적·동률 처리·first-match 규칙."
rule: "모든 상태는 ordered_checks를 위에서 아래로 평가하고, 첫 번째 BLOCK/PASS/SELECT 결과를 decision_trace에 기록한다."
no_freeform_override:
- "상태 머신 밖의 산문 판단으로 final_action, grade, quantity, sell_priority를 변경 금지"
- "동일 입력·동일 기준시각이면 동일 decision_trace와 동일 final_action이 나와야 한다."
- "규칙 충돌 시 자연어로 절충하지 말고 tie_breaker 또는 INSUFFICIENT_DATA/BLOCKED로 종료한다."
trace_required_fields:
- "state"
- "check_id"
- "rule_ref"
- "inputs_used"
- "result"
- "selected_action"
- "blocked_actions"
- "missing_inputs"
- "tie_breaker_applied"
tie_breaker_order:
1: "source_of_truth_order 상위 파일"
2: "risk hard stop 또는 master_prohibitions"
3: "데이터 완성도 OK > PARTIAL > DATA_MISSING"
4: "계산 가능한 공식 ID가 있는 규칙"
5: "보수적 행동: BLOCKED/INSUFFICIENT_DATA/WATCH"
null_propagation_rule: "필수 입력이 null이면 해당 계산은 null로 유지하고 prohibited_calculations에 사유를 남긴다. null을 0으로 대체 금지."
output_requirement: "OUTPUT_VALIDATION에서 decision_trace 누락 시 schema_validation_status=FAIL."
states:
INPUT_VALIDATION:
purpose: "요청, 기준일, 계좌, 보유수량, 가격/수급/ATR 입력 존재 여부 확인"
required_refs:
- "spec/01_objective_profile.yaml:input_required"
- "spec/12_field_dictionary.yaml:field_dictionary"
- "spec/02_data_contract.yaml:quant_feed_contract"
required_inputs: ["request_scope", "analysis_date", "account_scope"]
computed_outputs: ["normalized_request", "field_alias_resolution_log"]
pass_condition: "minimum request context exists"
fail_state: "INSUFFICIENT_DATA"
DATA_COMPLETENESS_CHECK:
purpose: "데이터 상태 OK/PARTIAL/DATA_MISSING/DATA_CONFLICT/DATA_STALE 판정"
required_refs:
- "spec/02_data_contract.yaml:data_rule"
- "spec/02_data_contract.yaml:quant_feed_contract.data_completeness_gate"
- "spec/12_field_dictionary.yaml:field_dictionary.policy"
required_inputs: ["normalized_request", "raw_data_sources"]
computed_outputs: ["data_completeness_matrix", "missing_fields", "field_unit_conflicts"]
pass_condition: "data_completeness_matrix produced"
fail_state: "INSUFFICIENT_DATA"
HARD_FILTER_CHECK:
purpose: "하드 필터를 점수보다 먼저 적용"
required_refs:
- "spec/08_scoring_rules.yaml:hard_filters"
- "spec/risk/aggregate_risk.yaml:risk_control.aggregate_risk_cap"
- "spec/risk/circuit_breakers.yaml:risk_control.weekly_circuit_breaker"
- "spec/13_formula_registry.yaml:formula_registry.formulas.TOTAL_HEAT_V1"
required_inputs: ["data_completeness_matrix", "account_snapshot", "total_asset"]
computed_outputs: ["hard_filter_results", "total_heat_pct", "blocked_actions"]
pass_condition: "no blocking hard filter for requested action"
fail_state: "BLOCKED"
MARKET_REGIME_CHECK:
purpose: "Risk-On/Neutral/Risk-Off 및 현금 목표 확인"
required_refs:
- "spec/risk/market_risk_cash.yaml:risk_control.market_risk_score_based_cash"
- "spec/strategy/entry_core.yaml:entry_timing_guardrails.regime_based_entry"
- "spec/13_formula_registry.yaml:formula_registry.formulas.MARKET_RISK_SCORE_V1"
- "spec/13_formula_registry.yaml:formula_registry.formulas.TARGET_CASH_PCT_V1"
required_inputs: ["vix_close", "kospi_close", "kospi_ma20", "usd_krw", "usd_jpy_2d_change_pct", "credit_stress_status"]
computed_outputs: ["market_risk_score", "target_cash_pct", "market_regime_state"]
pass_condition: "market regime classified or marked UNKNOWN"
fail_state: "INSUFFICIENT_DATA"
STRATEGY_SCORING:
purpose: "섹터/종목/수급/유동성/실적/밸류 점수 산출"
required_refs:
- "spec/08_scoring_rules.yaml:strategy_score"
- "spec/strategy/sector_model.yaml:sector_model"
- "spec/13_formula_registry.yaml:formula_registry.formulas.FLOW_CREDIT_V1"
required_inputs: ["close_price", "ma20", "flow_ok", "flow_rows", "frg_5d_sh", "inst_5d_sh", "avg_trade_value_5d"]
computed_outputs: ["flow_credit", "strategy_score", "component_scores", "grade_candidate"]
pass_condition: "strategy_score calculated or missing fields listed"
fail_state: "INSUFFICIENT_DATA"
PORTFOLIO_CONSTRAINT_CHECK:
purpose: "계좌·비중·중복노출·현금·납입한도 확인"
required_refs:
- "spec/10_portfolio_rules.yaml"
- "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework"
required_inputs: ["target_cash_pct", "available_cash", "total_asset", "current_exposures", "account_scope"]
computed_outputs: ["portfolio_fit_score", "cash_floor_status", "exposure_limit_amounts"]
pass_condition: "portfolio constraints pass or downgrade action selected"
fail_state: "BLOCKED"
cash_shortfall_resolution: # [2026-05-19_HARNESS_AUDIT_V1] H2
rule_id: "CSR001"
purpose: >
현금 < target_cash_pct 확인 시 BUY를 결정론적으로 차단하고
SELL_PRIORITY 엔진으로 TRIM 수량을 자동 배정한다. LLM 임의 배정 금지.
trigger_condition: "CASH_RATIOS_V1.current_cash_pct < TARGET_CASH_PCT_V1.target_cash_pct"
steps:
step_1_block_buy:
action: "모든 BUY 신호 → IGNORE 전환. 이유 불문, LLM 재해석 금지."
output_key: "buy_gate_status = CASH_BLOCKED"
step_2_calc_shortfall:
formula: "cash_shortfall_krw = max(0, (target_cash_pct/100 - current_cash_pct/100) × total_asset)"
source: "CASH_RATIOS_V1 결과 기반 — LLM 직접 계산 금지"
output_key: "cash_shortfall_krw"
step_3_assign_trim:
source: "sell_priority_engine (spec/risk/portfolio_exposure.yaml)"
algorithm: >
SELL_PRIORITY 1순위 종목부터 계단식 TRIM 수량 배정.
잔여 부족액이 남으면 2순위로 이동하며 반복.
formula: "trim_qty = min(floor(remaining_shortfall_krw / current_price), holding_quantity)"
cascade: "remaining_shortfall_krw -= trim_qty × current_price → 다음 순위"
output_tag: "[CASH_RAISE_AUTO: {종목} {qty}주 TRIM → 예상확보 {proceeds}원]"
step_4_output_required:
fields:
- "cash_shortfall_krw (부족액)"
- "trim_assignments: [{종목, qty, expected_proceeds}] (배정 목록)"
- "post_trim_cash_pct (TRIM 후 예상 현금비중)"
enforcement:
- "수동 배정·서사적 배정 금지 — 공식 결과만 기재"
- "trim_assignments 없이 현금 부족 해소 주장 금지"
- "QEH_AUDIT_BLOCK.SELL_PRIORITY_V1 행에 배정 결과 요약 필수"
- "1순위 소진 전 2순위 배정 금지 (sell_priority_engine 순서 준수)"
POSITION_SIZING:
purpose: "ATR20·현금·목표비중·유동성으로 정수 수량 산출"
required_refs:
- "spec/05_position_sizing.yaml:position_sizing"
- "spec/13_formula_registry.yaml:formula_registry.formulas.RISK_BUDGET_CASCADE_V1"
- "spec/13_formula_registry.yaml:formula_registry.formulas.POSITION_SIZE_V1"
required_inputs: ["total_asset", "atr20", "entry_price", "available_cash", "exposure_limit_amounts", "bayesian_confidence_multiplier"]
computed_outputs: ["final_risk_budget", "atr_quantity", "cash_limit_quantity", "target_weight_limit_quantity", "sector_limit_quantity", "liquidity_limit_quantity", "final_quantity"]
pass_condition: "integer quantity calculated, or NO_QUANTITY reason emitted"
fail_state: "INSUFFICIENT_DATA"
EXIT_POLICY_CHECK:
purpose: "손절/익절/trailing/보유주 점검 규칙 적용"
required_refs:
- "spec/exit/stop_loss.yaml:stop_loss"
- "spec/exit/take_profit.yaml:take_profit"
- "spec/exit/position_review.yaml:position_review_cycle"
- "spec/13_formula_registry.yaml:formula_registry.formulas.EXPECTED_EDGE_V1"
required_inputs: ["entry_price", "stop_price", "target_price", "final_quantity"]
computed_outputs: ["expected_edge", "stop_order", "take_profit_order", "invalidation_conditions"]
pass_condition: "exit or hold policy evaluated"
fail_state: "INSUFFICIENT_DATA"
proactive_exit_radar_check: # [2026-05-19_PROACTIVE_RADAR_V1]
purpose: >
보유 포지션 분석 시 항상 실행. 가격이 멀쩡할 때도 수급·유동성·섹터
신호로 선제 매도 적기를 감지한다. '매도가 예술이다' 원칙 구현.
required_refs:
- "spec/exit/proactive_exit_radar.yaml"
- "spec/13_formula_registry.yaml:DIVERGENCE_SCORE_V1"
- "spec/13_formula_registry.yaml:OVERHANG_PRESSURE_V1"
- "spec/13_formula_registry.yaml:SECTOR_ROTATION_RADAR_V1"
required_inputs:
- "frg_5d_sh, inst_5d_sh, flow_credit (W1)"
- "frg_5d_sh, frg_20d_sh, volume, avg_volume_5d (W2)"
- "sector_flow.SmartMoney_5D_Norm_Score, sector_flow.Rank (W3)"
computed_outputs:
- "divergence_score + W1_status"
- "overhang_score + W2_status"
- "W3_status"
- "radar_composite_status (PASS/CAUTION/ALERT/CRITICAL_ALERT)"
- "PROACTIVE_RADAR_BLOCK (보고서 출력용)"
hard_rules:
- "ALERT 이상 발화 시 sell_priority_engine 실행 필수"
- "CRITICAL_ALERT 시 코어 포지션 포함 전면 재검토 강제"
- "LLM이 레이더 결과를 완화하는 서사 출력 금지 (Section B 해설만 허용)"
- "RADAR_MISSING(데이터 부족) 시 soft-block: 보유 포지션 수동 점검 권고 문구 출력"
OUTPUT_VALIDATION:
purpose: "JSON Schema와 HTS 표 필드 검증"
required_refs:
- "schemas/output_schema.json"
- "spec/07_output_schema.yaml"
required_inputs: ["final_action", "orders", "scores", "position_sizing", "triggered_rules", "missing_data"]
computed_outputs: ["schema_validation_status", "prohibited_calculations"]
pass_condition: "all required output fields populated or prohibited_calculations filled"
fail_state: "INSUFFICIENT_DATA"
FINAL_DECISION:
purpose: "BUY/HOLD/SELL/TRIM/ROTATE/AVOID/WATCH/INSUFFICIENT_DATA 중 하나로 결론"
required_refs:
- "spec/07_output_schema.yaml:recommendation_grade"
output_required:
- "final_action"
- "grade"
- "orders or prohibited_calculations"
- "rules_used"
INSUFFICIENT_DATA:
purpose: "데이터 부족으로 산출 불가. 다음 확인 출처를 제시."
output_required:
- "missing_fields"
- "prohibited_calculations"
- "next_source_to_check"
BLOCKED:
purpose: "하드 필터 또는 리스크 정책으로 행동 차단."
output_required:
- "triggered_rules"
- "blocked_action"
- "allowed_alternative"
transitions:
- from: "INPUT_VALIDATION"
to: "DATA_COMPLETENESS_CHECK"
condition: "minimum request context exists"
- from: "INPUT_VALIDATION"
to: "INSUFFICIENT_DATA"
condition: "account/request context missing and cannot be inferred"
- from: "DATA_COMPLETENESS_CHECK"
to: "HARD_FILTER_CHECK"
condition: "data_completeness_matrix produced"
- from: "DATA_COMPLETENESS_CHECK"
to: "INSUFFICIENT_DATA"
condition: "matrix cannot be produced"
- from: "HARD_FILTER_CHECK"
to: "BLOCKED"
condition: "blocking hard_filter failed"
- from: "HARD_FILTER_CHECK"
to: "MARKET_REGIME_CHECK"
condition: "no blocking hard_filter failed"
- from: "MARKET_REGIME_CHECK"
to: "STRATEGY_SCORING"
condition: "regime classified or UNKNOWN with caution"
- from: "STRATEGY_SCORING"
to: "PORTFOLIO_CONSTRAINT_CHECK"
condition: "strategy score calculated or downgrade reason emitted"
- from: "PORTFOLIO_CONSTRAINT_CHECK"
to: "BLOCKED"
condition: "cash_floor, duplicate exposure, account limit, or Total_Heat blocks requested action"
- from: "PORTFOLIO_CONSTRAINT_CHECK"
to: "POSITION_SIZING"
condition: "requested action requires quantity"
- from: "PORTFOLIO_CONSTRAINT_CHECK"
to: "EXIT_POLICY_CHECK"
condition: "requested action is hold/trim/sell review"
- from: "POSITION_SIZING"
to: "EXIT_POLICY_CHECK"
condition: "quantity calculated or NO_QUANTITY reason emitted"
- from: "EXIT_POLICY_CHECK"
to: "OUTPUT_VALIDATION"
condition: "order/hold/watch decision prepared"
- from: "OUTPUT_VALIDATION"
to: "FINAL_DECISION"
condition: "schema and required output fields valid"
- from: "OUTPUT_VALIDATION"
to: "INSUFFICIENT_DATA"
condition: "required output fields missing without prohibited_calculations reason"
global_prohibitions:
- "HARD_FILTER_CHECK 이전에 BUY 결론 출력 금지"
- "POSITION_SIZING 이전에 정수 주문수량 출력 금지"
- "OUTPUT_VALIDATION 실패 상태에서 즉시 실행 플레이북 출력 금지"
- "BLOCKED 상태를 WATCH로 미화 금지. 차단 사유를 명시한다."