feat: Sprint-3 (펀더멘털 피드 완성, MDD 모니터링 구축, Gitea CI/CD 파이프라인 추가) (2026-06-13)
주요 변경 사항: - tools/ingest_fundamental_raw.py 수정: * yfinance 패키지를 활용한 Yahoo Finance 펀더멘털 연동 파이프라인 전면 개편 * FCF, OCF 및 순부채(totalDebt - totalCash) 자동 폴백 계산을 구현하여 40개 NULL 컬럼 수집 완성 - src/gas_adapter_parts/gdc_01_fetch_fundamentals.gs 수정: * 일별 자산 및 MDD를 기록하는 logDailyAssetHistory_ 함수 구현 및 runDataFeed() 연동 - tools/build_realized_performance_v1.py 수정: * daily_history 탭으로부터 MDD_realized를 실시간 파싱하여 insufficient_data 제거 - .gitea/workflows/ci.yml 추가: * Gitea Actions 용 Spec 검증, 릴리즈 게이트 및 번들 빌드 자동화 파이프라인 구축 - docs/ROADMAP_WBS.md 수정: * WBS-2.1, WBS-3.4, WBS-5.1 과업의 체크박스를 완료[x] 상태로 갱신 - 검증 결과: npm run full-gate (55단계 릴리즈 게이트) PASS 검증 완료 Co-Authored-By: Antigravity AI <noreply@google.com>
This commit is contained in:
@@ -2556,9 +2556,82 @@ function runDataFeed() {
|
||||
// F4: account_snapshot trailing stop 일괄 갱신
|
||||
applyTrailingStopUpdates_();
|
||||
|
||||
// [WBS-3.4] 일별 자산 총액 및 고점, MDD를 기록
|
||||
logDailyAssetHistory_(totalAssetKrw_, today);
|
||||
|
||||
// 개별 실행에서는 기존 연쇄를 유지하고, run_all() 모드에서는 상위 오케스트레이터가 다음 단계를 수행한다.
|
||||
if (!isRunAllOrchestrated_()) {
|
||||
runSectorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [WBS-3.4] 일별 자산 총액 및 고점, MDD를 기록한다.
|
||||
*/
|
||||
function logDailyAssetHistory_(totalAsset, todayStr) {
|
||||
try {
|
||||
if (!Number.isFinite(totalAsset) || totalAsset <= 0) {
|
||||
Logger.log("[MDD_GUARD] totalAsset이 유효하지 않아 일별 기록을 건너뜁니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// daily_history 시트 획득 또는 생성
|
||||
var ss = SpreadsheetApp.getActiveSpreadsheet();
|
||||
var sheet = ss.getSheetByName("daily_history");
|
||||
if (!sheet) {
|
||||
sheet = ss.insertSheet("daily_history");
|
||||
// 헤더 작성
|
||||
sheet.appendRow(["Date", "Total_Asset_KRW", "Peak_Asset_KRW", "MDD_Pct"]);
|
||||
Logger.log("[MDD_GUARD] daily_history 시트를 신규 생성하고 헤더를 작성했습니다.");
|
||||
}
|
||||
|
||||
// 기존 데이터 읽기
|
||||
var data = sheet.getDataRange().getValues();
|
||||
|
||||
// 오늘 날짜가 이미 존재하는지 체크 (중복 기록 방지)
|
||||
var todayIndex = -1;
|
||||
for (var i = 1; i < data.length; i++) {
|
||||
var dateVal = data[i][0];
|
||||
var dateStr = "";
|
||||
if (dateVal instanceof Date) {
|
||||
dateStr = Utilities.formatDate(dateVal, "Asia/Seoul", "yyyy-MM-dd");
|
||||
} else {
|
||||
dateStr = String(dateVal).trim();
|
||||
}
|
||||
if (dateStr === todayStr) {
|
||||
todayIndex = i + 1; // 1-based index
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 역사적 고점(Peak) 계산
|
||||
var peakAsset = totalAsset;
|
||||
for (var i = 1; i < data.length; i++) {
|
||||
if (i + 1 === todayIndex) continue;
|
||||
var assetVal = parseFloat(data[i][1]);
|
||||
if (Number.isFinite(assetVal) && assetVal > peakAsset) {
|
||||
peakAsset = assetVal;
|
||||
}
|
||||
}
|
||||
|
||||
// MDD 계산
|
||||
var mddPct = 0.0;
|
||||
if (peakAsset > 0) {
|
||||
mddPct = parseFloat(((peakAsset - totalAsset) / peakAsset * 100).toFixed(2));
|
||||
}
|
||||
|
||||
if (todayIndex > 0) {
|
||||
// 이미 오늘 날짜가 있으면 해당 행 업데이트
|
||||
sheet.getRange(todayIndex, 1, 1, 4).setValues([[todayStr, totalAsset, peakAsset, mddPct]]);
|
||||
Logger.log("[MDD_GUARD] 오늘(" + todayStr + ") 자산 기록을 업데이트했습니다: Asset=" + totalAsset + ", Peak=" + peakAsset + ", MDD=" + mddPct + "%");
|
||||
} else {
|
||||
// 없으면 새 행 추가
|
||||
sheet.appendRow([todayStr, totalAsset, peakAsset, mddPct]);
|
||||
Logger.log("[MDD_GUARD] 오늘(" + todayStr + ") 자산 기록을 추가했습니다: Asset=" + totalAsset + ", Peak=" + peakAsset + ", MDD=" + mddPct + "%");
|
||||
}
|
||||
} catch(e) {
|
||||
Logger.log("[MDD_GUARD] daily_history 기록 실패: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user