refactor(gas): GAS 중복 함수 제거 및 루트 레벨 stale .gs 파일 정리
- gas_apex_alpha_watch.gs: gas_apex_runtime_core.gs의 5개 실 구현을 shadowing하던 stub/구버전 제거 (applyApexMacroAlphaSuiteImpl_, applyApexMacroEventSuiteImpl_, calcConsistencyValidatorV2Impl_, calcMacroEventSynchronizerV1Impl_, calcMacroRegimeAdaptiveGateV2Impl_) - gas_lib.gs: gdf_05_alpha_engines.gs로 이전된 runAlphaFeedbackLoop_, getAlphaFeedbackJson_ 스테일 사본 제거 - 루트 레벨 .gs 8개 삭제: src/gas/ 구조 이전 전 구버전, 배포 경로 밖 dead code Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -474,31 +474,6 @@ function appendAlphaHistory_(ss, aewRows, holdings, dfMap, marketRegime) {
|
||||
});
|
||||
}
|
||||
|
||||
function getAlphaFeedbackJson_() {
|
||||
var defaultPayload = {
|
||||
formula_id: 'ALPHA_FEEDBACK_LOOP_V1',
|
||||
as_of: '',
|
||||
analysis_period: '',
|
||||
status: 'DATA_MISSING',
|
||||
cases_analyzed: 0,
|
||||
grade_count: 0,
|
||||
eligible_t20_fail_rate: null,
|
||||
eligible_t60_fail_rate: null,
|
||||
recommended_filter_adjustments: [],
|
||||
grade_summary: []
|
||||
};
|
||||
try {
|
||||
var settings = readSettingsTab_();
|
||||
var raw = settings['afl_v1_last_result'];
|
||||
if (!raw) return defaultPayload;
|
||||
var payload = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||
return payload && typeof payload === 'object' ? payload : defaultPayload;
|
||||
} catch (e) {
|
||||
Logger.log('[AFL] getAlphaFeedbackJson_ error: ' + e.message);
|
||||
return defaultPayload;
|
||||
}
|
||||
}
|
||||
|
||||
// ── settings 탭 읽기 → 사용자 입력 파라미터 (total_asset 등) ────────────────
|
||||
// settings 탭: row2=헤더(key|value|note), row3+=데이터
|
||||
// 없으면 빈 객체 반환 (각 호출처에서 null 처리)
|
||||
@@ -2406,143 +2381,6 @@ function getOrbitGapJson() {
|
||||
};
|
||||
}
|
||||
|
||||
// ── [2026-05-21_AFL_V1] ALPHA_FEEDBACK_LOOP_V1 -- monthly grade analysis ────────
|
||||
function runAlphaFeedbackLoop_() {
|
||||
var ss = getSpreadsheet_();
|
||||
var sheet = ss.getSheetByName("alpha_history");
|
||||
var today = Utilities.formatDate(new Date(), "Asia/Seoul", "yyyy-MM-dd");
|
||||
var monthKey = today.substring(0, 7);
|
||||
var defaultPayload = {
|
||||
formula_id: 'ALPHA_FEEDBACK_LOOP_V1',
|
||||
as_of: today,
|
||||
analysis_period: monthKey,
|
||||
status: 'DATA_MISSING',
|
||||
cases_analyzed: 0,
|
||||
grade_count: 0,
|
||||
eligible_t20_fail_rate: null,
|
||||
eligible_t60_fail_rate: null,
|
||||
recommended_filter_adjustments: [],
|
||||
grade_summary: []
|
||||
};
|
||||
if (!sheet) {
|
||||
writeSettingValue_(ss, 'afl_v1_last_result', JSON.stringify(defaultPayload));
|
||||
Logger.log("[AFL] alpha_history sheet not found");
|
||||
return defaultPayload;
|
||||
}
|
||||
var data = sheet.getDataRange().getValues();
|
||||
if (data.length < 2) {
|
||||
writeSettingValue_(ss, 'afl_v1_last_result', JSON.stringify(defaultPayload));
|
||||
Logger.log("[AFL] alpha_history has no data");
|
||||
return defaultPayload;
|
||||
}
|
||||
|
||||
var hdrRow = data[0];
|
||||
var hdrMap = {};
|
||||
hdrRow.forEach(function(h, i) { hdrMap[h] = i; });
|
||||
|
||||
var gradeStats = {};
|
||||
var analyzedCases = 0;
|
||||
for (var i = 1; i < data.length; i++) {
|
||||
var row = data[i];
|
||||
var grade = String(row[hdrMap['SAQG_Grade_At_Entry']] || '').trim();
|
||||
var t20g = String(row[hdrMap['T20_Alpha_Gate']] || '').trim();
|
||||
var t60g = String(row[hdrMap['T60_Alpha_Gate']] || '').trim();
|
||||
if (!grade) continue;
|
||||
if (!gradeStats[grade]) gradeStats[grade] = { t20_total: 0, t20_pass: 0, t60_total: 0, t60_pass: 0 };
|
||||
var s = gradeStats[grade];
|
||||
var skipVals = { 'NOT_YET': 1, 'EXEMPT': 1, 'DATA_MISSING': 1, '': 1 };
|
||||
var hasT20 = t20g && !skipVals[t20g];
|
||||
var hasT60 = t60g && !skipVals[t60g];
|
||||
if (hasT20) { s.t20_total++; if (t20g === 'T20_ALPHA_PASS') s.t20_pass++; }
|
||||
if (hasT60) { s.t60_total++; if (t60g === 'T60_ALPHA_PASS') s.t60_pass++; }
|
||||
if (hasT20 || hasT60) analyzedCases++;
|
||||
}
|
||||
|
||||
var gradeSummary = [];
|
||||
Object.keys(gradeStats).sort().forEach(function(grade) {
|
||||
var s = gradeStats[grade];
|
||||
var t20FailRate = s.t20_total > 0 ? parseFloat((((s.t20_total - s.t20_pass) / s.t20_total) * 100).toFixed(2)) : null;
|
||||
var t60FailRate = s.t60_total > 0 ? parseFloat((((s.t60_total - s.t60_pass) / s.t60_total) * 100).toFixed(2)) : null;
|
||||
var t20PassRate = s.t20_total > 0 ? parseFloat(((s.t20_pass / s.t20_total) * 100).toFixed(2)) : null;
|
||||
var t60PassRate = s.t60_total > 0 ? parseFloat(((s.t60_pass / s.t60_total) * 100).toFixed(2)) : null;
|
||||
gradeSummary.push({
|
||||
grade: grade,
|
||||
t20_total: s.t20_total,
|
||||
t20_pass: s.t20_pass,
|
||||
t20_pass_rate: t20PassRate,
|
||||
t20_fail_rate: t20FailRate,
|
||||
t60_total: s.t60_total,
|
||||
t60_pass: s.t60_pass,
|
||||
t60_pass_rate: t60PassRate,
|
||||
t60_fail_rate: t60FailRate,
|
||||
status: (s.t20_total >= 10 || s.t60_total >= 10) ? 'ANALYZED' : 'DATA_INSUFFICIENT'
|
||||
});
|
||||
});
|
||||
|
||||
var eligibleRow = gradeStats['ELIGIBLE'] || { t20_total: 0, t20_pass: 0, t60_total: 0, t60_pass: 0 };
|
||||
var eligibleT20FailRate = eligibleRow.t20_total > 0
|
||||
? parseFloat((((eligibleRow.t20_total - eligibleRow.t20_pass) / eligibleRow.t20_total) * 100).toFixed(2))
|
||||
: null;
|
||||
var eligibleT60FailRate = eligibleRow.t60_total > 0
|
||||
? parseFloat((((eligibleRow.t60_total - eligibleRow.t60_pass) / eligibleRow.t60_total) * 100).toFixed(2))
|
||||
: null;
|
||||
var eligibleT20PassRate = eligibleRow.t20_total > 0
|
||||
? parseFloat(((eligibleRow.t20_pass / eligibleRow.t20_total) * 100).toFixed(2))
|
||||
: null;
|
||||
|
||||
var recommendations = [];
|
||||
if (analyzedCases >= 10) {
|
||||
if (eligibleT20FailRate !== null && eligibleT20FailRate > 50) {
|
||||
recommendations.push({
|
||||
filter_id: 'SAQG_F2_RECOVERY_RATIO',
|
||||
current: '1.20',
|
||||
recommended: '1.35',
|
||||
rationale: 'ELIGIBLE T+20 fail rate > 50%',
|
||||
action: 'TIGHTEN'
|
||||
});
|
||||
recommendations.push({
|
||||
filter_id: 'SAQG_F3_EXCESS_DRAWDOWN',
|
||||
current: '5%p',
|
||||
recommended: '4%p',
|
||||
rationale: 'ELIGIBLE T+20 fail rate > 50%',
|
||||
action: 'TIGHTEN'
|
||||
});
|
||||
} else if (eligibleT20PassRate !== null && eligibleT20PassRate > 70 && eligibleRow.t20_total >= 12) {
|
||||
recommendations.push({
|
||||
filter_id: 'SAQG_F3_EXCESS_DRAWDOWN',
|
||||
current: '5%p',
|
||||
recommended: '7%p',
|
||||
rationale: 'ELIGIBLE T+20 success rate > 70% and cases >= 12',
|
||||
action: 'RELAX_REVIEW'
|
||||
});
|
||||
} else {
|
||||
recommendations.push({
|
||||
filter_id: 'SAQG_F1_F2_F3',
|
||||
current: 'UNCHANGED',
|
||||
recommended: 'HOLD',
|
||||
rationale: 'No threshold change supported by current sample',
|
||||
action: 'HOLD'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var payload = {
|
||||
formula_id: 'ALPHA_FEEDBACK_LOOP_V1',
|
||||
as_of: today,
|
||||
analysis_period: monthKey,
|
||||
status: analyzedCases >= 10 ? 'ANALYZED' : 'DATA_INSUFFICIENT',
|
||||
cases_analyzed: analyzedCases,
|
||||
grade_count: Object.keys(gradeStats).length,
|
||||
eligible_t20_fail_rate: eligibleT20FailRate,
|
||||
eligible_t60_fail_rate: eligibleT60FailRate,
|
||||
recommended_filter_adjustments: analyzedCases >= 10 ? recommendations : [],
|
||||
grade_summary: gradeSummary
|
||||
};
|
||||
writeSettingValue_(ss, 'afl_v1_last_result', JSON.stringify(payload));
|
||||
Logger.log('[AFL] done - ' + payload.grade_count + ' grades analyzed, cases=' + analyzedCases);
|
||||
return payload;
|
||||
}
|
||||
|
||||
// ── E2: 월말 자산 스냅샷 → monthly_history 기록 ─────────────────────────────
|
||||
// 트리거: 매달 마지막 영업일 16:30 독립 실행 OR runDataFeed 완료 후 호출.
|
||||
function runMonthlySnapshot() {
|
||||
|
||||
@@ -91,94 +91,6 @@ function calcAntiLateEntryGateV2Impl_(holdings, dfMap) {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* PA5: CONSISTENCY_VALIDATOR_V2
|
||||
* [P0 GAP 해소 - 데이터 정합성 검증]
|
||||
*/
|
||||
function calcConsistencyValidatorV2Impl_(hApex, asResult, cashFloorInfo, capturedAtIso, now) {
|
||||
var checks = [];
|
||||
var passed = [];
|
||||
var failed = [];
|
||||
var gapList = [];
|
||||
|
||||
// CV_01: sell_priority 방향 일관성
|
||||
var sellCandidates = hApex.sell_candidates_json || [];
|
||||
var tierOk = true;
|
||||
for (var i = 1; i < sellCandidates.length; i++) {
|
||||
if (sellCandidates[i].tier < sellCandidates[i-1].tier) {
|
||||
tierOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tierOk) passed.push('CV_01'); else failed.push({check_id: 'CV_01', reason: 'tier_reversal'});
|
||||
|
||||
// CV_02: 가격 순서 검증
|
||||
var prices = hApex.prices_json || [];
|
||||
var priceOk = true;
|
||||
for (var i = 0; i < prices.length; i++) {
|
||||
var p = prices[i];
|
||||
if (p.stop_price && p.current_price && p.stop_price >= p.current_price) priceOk = false;
|
||||
}
|
||||
if (priceOk) passed.push('CV_02'); else failed.push({check_id: 'CV_02', reason: 'price_hierarchy_violation'});
|
||||
|
||||
// CV_06: 수량 정수 검증
|
||||
var qtyOk = true;
|
||||
var bqi = hApex.buy_qty_inputs_json || [];
|
||||
for (var i = 0; i < bqi.length; i++) {
|
||||
if (bqi[i].final_qty && bqi[i].final_qty % 1 !== 0) qtyOk = false;
|
||||
}
|
||||
if (qtyOk) passed.push('CV_06'); else failed.push({check_id: 'CV_06', reason: 'float_quantity'});
|
||||
|
||||
// CV_08: 현금 계산 경로
|
||||
if (hApex.cash_ledger_basis === 'D2_ONLY') passed.push('CV_08');
|
||||
else failed.push({check_id: 'CV_08', reason: 'invalid_cash_basis'});
|
||||
|
||||
// Score 계산
|
||||
var score = Math.floor((passed.length / 12) * 100);
|
||||
var status = score >= 90 ? (score === 100 ? 'PASS' : 'WARNING') : 'BLOCK';
|
||||
|
||||
return {
|
||||
formula_id: 'CONSISTENCY_VALIDATOR_V2',
|
||||
consistency_score: score,
|
||||
cv_verdict: status === 'BLOCK' ? 'ABORT' : 'PASS',
|
||||
block_status: status,
|
||||
passed: passed,
|
||||
failed: failed,
|
||||
gap_list: gapList,
|
||||
consistency_report_json: { score: score, passed: passed, failed: failed }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* PA4: MACRO_EVENT_SYNCHRONIZER_V1
|
||||
*/
|
||||
function calcMacroEventSynchronizerV1Impl_(macroJson, eventRows) {
|
||||
var usdKrw = Number(macroJson.usd_krw || 0);
|
||||
var foreignSellDays = Number(macroJson.foreign_sell_consecutive_days || 0);
|
||||
|
||||
var score = 0;
|
||||
if (usdKrw > 1500) score += 20;
|
||||
else if (usdKrw > 1480) score += 15;
|
||||
|
||||
if (foreignSellDays >= 10) score += 20;
|
||||
else if (foreignSellDays >= 5) score += 15;
|
||||
|
||||
var regime = 'MACRO_NEUTRAL';
|
||||
var heatAdj = 0;
|
||||
if (score >= 60) { regime = 'MACRO_CRITICAL'; heatAdj = -3; }
|
||||
else if (score >= 40) { regime = 'MACRO_ELEVATED'; heatAdj = -1; }
|
||||
else if (score < 20) { regime = 'MACRO_FAVORABLE'; heatAdj = 1; }
|
||||
|
||||
return {
|
||||
formula_id: 'MACRO_EVENT_SYNCHRONIZER_V1',
|
||||
macro_risk_score: score,
|
||||
macro_risk_regime: regime,
|
||||
effective_heat_gate_adjustment: heatAdj,
|
||||
mega_sell_alert: false,
|
||||
macro_event_json: { score: score, regime: regime, heat_gate_adj: heatAdj }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* PA1: PREDICTIVE_ALPHA_ENGINE_V1
|
||||
*/
|
||||
@@ -216,29 +128,6 @@ function calcPredictiveAlphaEngineV1Impl_(holdings, dfMap, macroJson, mesResult,
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* MACRO_REGIME_ADAPTIVE_GATE_V2
|
||||
*/
|
||||
function calcMacroRegimeAdaptiveGateV2Impl_(macroJson, mesResult, hApex) {
|
||||
var totalScore = mesResult.macro_risk_score || 0;
|
||||
var regime = 'MODERATE_RISK';
|
||||
var heatThreshold = 10.0;
|
||||
var sizeScale = 1.0;
|
||||
|
||||
if (totalScore >= 75) { regime = 'EXTREME_RISK'; heatThreshold = 5.0; sizeScale = 0.25; }
|
||||
else if (totalScore >= 50) { regime = 'HIGH_RISK'; heatThreshold = 7.0; sizeScale = 0.50; }
|
||||
else if (totalScore < 25) { regime = 'LOW_RISK'; heatThreshold = 12.0; sizeScale = 1.10; }
|
||||
|
||||
return {
|
||||
formula_id: 'MACRO_REGIME_ADAPTIVE_GATE_V2',
|
||||
total_mrag_score: totalScore,
|
||||
regime_label: regime,
|
||||
effective_heat_gate_threshold: heatThreshold,
|
||||
effective_position_size_scale: sizeScale,
|
||||
mrag_v2_json: { score: totalScore, regime: regime }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* applyAlegGate4And5Impl_
|
||||
*/
|
||||
@@ -263,19 +152,6 @@ function applyAlegGate4And5Impl_(alegRows, paeRows, hApex) {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suite Aggregators
|
||||
*/
|
||||
function applyApexMacroAlphaSuiteImpl_(holdings, dfMap, hApex) {
|
||||
// Placeholder for macro alpha suite
|
||||
return hApex;
|
||||
}
|
||||
|
||||
function applyApexMacroEventSuiteImpl_(hApex) {
|
||||
// Placeholder for macro event suite
|
||||
return hApex;
|
||||
}
|
||||
|
||||
function applyApexPredictiveAlphaSuiteImpl_(holdings, dfMap, hApex) {
|
||||
var macroJson = hApex.macro_event_json || {};
|
||||
var mesResult = hApex.macro_event_json || {};
|
||||
|
||||
Reference in New Issue
Block a user