@@ -126,12 +126,51 @@ def summarize_blockers(blockers: dict[str, Any] | None) -> dict[str, Any]:
126126 }
127127
128128
129+ def summarize_deployment (
130+ deployment_ratio : float | None ,
131+ design_fraction : float | None ,
132+ * ,
133+ tolerance : float = 0.05 ,
134+ ) -> dict [str , Any ]:
135+ """집계 배치율(총자산 중 실제 주식비중)이 설계 대비 크게 미달인지 판정(순수 함수).
136+
137+ 한 달 운영 리뷰(docs/PAPER_MONTH1_REVIEW_AND_PLAN.md P1-5)의 배경: 종목별 드리프트
138+ 트리거는 '집계 배치율' 이탈(예: 실효 61% vs 설계 80%)을 영영 못 본다. 여기서 그 이탈을
139+ 운영자 헬스로 표면화한다. 실제가 설계보다 tolerance(기본 5%p) 초과로 낮으면 ATTENTION.
140+ (초과 배치는 리밸런서가 자연 교정하므로 '미달'만 감시한다.)
141+
142+ 반환: {verdict(OK|ATTENTION), note(str|None), deployment_ratio, design_fraction, shortfall}
143+ """
144+ if deployment_ratio is None or design_fraction is None :
145+ return {
146+ "verdict" : "OK" , "note" : None ,
147+ "deployment_ratio" : deployment_ratio , "design_fraction" : design_fraction ,
148+ "shortfall" : None ,
149+ }
150+ shortfall = float (design_fraction ) - float (deployment_ratio )
151+ verdict , note = "OK" , None
152+ if shortfall > tolerance :
153+ verdict = "ATTENTION"
154+ note = (
155+ f"주식 배치율 { deployment_ratio :.0%} < 설계 { design_fraction :.0%} "
156+ f"({ - shortfall * 100 :.1f} %p) — 미체결 슬롯/자본 점검"
157+ )
158+ return {
159+ "verdict" : verdict , "note" : note ,
160+ "deployment_ratio" : float (deployment_ratio ), "design_fraction" : float (design_fraction ),
161+ "shortfall" : shortfall ,
162+ }
163+
164+
129165def summarize_basket_operation (
130166 enabled_baskets : list [str ],
131167 last_snapshot_date : Any ,
132168 position_count : int ,
133169 today : Any ,
134170 max_stale_calendar_days : int = 4 ,
171+ deployment_ratio : float | None = None ,
172+ design_fraction : float | None = None ,
173+ deployment_tolerance : float = 0.05 ,
135174) -> dict [str , Any ]:
136175 """바스켓 paper 운영(트랙레코드 축적) 상태를 verdict + 요약으로 환원한다.
137176
@@ -158,6 +197,8 @@ def _as_date(v: Any):
158197 "last_snapshot_date" : None ,
159198 "position_count" : int (position_count or 0 ),
160199 "stale_days" : None ,
200+ "deployment_ratio" : None ,
201+ "design_fraction" : None ,
161202 "notes" : ["enabled 바스켓 없음(운영 안 함)" ],
162203 }
163204
@@ -176,12 +217,23 @@ def _as_date(v: Any):
176217 "일일 사이클 중단 의심"
177218 )
178219
220+ # 집계 배치율 미달 감시 — 종목별 드리프트가 못 보는 설계 대비 이탈을 표면화.
221+ dep = summarize_deployment (
222+ deployment_ratio , design_fraction , tolerance = deployment_tolerance ,
223+ )
224+ if dep ["verdict" ] == "ATTENTION" :
225+ verdict = "ATTENTION"
226+ if dep ["note" ]:
227+ notes .append (dep ["note" ])
228+
179229 return {
180230 "verdict" : verdict ,
181231 "enabled_baskets" : list (enabled_baskets ),
182232 "last_snapshot_date" : _as_date (last_snapshot_date ) if last_snapshot_date is not None else None ,
183233 "position_count" : int (position_count or 0 ),
184234 "stale_days" : stale_days ,
235+ "deployment_ratio" : dep ["deployment_ratio" ],
236+ "design_fraction" : dep ["design_fraction" ],
185237 "notes" : notes ,
186238 }
187239
0 commit comments