ee3e799de1
주요 변경: - 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>
207 lines
8.3 KiB
Python
207 lines
8.3 KiB
Python
"""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())
|