Merge pull request '[WBS-2.3] RS(상대강도) 신호 V2 완성' (#6) from feature/wbs-2.3 into main

Reviewed-on: http://192.168.123.100:8418/KimJaeHyun/myfinance/pulls/6
This commit is contained in:
2026-06-13 14:18:44 +09:00
2 changed files with 14 additions and 6 deletions
@@ -2256,7 +2256,15 @@ function runDataFeed() {
const macroSheet = getSpreadsheet_().getSheetByName("macro"); const macroSheet = getSpreadsheet_().getSheetByName("macro");
if (macroSheet) { if (macroSheet) {
const mData = macroSheet.getDataRange().getValues(); const mData = macroSheet.getDataRange().getValues();
const mHdr = mData[1] ?? []; let headerRowIdx = 0;
for (let r = 0; r < Math.min(5, mData.length); r++) {
const row = mData[r] ?? [];
if (row.indexOf("Symbol") >= 0 && row.indexOf("Name") >= 0) {
headerRowIdx = r;
break;
}
}
const mHdr = mData[headerRowIdx] ?? [];
const symIdx = mHdr.indexOf("Symbol"); const symIdx = mHdr.indexOf("Symbol");
const nameIdx = mHdr.indexOf("Name"); const nameIdx = mHdr.indexOf("Name");
const closeIdx = mHdr.indexOf("Close"); const closeIdx = mHdr.indexOf("Close");
@@ -2265,7 +2273,7 @@ function runDataFeed() {
const ret10DIdx = mHdr.indexOf("Ret10D"); const ret10DIdx = mHdr.indexOf("Ret10D");
const ret20DIdx = mHdr.indexOf("Ret20D"); const ret20DIdx = mHdr.indexOf("Ret20D");
const ret60DIdx = mHdr.indexOf("Ret60D"); const ret60DIdx = mHdr.indexOf("Ret60D");
for (let i = 2; i < mData.length; i++) { for (let i = headerRowIdx + 1; i < mData.length; i++) {
const sym = symIdx >= 0 ? String(mData[i][symIdx]).trim() : ""; const sym = symIdx >= 0 ? String(mData[i][symIdx]).trim() : "";
const name = nameIdx >= 0 ? String(mData[i][nameIdx]).trim() : ""; const name = nameIdx >= 0 ? String(mData[i][nameIdx]).trim() : "";
if (name === "KOSPI") { if (name === "KOSPI") {
@@ -124,7 +124,7 @@ def run() -> dict:
"target_date": ACTIVATION_DATE_TARGET, "target_date": ACTIVATION_DATE_TARGET,
"message": ( "message": (
f"live_t20={live_t20_count}/{LIVE_T20_THRESHOLD} " f"live_t20={live_t20_count}/{LIVE_T20_THRESHOLD} "
f"({progress_pct}%) 목표일 {ACTIVATION_DATE_TARGET}까지 " f"({progress_pct}%) - 목표일 {ACTIVATION_DATE_TARGET}까지 "
f"{days_remaining}일 잔여" f"{days_remaining}일 잔여"
), ),
"transition_checks": [], "transition_checks": [],
@@ -141,14 +141,14 @@ def run() -> dict:
"progress_pct": 100.0, "progress_pct": 100.0,
"message": ( "message": (
f"live_t20 임계값 도달({live_t20_count}건). " f"live_t20 임계값 도달({live_t20_count}건). "
f"전환 조건 {'전부 PASS' if all_pass else '일부 FAIL 재빌드 필요'}." f"전환 조건 {'전부 PASS' if all_pass else '일부 FAIL - 재빌드 필요'}."
), ),
"transition_checks": transition_checks, "transition_checks": transition_checks,
"all_transitions_complete": all_pass, "all_transitions_complete": all_pass,
} }
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_PATH.write_text(json.dumps(result, ensure_ascii=False, indent=2)) OUTPUT_PATH.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
return result return result
@@ -160,7 +160,7 @@ def main() -> None:
if gate == "FAIL": if gate == "FAIL":
failed = [c for c in result.get("transition_checks", []) if not c["passed"]] failed = [c for c in result.get("transition_checks", []) if not c["passed"]]
for f in failed: for f in failed:
print(f" FAIL: {f['condition']} actual={f['actual']} target={f['target']}") print(f" FAIL: {f['condition']} - actual={f['actual']} target={f['target']}")
sys.exit(1) sys.exit(1)
if gate == "PENDING": if gate == "PENDING":
sys.exit(0) # PENDING은 오류가 아님, 정상 진행 중 sys.exit(0) # PENDING은 오류가 아님, 정상 진행 중