Files
QuantEngineByItz/spec/10_portfolio_rules.yaml
T

171 lines
7.8 KiB
YAML

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:
- "계좌 한도·현금·보유수량 미확인 상태에서 최종 주문수량 출력 금지"
- "포트폴리오 제약 실패를 종목 점수로 상쇄 금지"