feat: Sprint-3 완결 + Sprint-4 착수 (WBS-3.2, 3.4, 5.2)
주요 변경: - [WBS-3.2] 리밸런싱 V2 신호 가중 목표배분 (signal_weighted_ss001_v1) * equal_weight -> SS001_Norm_Score 비례 버킷내 배분 * 하네스: 삼성(36.84%) > SK하이닉스(29.16%), Core=66.00% PASS - [WBS-3.4] logDailyAssetHistory_ SpreadsheetApp.getActiveSpreadsheet() -> getSpreadsheet_() 수정 * run_all 컨텍스트에서 null 반환 방지 - [WBS-5.2] deploy_gas.py 전면 재작성 * src/gas_adapter_parts/ + src/gas/ 양쪽 소스 탐색 * gdc_01+gdc_02 -> gas_data_collect.gs 번들링 * dry-run PASS: 17개 파일 WARN 0건 - src/gas/ 디렉토리 신규 추가 (CLASP 조직화 구조) - tools/automate_routine.py, download_trading_data.py 신규 추가 - .gitignore: .clasprc.json OAuth 토큰 제외 추가 - ROADMAP_WBS.md: Sprint-3 [x] 완료, Sprint-4 착수 목록 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -381,6 +381,9 @@ def main() -> int:
|
||||
continue
|
||||
seen_tickers.add(ticker)
|
||||
df_row = df_row_map.get(ticker, {})
|
||||
# SS001_Norm_Score: 0~100 신호 강도. 0은 데이터 없음 → 최소 가중치 1.0으로 폴백
|
||||
ss001_raw = _f(df_row.get("SS001_Norm_Score"), default=-1.0)
|
||||
ss001_norm = max(1.0, ss001_raw) if ss001_raw > 0 else 1.0
|
||||
holdings.append({
|
||||
"ticker": ticker,
|
||||
"name": sp["name"] or _s(df_row.get("Name")),
|
||||
@@ -392,6 +395,7 @@ def main() -> int:
|
||||
"final_action": _s(df_row.get("Final_Action")),
|
||||
"sell_reason": _s(df_row.get("Sell_Reason")),
|
||||
"force_signal": _detect_force_signal(df_row),
|
||||
"ss001_norm": ss001_norm,
|
||||
})
|
||||
|
||||
# data_feed 에만 있는 보유 종목 보완 (snap 에 없는 경우 — 비정상이지만 방어)
|
||||
@@ -403,6 +407,8 @@ def main() -> int:
|
||||
acct_mv = _f(row.get("Account_Market_Value"))
|
||||
if weight_pct <= 0 and acct_mv <= 0:
|
||||
continue
|
||||
ss001_raw = _f(row.get("SS001_Norm_Score"), default=-1.0)
|
||||
ss001_norm = max(1.0, ss001_raw) if ss001_raw > 0 else 1.0
|
||||
holdings.append({
|
||||
"ticker": ticker,
|
||||
"name": _s(row.get("Name")),
|
||||
@@ -414,6 +420,7 @@ def main() -> int:
|
||||
"final_action": _s(row.get("Final_Action")),
|
||||
"sell_reason": _s(row.get("Sell_Reason")),
|
||||
"force_signal": _detect_force_signal(row),
|
||||
"ss001_norm": ss001_norm,
|
||||
})
|
||||
|
||||
# ── 3. 버킷별 현재 비중 집계 ─────────────────────────────────────────────
|
||||
@@ -462,17 +469,22 @@ def main() -> int:
|
||||
})
|
||||
|
||||
# ── 4. 종목별 분석 ───────────────────────────────────────────────────────
|
||||
# 버킷 내 equal-weight target
|
||||
bucket_ticker_count: dict[str, int] = {}
|
||||
# [WBS-3.2] 신호 가중 목표배분 (SS001_Norm_Score 기반)
|
||||
# ticker_target_pct = bucket_target × (ss001_norm / Σ ss001_norm in bucket)
|
||||
# 폴백: ss001_norm 모두 1.0 → equal_weight 와 동일
|
||||
bucket_ss001_total: dict[str, float] = {}
|
||||
for h in holdings:
|
||||
bucket_ticker_count[h["bucket"]] = bucket_ticker_count.get(h["bucket"], 0) + 1
|
||||
bucket_ss001_total[h["bucket"]] = (
|
||||
bucket_ss001_total.get(h["bucket"], 0.0) + h.get("ss001_norm", 1.0)
|
||||
)
|
||||
|
||||
ticker_rows: list[dict] = []
|
||||
for h in holdings:
|
||||
bname = h["bucket"]
|
||||
bcfg = BUCKET_CONFIG.get(bname, BUCKET_CONFIG["Satellite"])
|
||||
n_tickers = bucket_ticker_count.get(bname, 1)
|
||||
target_pct = _round2(bcfg["target"] / n_tickers)
|
||||
bname = h["bucket"]
|
||||
bcfg = BUCKET_CONFIG.get(bname, BUCKET_CONFIG["Satellite"])
|
||||
ss001_w = h.get("ss001_norm", 1.0)
|
||||
bucket_ss001 = max(bucket_ss001_total.get(bname, ss001_w), 0.01)
|
||||
target_pct = _round2(bcfg["target"] * ss001_w / bucket_ss001)
|
||||
current_pct = _round2(h["weight_pct"])
|
||||
drift = _round2(current_pct - target_pct)
|
||||
band_min = _round2(target_pct - band["contract"])
|
||||
@@ -612,7 +624,7 @@ def main() -> int:
|
||||
out = {
|
||||
"formula_id": FORMULA_ID,
|
||||
"metadata": {
|
||||
"per_ticker_target_method": "equal_weight_within_bucket",
|
||||
"per_ticker_target_method": "signal_weighted_ss001_v1",
|
||||
"regime_source": "macro.REGIME_PRELIM > settings > harness_context > computed_harness",
|
||||
},
|
||||
"summary": summary,
|
||||
|
||||
Reference in New Issue
Block a user