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:
2026-06-13 13:20:14 +09:00
commit ee3e799de1
1474 changed files with 176087 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
# spec/28 — 대체데이터 노출 게이트 계약 (IMPUTED_DATA_EXPOSURE_GATE_V1)
#
# 목적: 거버넌스/truth 점수(schema_presence, investment_quality, confidence_cap_basis)는
# 높지만 그 분모가 "스키마 존재율"에 치우쳐 실질 입력의 대체(imputed)·합성·PENDING을
# 가릴 수 있다. 이 게이트는 실질 데이터 커버리지를 결정론적으로 측정해 정직 신뢰도 캡을
# 재산출하고, 대체데이터 감지 시 장기·펀더멘털 단정을 차단한다.
#
# 위치: ENGINE_AUDIT_V1 감사 산출물(Temp/engine_audit_v1.json) 전용.
# GAS 런타임/HTS 주문 판단에는 개입하지 않는다(감사·진단 계층).
# 우선순위: 리스크 정책(spec/03) 하위. 본 계약은 감사 가시성 계약이며 주문을 생성하지 않는다.
meta:
formula_id: IMPUTED_DATA_EXPOSURE_GATE_V1
audit_id: ENGINE_AUDIT_V1
version: "2026-05-31_ENGINE_AUDIT_V1"
python_tool: tools/build_engine_audit_v1.py
validator_tool: tools/validate_engine_audit_v1.py
canonical_ref: spec/13b_harness_formulas.yaml:IMPUTED_DATA_EXPOSURE_GATE_V1
llm_role: explanation_only # 게이트 산출값은 LLM 재계산·완화 금지
# ── 실질 데이터 도메인 (가중치 합 = 1.0) ───────────────────────────────
domains:
fundamental_core:
weight: 0.30
coverage_source: "fundamental_multifactor_v3.json:rows[non-ETF].breakdown{roe,opm,ocf,fcf}"
coverage_formula: "present_core_factors / (4 × non_etf_ticker_count)"
note: ROE/OPM/OCF/FCF 결측(PARTIAL)이면 coverage↓. 장기·펀더멘털 우위 판단의 핵심.
realized_outcome:
weight: 0.30
coverage_source: "prediction_accuracy_harness_v2.json:{t1_sample,t5_sample,t20_sample}"
coverage_formula: "windows_with_sample / 3"
note: T+20 실현 표본 0건이면 장기 예측 미검증.
trade_quality:
weight: 0.15
coverage_source: "data_quality_gate_v2_py.json:category_scores.trade_quality"
coverage_formula: "score/100 (PENDING → 0)"
pattern:
weight: 0.10
coverage_source: "data_quality_gate_v2_py.json:category_scores.pattern"
coverage_formula: "score/100 (PENDING → 0)"
alpha_eval:
weight: 0.15
coverage_source: "data_quality_gate_v2_py.json:category_scores.alpha_eval"
coverage_formula: "score/100 (PENDING → 0)"
# ── 산식 ────────────────────────────────────────────────────────────────
formulas:
weighted_coverage: "Σ(domain.weight × domain.coverage)"
imputed_field_ratio: "1 weighted_coverage"
imputed_domain_ratio: "count(domain.coverage < 0.5) / domain_count"
fundamental_core_factor_coverage: "domains.fundamental_core.coverage"
surrogate_outcome_ratio: "1 domains.realized_outcome.coverage"
# 시스템 자체 캡 공식 재사용, 분모만 정직 커버리지로 교체
effective_confidence_honest: "raw_confidence_cap_basis × (0.4 + 0.6 × weighted_coverage)"
confidence_cap_inflation_gap: "raw_confidence_cap_basis effective_confidence_honest"
# ── 임계값 ──────────────────────────────────────────────────────────────
thresholds:
block_ratio: 0.50 # imputed_field_ratio ≥ 0.50 → IMPUTED_DATA_BLOCK
warn_ratio: 0.25 # ≥ 0.25 → IMPUTED_DATA_WARN
fund_factor_min_coverage: 0.50 # 미만이면 fundamental_claim_allowed=false
render_skew_pct: 10.0 # 렌더 보고서 값 vs 권위 JSON 차이 임계(%)
# ── 게이트 출력 계약 ────────────────────────────────────────────────────
output:
target: Temp/engine_audit_v1.json
block: imputed_data_exposure
fields:
- gate_status # PASS | IMPUTED_DATA_WARN | IMPUTED_DATA_BLOCK
- imputed_field_ratio
- imputed_domain_ratio
- weighted_coverage
- domain_coverage
- fundamental_core_factor_coverage
- fundamental_missing_ratio
- surrogate_outcome_ratio
- raw_confidence_cap_basis
- effective_confidence_honest
- confidence_cap_inflation_gap
- long_horizon_allowed # t20_sample>0 AND fundamental_core_factor_coverage≥0.5
- fundamental_claim_allowed # fundamental_core_factor_coverage≥0.5
- report_render_skew # 렌더 보고서 vs 권위 JSON 불일치 감지
- exposure_reasons
# ── 마스킹 금지 규칙 (RAW_VS_ADJUSTED_DISCLOSURE_V1) ─────────────────────
masking_rules:
RAW_VS_ADJUSTED_DISCLOSURE_V1:
formula_id: RAW_VS_ADJUSTED_DISCLOSURE_V1
rationale: >
raw_value_damage=15.7%가 adjusted=0.0%로 가려진 채 게이트에 들어가면
가치훼손 캡(10%)이 무력화된다.
enforcement:
- "게이트 입력(value_damage_cap 등)은 항상 raw_* 값을 사용한다. adjusted_*는 게이트 입력 금지"
- "보고서 표에 adjusted를 표시할 경우 같은 행/셀에 raw를 의무 병기: 'raw 15.7% / adj 0.0%'"
- "raw 병기 없는 adjusted 단독 표시 1건당 masked_metric_without_raw_count += 1"
detection:
fields_to_check:
- {raw: raw_value_damage_pct_avg, adjusted: adjusted_value_damage_pct_avg,
source: smart_cash_recovery_v8.json}
- {raw: raw_value_damage_pct, adjusted: adjusted_value_damage_pct,
source: value_preservation_scorer_v2.json}
output_metric: operational_report.json.summary.masked_metric_without_raw_count
acceptance: "masked_metric_without_raw_count == 0"
python_tool: tools/build_value_preservation_scorer_v2.py
gs_coverage: "gas_lib.gs:formatRawAdjustedPair_()"
validator: "tools/validate_value_damage_reconciliation_v1.py --raw-required"
# ── 금지 사항 ───────────────────────────────────────────────────────────
prohibitions:
- "LLM이 gate_status / effective_confidence_honest / coverage 를 재계산·완화하는 것 금지(HS011)"
- "fundamental_claim_allowed=false 인데 펀더멘털·장기 우위를 단정하는 서술 금지"
- "long_horizon_allowed=false 인데 POSITION(장기) 신규 진입을 정당화하는 서술 금지"
- "report_render_skew.skew_detected=true 를 무시하고 렌더 보고서 값을 권위값으로 인용 금지"
# ── 검증 (validate_engine_audit_v1.py) ─────────────────────────────────
validation:
default_mode: "산출물 무결성(스키마·불변식·산식 재현). 엔진 status=failed 여도 PASS 가능."
strict_mode: "추가로 final_verdict.status == passed 요구 → 미달 시 비0 종료."
invariants:
- "fundamental_core_factor_coverage < fund_factor_min_coverage → fundamental_claim_allowed == false"
- "imputed_field_ratio ≥ block_ratio → gate_status == IMPUTED_DATA_BLOCK"
- "decision.decision_source == rule_engine"
- "llm_control.final_decision_from_llm == false AND llm_generated_decision_field_count == 0"
- "weighted_coverage / imputed_field_ratio / effective_confidence_honest 재계산 일치(±오차)"