meta: title: "은퇴자산포트폴리오 — Final_Action 결정 매트릭스" version: "2026-05-17-action_matrix_v1" language: "ko-KR" role: "canonical" has_code_implementation: true code_path: ["spec/strategy/action_matrix.yaml"] purpose: > gas_data_feed.gs의 Allowed_Action(매수 게이트) + calcSellDecision_(매도) + calcFinalDecision_(통합)이 어떤 조건에서 어떤 Final_Action을 출력하는지 단일 진실 소스로 문서화한다. "왜 지금 매수/매도인가"의 패턴을 Action_Reason 컬럼과 함께 읽으면 된다. # ───────────────────────────────────────────────────────────────────────────── # canonical_fields # ───────────────────────────────────────────────────────────────────────────── canonical_fields: Final_Action: role: "외부 소비 canonical field — getDailyBrief, API, ChatGPT 모두 이 값을 참조" allowed_values: SELL_READY: "Sell_Validation=PASS — 즉시 HTS 주문 가능" SELL_CHECK_QTY: "Sell_Action 있으나 보유수량 미확인 — 사용자가 확인 후 주문" EXIT_SIGNAL: "Allowed_Action=EXIT_SIGNAL (RW>=3 또는 STOP_OR_TIME_EXIT_READY)" EXIT_REVIEW: "Allowed_Action=REVIEW_EXIT (RW=2 또는 EXIT_REVIEW)" BUY_STAGE1_READY: "SS001_Grade A + Entry_Mode_Gate=PASS + Timing=BUY_STAGE1_READY" BUY_BREAKOUT_PILOT_ONLY: "SS001_Grade A + 돌파 파일럿 진입 조건 충족" BUY_PULLBACK_WAIT: "SS001_Grade A/B + 눌림목 대기 (진입 타이밍 준비)" WATCH_TIMING_SETUP: "SS001_Grade A/B이지만 타이밍 미충족 — Allowed_Action=WATCH_CANDIDATE" NO_BUY_OVERHEATED: "과열 지표 발동 (AC_Gate=BLOCK 또는 Val_Surge 과도)" HOLD: "그 외 전체 — Allowed_Action(NO_ADD/HOLD/HOLD_NO_ADD/OBSERVE_ONLY)으로 세분" Action_Params: role: "실행 파라미터 압축 — 외부 소비자(getDailyBrief·API·ChatGPT)가 즉시 사용 가능한 실행 정보" format: SELL_READY: "{ratio}% {qty}주 @{price}원 | {executionWindow} | {orderType}" SELL_CHECK_QTY: "{sellAction} | 보유수량 미확인" EXIT_SIGNAL/EXIT_REVIEW: "보유수량 미확인 — HTS 확인 필요" BUY_*: "목표 {posSizeQty}주 | 손절 {stopPriceEst}원 | TP1 {tp1Price}원({tp1Qty}주)" WATCH_TIMING_SETUP: "대기 — {timingBlockReason}" HOLD/etc: "" # 빈 문자열 Action_Reason: role: "왜 이 Final_Action인가를 한 문자열로 요약 — 사람이 읽는 컬럼" format: SELL_READY: "{sellDetailLabel} {qty}주 @{limitPrice}원 [{sellReason}]" EXIT_SIGNAL/EXIT_REVIEW: "RW{n}({RW1+RW2...}) {exitSignalDetail}" BUY_*: "SS001:{grade}{normScore}점 RSI{rsi} 이격{disp}% FC{fc}" WATCH_TIMING_SETUP: "SS001:{grade}{normScore}점 타이밍미충족({timingBlockReason})" HOLD: "HeatBlock({heatPct}%) | {regime} | SS001:{grade}" NO_ADD: "수급이탈 | 거래대금{억}억 | 스프레드{pct}% | {regime}" HOLD_NO_ADD: "DART:{risk} | 과열({acGate})" OBSERVE_ONLY: "PRICE_MISSING({priceStatus})" Allowed_Action: role: "내부 계산 중간값 — 매수 게이트 판정. 외부 소비 시 Final_Action 우선." allowed_values: OBSERVE_ONLY: "가격 데이터 없음 — 모든 계산 불가" HOLD: "HF005 BLOCK, 레짐 차단, 또는 SS001_Grade C" NO_ADD: "수급이탈 / 거래대금 부족 / 스프레드 과도 / 레짐 차단(미보유)" HOLD_NO_ADD: "DART 리스크 또는 과열 게이트" EXIT_SIGNAL: "RW_Partial >= 3 또는 Timing_Action=STOP_OR_TIME_EXIT_READY" REVIEW_EXIT: "RW_Partial >= 2 또는 Timing_Action=EXIT_REVIEW" WATCH_CANDIDATE: "SS001_Grade A/B이지만 타이밍 미충족" BUY_STAGE1_READY: "SS001_Grade A + 타이밍 충족" BUY_BREAKOUT_PILOT_ONLY: "SS001_Grade A + 돌파 파일럿" BUY_PULLBACK_WAIT: "SS001_Grade A/B + 눌림목 대기" # ───────────────────────────────────────────────────────────────────────────── # 매수 패턴 매트릭스 # ───────────────────────────────────────────────────────────────────────────── buy_action_matrix: purpose: "SS001_Grade × Timing_Action × 제약 → Final_Action 결정 규칙" BUY_STAGE1_READY: required_all: - SS001_Grade: "A" - Timing_Action: "BUY_STAGE1_READY" - Entry_Mode_Gate: "PASS" blocked_if_any: - heatBlock: true # globalHeatPct >= 10% - isRiskOffRegime: true # RISK_OFF or RISK_OFF_CANDIDATE - dartRisk: true - liquidityFail: true # flow.ok=F or avgTV5D<50 or spread>0.8% caution_if: - heatCaution: true # 7~10% → Pos_Size_Qty × 0.5 & Action_Reason에 표기 action_reason_template: "SS001:A{score}점 RSI{rsi} 이격{disp}% FC{fc}" BUY_BREAKOUT_PILOT_ONLY: required_all: - SS001_Grade: "A" - Timing_Action: "BUY_BREAKOUT_PILOT_ONLY" - Entry_Mode_Gate: "PASS" blocked_if_any: [heatBlock, isRiskOffRegime, dartRisk, liquidityFail] note: "돌파 파일럿 — 전체 수량의 30~50%만 진입. Entry_Mode=BREAKOUT." BUY_PULLBACK_WAIT: required_any: - {SS001_Grade: "A", Timing_Action: "BUY_PULLBACK_WAIT"} - {SS001_Grade: "B", Timing_Action: "BUY_PULLBACK_WAIT"} blocked_if_any: [heatBlock, isRiskOffRegime, dartRisk, liquidityFail] note: "눌림목 대기 — 진입 타이밍 준비 중. 지정가 주문 미리 설정 권장." WATCH_TIMING_SETUP: required_all: - SS001_Grade: ["A", "B"] timing_condition: "Timing_Action not in [BUY_STAGE1_READY, BUY_BREAKOUT_PILOT_ONLY, BUY_PULLBACK_WAIT]" note: "등급은 되지만 타이밍 미충족. Action_Reason에 구체적 미충족 이유 표기." # ───────────────────────────────────────────────────────────────────────────── # 매도 패턴 매트릭스 # ───────────────────────────────────────────────────────────────────────────── sell_action_matrix: purpose: "매도 신호 발생 시 Final_Action 결정 규칙. spec/exit/stop_loss.yaml sell_signal_priority와 연동." SELL_READY: trigger: "calcSellDecision_()의 Sell_Validation = PASS" substates: EXIT_100: "손절전량 — STOP_OR_TIME_EXIT_READY 또는 RW_Partial >= 4" REGIME_TRIM_50: "레짐 50% 축소 — getDailyBrief 포트폴리오 경고로 이동 (방향 A: 개별 종목 신호 아님)" TRIM_70: "RW청산 70% — RW_Partial >= 3 또는 Timing_Exit_Score >= 75" TRAILING_STOP_BREACH: "트레일링이탈 70% — close <= trailing_stop_price 직접 체크" TRIM_50: "RW부분 50% — RW_Partial >= 2 OR (RW_Partial >= 1 AND Timing_Exit_Score >= 50). RW_Partial=0 단독 기술지표로는 TRIM_50 불가." PROFIT_TRIM_50/35/25: "익절 사다리 — Profit_Pct >= 50/30/20" TAKE_PROFIT_TIER1: "TP1 익절 25% — Profit_Pct >= 10" TIME_EXIT_100: "타임스탑 전량 — Days_To_Time_Stop <= 0 (spec priority 6)" TIME_TRIM_50: "타임스탑 50% — Days_To_Time_Stop <= 7 (spec priority 6)" canonical_price_field: Sell_Limit_Price canonical_qty_field: Sell_Qty action_reason_template: "{label} {qty}주 @{price}원 [{reason}]" note: > 복수 조건 동시 발동 시 SL003_PRIORITY_MATRIX 적용: Sell_Limit_Price = max(모든 발동 조건의 후보가격). priceSource=PRIORITY_MATRIX_MAX. EXIT_SIGNAL: trigger: "Sell_Validation != PASS AND (RW_Partial >= 3 OR Timing_Action=STOP_OR_TIME_EXIT_READY)" action_reason_template: "RW{n}({items}) {exitSignalDetail}" note: "보유수량 미확인 상태. 사용자가 HTS에서 보유수량 확인 후 주문." EXIT_REVIEW: trigger: "RW_Partial >= 2 OR Timing_Action=EXIT_REVIEW" action_reason_template: "RW{n}({items}) 검토" note: "매도 검토 단계. 다음 영업일 재확인 권장." # ───────────────────────────────────────────────────────────────────────────── # Action_Priority 우선순위 숫자 (calcFinalDecision_ 기준) # ───────────────────────────────────────────────────────────────────────────── action_priority_table: 10: SELL_READY 20: SELL_CHECK_QTY 28: EXIT_SIGNAL 32: EXIT_REVIEW 50: NO_BUY_OVERHEATED 60: BUY_STAGE1_READY 70: BUY_BREAKOUT_PILOT_ONLY 80: BUY_PULLBACK_WAIT 90: WATCH_TIMING_SETUP 99: HOLD note: "낮을수록 우선순위 높음. Final_Rank는 Priority_Score 기준 내림차순 정렬 후 부여." # ───────────────────────────────────────────────────────────────────────────── # 브리핑 출력 형식 (getDailyBrief — C-1 재구조화) # ───────────────────────────────────────────────────────────────────────────── brief_format: canonical_source: "Final_Action (not Allowed_Action)" sort_within_group: "Final_Rank 오름차순 (Priority_Score 기반)" sections_order: 1: "SELL_READY — 즉시 HTS 주문 가능" 2: "EXIT_SIGNAL / EXIT_REVIEW — 보유수량 확인 후 매도" 3: "BUY — 진입 조건 충족 (BUY_STAGE1_READY / BUY_BREAKOUT_PILOT_ONLY / BUY_PULLBACK_WAIT)" 4: "WATCH — 타이밍 대기 (WATCH_TIMING_SETUP)" 5: "HOLD / BLOCK — Allowed_Action으로 세분 표시" dedup_rule: > 같은 종목이 SELL_READY이면서 EXIT_SIGNAL도 발생할 수 있다. Final_Action=SELL_READY가 최우선 — SELL_READY 섹션에만 출력. action_reason_display: "각 종목 한 줄에 Action_Reason 출력 → '왜'를 즉시 파악 가능"