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,206 @@
|
||||
"""build_performance_monitoring_dashboard_v1.py — PERFORMANCE_MONITORING_DASHBOARD_V1
|
||||
|
||||
모든 핵심 지표(스코어·라우팅·매도·예측·완료기준·달성률)를 단일 JSON으로 집계하는
|
||||
성과 모니터링 대시보드.
|
||||
|
||||
산출물: Temp/performance_monitoring_dashboard_v1.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
TEMP = ROOT / "Temp"
|
||||
DEFAULT_JSON = ROOT / "GatherTradingData.json"
|
||||
DEFAULT_OUT = TEMP / "performance_monitoring_dashboard_v1.json"
|
||||
FORMULA_ID = "PERFORMANCE_MONITORING_DASHBOARD_V1"
|
||||
NA = "not_available"
|
||||
|
||||
|
||||
def _load(path: Path) -> Any:
|
||||
if not path.exists():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _f(v: Any, default: float | None = None) -> float | None:
|
||||
try:
|
||||
return float(v)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
|
||||
def _extract_harness(payload: Any) -> dict[str, Any]:
|
||||
if not isinstance(payload, dict):
|
||||
return {}
|
||||
h = payload.get("hApex")
|
||||
dc = (payload.get("data") or {}).get("_harness_context")
|
||||
if isinstance(h, dict) and isinstance(dc, dict):
|
||||
m = dict(dc); m.update(h); return m
|
||||
return h if isinstance(h, dict) else dc if isinstance(dc, dict) else payload
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--json", default=str(DEFAULT_JSON))
|
||||
ap.add_argument("--out", default=str(DEFAULT_OUT))
|
||||
args = ap.parse_args()
|
||||
|
||||
json_path = Path(args.json); json_path = json_path if json_path.is_absolute() else ROOT / json_path
|
||||
out_path = Path(args.out); out_path = out_path if out_path.is_absolute() else ROOT / args.out
|
||||
|
||||
payload = _load(json_path)
|
||||
harness = _extract_harness(payload)
|
||||
|
||||
# 하네스 출력 로드
|
||||
audit = _load(TEMP / "engine_audit_v1.json")
|
||||
truth = _load(TEMP / "operational_truth_score_v1.json")
|
||||
gap = _load(TEMP / "completion_gap_v1.json")
|
||||
scores = _load(TEMP / "scores_harness_v1.json")
|
||||
routing = _load(TEMP / "strategy_routing_audit_v1.json")
|
||||
sell = _load(TEMP / "sell_engine_audit_v1.json")
|
||||
pred = _load(TEMP / "prediction_accuracy_harness_v2.json")
|
||||
perf = _load(TEMP / "realized_performance_v1.json")
|
||||
horizon_plan = _load(TEMP / "horizon_rebalance_plan_v1.json")
|
||||
ycc = _load(TEMP / "yaml_code_coverage_v1.json")
|
||||
exp = (audit.get("imputed_data_exposure") or {})
|
||||
fv = (audit.get("final_verdict") or {})
|
||||
|
||||
# 핵심 지표 집계
|
||||
dashboard = {
|
||||
"formula_id": FORMULA_ID,
|
||||
"as_of_date": date.today().isoformat(),
|
||||
|
||||
# 1. 엔진 상태 요약
|
||||
"engine_status": {
|
||||
"audit_status": fv.get("status"),
|
||||
"investment_decision_allowed": fv.get("investment_decision_allowed"),
|
||||
"global_execution_gate": (audit.get("decision") or {}).get("global_execution_gate"),
|
||||
"decision_source": "deterministic_rule_engine",
|
||||
},
|
||||
|
||||
# 2. spec/30 달성 현황
|
||||
"spec30": {
|
||||
"pass_rate_pct": gap.get("pass_rate_pct"),
|
||||
"passed": gap.get("passed_count"),
|
||||
"total": gap.get("total_criteria"),
|
||||
"immediate_actions": gap.get("immediate_actions", []),
|
||||
"immediate_count": len(gap.get("immediate_actions") or []),
|
||||
},
|
||||
|
||||
# 3. 스코어 현황 (SCORES_HARNESS_V1)
|
||||
"scores": {
|
||||
"fundamental": (scores.get("scores") or {}).get("fundamental_score"),
|
||||
"smart_money": (scores.get("scores") or {}).get("smart_money_score"),
|
||||
"liquidity": (scores.get("scores") or {}).get("liquidity_score"),
|
||||
"momentum": (scores.get("scores") or {}).get("momentum_score"),
|
||||
"valuation": (scores.get("scores") or {}).get("valuation_score"),
|
||||
"risk": (scores.get("scores") or {}).get("risk_score"),
|
||||
"final": (scores.get("final_score") or {}).get("value"),
|
||||
"dominant_horizon": scores.get("dominant_horizon"),
|
||||
"action_implied": "sell/partial_sell" if (_f((scores.get("final_score") or {}).get("value"), 100) or 100) < 45 else "hold",
|
||||
},
|
||||
|
||||
# 4. 신뢰도 캡 정직성
|
||||
"confidence": {
|
||||
"raw_cap": exp.get("raw_confidence_cap_basis"),
|
||||
"honest_cap": exp.get("effective_confidence_honest"),
|
||||
"inflation_gap": exp.get("confidence_cap_inflation_gap"),
|
||||
"imputed_field_ratio": exp.get("imputed_field_ratio"),
|
||||
"gate": exp.get("gate_status"),
|
||||
"long_horizon_allowed": exp.get("long_horizon_allowed"),
|
||||
"fundamental_claim_allowed": exp.get("fundamental_claim_allowed"),
|
||||
},
|
||||
|
||||
# 5. 라우팅 현황 (STRATEGY_ROUTING_AUDIT_V1)
|
||||
"routing": {
|
||||
"selected_horizon": routing.get("selected_horizon"),
|
||||
"current_short_pct": horizon_plan.get("current_short_pct"),
|
||||
"short_cap_pct": horizon_plan.get("short_cap_pct"),
|
||||
"excess_pct": horizon_plan.get("excess_pct"),
|
||||
"routing_confidence": routing.get("routing_confidence"),
|
||||
"horizon_conflict_count": routing.get("horizon_conflict_count"),
|
||||
"gate": routing.get("gate"),
|
||||
},
|
||||
|
||||
# 6. 매도 현황 (SELL_ENGINE_AUDIT_V1)
|
||||
"sell_engine": {
|
||||
"gate": sell.get("gate"),
|
||||
"sell_type_counts": sell.get("sell_type_counts"),
|
||||
"breach_tickers": sell.get("breach_tickers"),
|
||||
"cash_shortfall_min_krw": (sell.get("scr_plan") or {}).get("cash_shortfall_min_krw"),
|
||||
"execution_allowed": (sell.get("scr_plan") or {}).get("execution_allowed"),
|
||||
},
|
||||
|
||||
# 7. 예측 정확도 (PREDICTION_ACCURACY_HARNESS_V2)
|
||||
"prediction": {
|
||||
"t1_op_rate": pred.get("t1_op_rate"),
|
||||
"t5_op_rate": pred.get("t5_op_rate"),
|
||||
"t20_op_rate": pred.get("t20_op_rate"),
|
||||
"t20_replay_rate": pred.get("t20_replay_rate"),
|
||||
"calibration_state": pred.get("calibration_state"),
|
||||
"replay_calibration_state": pred.get("replay_calibration_state"),
|
||||
},
|
||||
|
||||
# 8. 커버리지 (YAML_TO_CODE_COVERAGE_V1)
|
||||
"coverage": {
|
||||
"yaml_to_code_ratio": _f(ycc.get("coverage_ratio")),
|
||||
"golden_test_ratio": _f(ycc.get("golden_coverage_ratio")),
|
||||
"golden_test_count": ycc.get("golden_test_count"),
|
||||
"yaml_formula_count": ycc.get("yaml_formula_count"),
|
||||
"behavioral_coverage_pct": (audit.get("audit") or {}).get("behavioral_coverage_pct"),
|
||||
},
|
||||
|
||||
# 9. 운영 진실성 점수 (OPERATIONAL_TRUTH_SCORE_V1)
|
||||
"truth_score": {
|
||||
"score_0_100": truth.get("score_0_100"),
|
||||
"gate": truth.get("gate"),
|
||||
"data_truth_score": truth.get("data_truth_score"),
|
||||
"decision_truth_score": truth.get("decision_truth_score"),
|
||||
"performance_readiness_score": truth.get("performance_readiness_score"),
|
||||
"report_consistency_score": truth.get("report_consistency_score"),
|
||||
},
|
||||
|
||||
# 10. 은퇴 목표 달성 현황
|
||||
"retirement_goal": {
|
||||
"total_asset_krw": harness.get("total_asset_krw"),
|
||||
"goal_achievement_pct": harness.get("goal_achievement_pct"),
|
||||
"goal_remaining_krw": harness.get("goal_remaining_krw"),
|
||||
"goal_status": harness.get("goal_status"),
|
||||
},
|
||||
|
||||
# 11. 즉시 실행 가능한 개선 요약
|
||||
"immediate_improvement_summary": {
|
||||
"item_count": len(gap.get("immediate_actions") or []),
|
||||
"actions": (gap.get("priority_roadmap") or {}).get("P1_immediately", []),
|
||||
"estimated_effect": (
|
||||
"GAS JSON 내보내기 후: schema_presence SLA 해소, "
|
||||
"fundamentals 로드 시 honest_cap 48.4→65+, "
|
||||
"BREACH 4종목 정리 시 routing_gate 개선 가능"
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
out_path.write_text(json.dumps(dashboard, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
spec30 = dashboard["spec30"]
|
||||
scores_d = dashboard["scores"]
|
||||
print(
|
||||
f"[{FORMULA_ID}] "
|
||||
f"spec30={spec30['passed']}/{spec30['total']}({spec30['pass_rate_pct']}%) "
|
||||
f"final_score={scores_d['final']} "
|
||||
f"breach={len(dashboard['sell_engine'].get('breach_tickers') or [])} "
|
||||
f"honest_cap={dashboard['confidence']['honest_cap']} -> {out_path}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user