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>
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
meta:
|
||||
title: "은퇴자산포트폴리오 — 포트폴리오 노출·현금 정책 분할 후보"
|
||||
parent_file: "spec/03_risk_policy.yaml"
|
||||
version: "2026-05-16-F9_secular_leader"
|
||||
language: "ko-KR"
|
||||
timezone: "Asia/Seoul"
|
||||
role: "canonical"
|
||||
migration_status: "canonical_split_active"
|
||||
authority_rule: "이 split 파일이 해당 섹션의 canonical source다. parent_file은 legacy compatibility index다."
|
||||
|
||||
|
||||
factor_risk_management:
|
||||
factor_risk_limit:
|
||||
purpose: >
|
||||
포트폴리오 전체의 팩터 집중도를 수치로 통제한다.
|
||||
개별 종목 리스크 게이트(Total_Heat, stop_loss)와 직교하는
|
||||
포트폴리오 레벨 팩터 가드레일이다.
|
||||
portfolio_beta:
|
||||
formula_ref: "spec/13_formula_registry.yaml:formula_registry.formulas.PORTFOLIO_BETA_V1"
|
||||
warning_threshold: 1.2
|
||||
hard_cap: 1.3
|
||||
action_warning: >
|
||||
PORTFOLIO_BETA >= 1.2 → 보고서에 [팩터경보: 포트폴리오 베타 상승] 출력.
|
||||
신규 위성 매수 시 고베타(Beta >= 1.5) 종목 추가 검토 신중.
|
||||
action_hard_cap: >
|
||||
PORTFOLIO_BETA >= 1.3 → 고베타(Beta >= 1.5) 신규 위성 매수 보류.
|
||||
기존 고베타 위성은 RW 점수 우선 재점검.
|
||||
missing_policy: "Beta 미확인 종목 제외 후 산출. 제외 비중 > 30% 시 PARTIAL 표기."
|
||||
single_stock_beta:
|
||||
high_beta_threshold: 1.5
|
||||
treatment: >
|
||||
Beta >= 1.5 종목은 trailing_stop ATR 배수 1.8배 적용
|
||||
(take_profit.trailing_stop.high_beta_leader 준용).
|
||||
very_high_beta_threshold: 2.0
|
||||
action_very_high: >
|
||||
Beta >= 2.0 위성은 최대 보유 기간 20거래일 이내로 제한.
|
||||
time_stop.satellite 기준 단축 적용.
|
||||
momentum_concentration:
|
||||
definition: "직전 20거래일 수익률(Ret20D) 기준 상위 2개 종목의 포트폴리오 비중 합산"
|
||||
warning_threshold: "상위 2종목 합산 비중 >= 50%"
|
||||
hard_cap: "상위 2종목 합산 비중 >= 60%"
|
||||
action_warning: "보고서에 [모멘텀 집중 경보] 출력. 신규 고모멘텀 종목 추가 자제."
|
||||
action_hard_cap: "상위 종목 중 약한 쪽(RW 점수 높은 쪽)의 비중 5%p 축소 검토."
|
||||
sector_beta_concentration:
|
||||
definition: "단일 섹터에 Beta >= 1.5 종목이 2개 이상이고 합산 비중 >= 25%"
|
||||
action: "해당 섹터 고베타 중 RW 점수 낮은 종목 먼저 비중 조정."
|
||||
calculation_frequency: "주간 정기점검(수요일) 및 신규 매수 직전"
|
||||
output_table:
|
||||
columns: ["항목", "현재값", "경보임계치", "하드캡", "상태", "조치"]
|
||||
rows:
|
||||
- ["포트폴리오 베타", "[PORTFOLIO_BETA_V1]", "1.2", "1.3", "[OK/경보/차단]", ""]
|
||||
- ["모멘텀 집중도(상위2종목비중)", "[산출]", "50%", "60%", "[OK/경보/차단]", ""]
|
||||
- ["섹터 고베타 집중", "[산출]", "—", "25%", "[OK/차단]", ""]
|
||||
prohibition:
|
||||
- "PORTFOLIO_BETA 미산출 상태에서 고베타 신규 위성 추가 허용 금지"
|
||||
- "Beta 미확인을 이유로 팩터 리스크 점검 전체를 생략 금지 (부분 산출 PARTIAL로 진행)"
|
||||
- "팩터 과집중 경보를 이유로 core 주도주(삼성전자·SK하이닉스) 우선 축소 금지"
|
||||
|
||||
|
||||
position_count_limit:
|
||||
id: "PCL_PORTFOLIO_EXPOSURE_ENFORCEMENT"
|
||||
canonical_ref: "spec/01_objective_profile.yaml:position_count_limit"
|
||||
purpose: >
|
||||
포트폴리오 노출 레이어에서 계좌별 종목 수 상한을 집행한다.
|
||||
계좌 성격이 다르므로 통합 합산 한도는 두지 않는다.
|
||||
연금저축은 ETF 전용 계좌 — 개별주 카운트 완전 제외.
|
||||
counting_rule:
|
||||
include: "개별주 직접 보유 (코어 + 위성)"
|
||||
exclude: ["ETF", "국내상장 해외ETF", "MMF", "RP", "단기채 ETF", "현금성 상품"]
|
||||
pension_scope: "연금저축 전체 제외 — ETF 전용 계좌"
|
||||
enforcement:
|
||||
PCL001_TAXABLE_HARD_BLOCK:
|
||||
condition: "taxable_individual_count >= 10"
|
||||
action: "ROTATE_REQUIRED (일반계좌)"
|
||||
output: >
|
||||
일반계좌 개별주 10종목 도달. 신규 매수 제안 시 교체 후보 1종목 이상 명시.
|
||||
교체 후보 선정 기준: financial_health_score 최저 → RW 점수 최고 → 보유 기간 최장.
|
||||
PCL002_TAXABLE_CAUTION:
|
||||
condition: "taxable_individual_count == 9"
|
||||
action: "CAUTION_FLAG (일반계좌)"
|
||||
output: "일반계좌 9종목. 신규 매수 전 포트폴리오 전체 검토 권고."
|
||||
PCL003_ISA_HARD_BLOCK:
|
||||
condition: "isa_individual_count >= 4"
|
||||
action: "ROTATE_REQUIRED (ISA)"
|
||||
output: "ISA 개별주 4종목 도달. 신규 매수 시 교체 후보 명시."
|
||||
PCL004_ISA_CAUTION:
|
||||
condition: "isa_individual_count == 3"
|
||||
action: "CAUTION_FLAG (ISA)"
|
||||
output: "ISA 3종목. 추가 진입 전 검토 권고."
|
||||
PCL005_SATELLITE_ROTATION_REVIEW:
|
||||
condition: "satellite_count >= 3 AND new_buy_target_bucket == satellite"
|
||||
action: "SATELLITE_ROTATION_REVIEW"
|
||||
output: >
|
||||
위성 3종목 이상 보유 중 추가 위성 진입 검토 시
|
||||
financial_health_score 가장 낮은 기존 위성 종목을 교체 후보로 우선 표시.
|
||||
output_table:
|
||||
columns: ["계좌", "현재 개별주 수", "경보", "하드차단", "상태"]
|
||||
rows:
|
||||
- ["일반계좌", "[taxable_individual_count]", "9종목", "10종목 이상", "[PASS/CAUTION/ROTATE_REQUIRED]"]
|
||||
- ["ISA", "[isa_individual_count]", "3종목", "4종목 이상", "[PASS/CAUTION/ROTATE_REQUIRED]"]
|
||||
- ["연금저축", "카운트 제외", "—", "—", "ETF 전용"]
|
||||
prohibition:
|
||||
- "PCL 미집계 상태에서 신규 매수 수량 출력 금지"
|
||||
- "ETF를 개별주 카운트에 포함해 한도 여유 있는 것처럼 계산 금지"
|
||||
- "연금저축 ETF를 개별주로 오분류해 카운트 금지"
|
||||
- "종목 수 초과를 이유로 손절 기준 미도달 종목 강제 매도 금지"
|
||||
|
||||
tactical_cash_buffer:
|
||||
amount: "총자산의 5% 고정 (방어용 cash_floor와 완전 별개)"
|
||||
purpose: "VIX 30 이상 돌파 또는 KOSPI 1일 -4% 이상 폭락 시에만 집행 가능한 긴급 실탄"
|
||||
activation_trigger:
|
||||
vix_shock: "VIX >= 30 돌파 확인 (macro 탭 VIX_Close 기준)"
|
||||
kospi_crash: "KOSPI 전일 대비 Ret1D <= -4%"
|
||||
execution_rule:
|
||||
- "A등급 주도주 중 낙폭 과대 종목 한정"
|
||||
- "1~2회 분할 지정가 투입"
|
||||
- "cash_floor 충족 상태에서만 사용 가능"
|
||||
prohibition:
|
||||
- "일상적 물타기·평단 낮추기 용도 사용 금지"
|
||||
- "B등급 이하 또는 관찰 중 종목 사용 금지"
|
||||
- "activation_trigger 미발동 시 사용 금지"
|
||||
- "cash_floor와 합산하여 운용 금지. 버퍼는 별도 현금 계선으로 관리."
|
||||
|
||||
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"
|
||||
rules:
|
||||
- id: "PE001_CASH_RATIOS"
|
||||
inputs: ["immediate_cash", "settlement_cash", "reserved_order_amount", "planned_buy_amount", "sell_cash_proceeds_immediate", "total_asset"]
|
||||
formula_ref: "CASH_RATIOS_V1"
|
||||
output_fields: ["immediate_cash_ratio", "settlement_cash_ratio", "buy_power_cash", "buy_power_ratio", "post_trade_immediate_cash_ratio"]
|
||||
on_missing: "NO_CASH_CHECK"
|
||||
- id: "PE002_CASH_FLOOR_GATE"
|
||||
inputs: ["post_trade_immediate_cash_ratio", "min_cash_ratio", "buy_power_ratio"]
|
||||
rules:
|
||||
- {if: "post_trade_immediate_cash_ratio < min_cash_ratio", action: "BUY_BLOCKED_TRIM_REQUIRED"}
|
||||
- {if: "post_trade_immediate_cash_ratio >= min_cash_ratio", action: "CASH_GATE_PASS"}
|
||||
output_field: "cash_floor_status"
|
||||
on_missing: "BUY_BLOCKED_NO_CASH_CHECK"
|
||||
- id: "PE003_TARGET_BUCKET_BAND"
|
||||
inputs: ["current_weight_pct", "target_band_min_pct", "target_band_max_pct"]
|
||||
formula_ref: "PORTFOLIO_BAND_STATUS_V1"
|
||||
output_field: "portfolio_band_status"
|
||||
on_missing: "DATA_MISSING_NO_ADD_TRIM_DECISION"
|
||||
- id: "PE004_DUPLICATE_EXPOSURE"
|
||||
inputs: ["same_sector_etf_weight_pct", "single_stock_sector_weight_pct", "etf_purity_ratio"]
|
||||
derived_field:
|
||||
duplicate_exposure_pct: "same_sector_etf_weight_pct * etf_purity_ratio + single_stock_sector_weight_pct"
|
||||
rules:
|
||||
- {if: "duplicate_exposure_pct >= 20", action: "ETF_STAGED_REDUCTION_REQUIRED"}
|
||||
- {if: "duplicate_exposure_pct < 20", action: "DUPLICATE_EXPOSURE_PASS"}
|
||||
output_field: "duplicate_exposure_status"
|
||||
on_missing: "DATA_MISSING_REVIEW"
|
||||
Reference in New Issue
Block a user