// gas_harness_rows.gs - Harness output serialization // buildHarnessRows_, assertHarnessRowsComplete_, checksum functions // Pure output assembly - no decision logic. Rarely changes after V stabilizes. // GAS global scope: functions in gas_data_feed.gs callable directly /** * computeBlueprintChecksum_ * order_blueprint_json의 위변조 탐지용 체크섬 (CRC32_V1). * ticker + order_type + quantity + limit_price_krw + validation_status 를 * 행 순서대로 연결한 문자열의 char-code sum을 반환한다. * Python converter는 이 값과 자신이 재계산한 값이 다르면 HARNESS_INTEGRITY_FAIL 처리. */ function computeBlueprintChecksum_(blueprint) { var s = ''; blueprint = blueprint || []; for (var i = 0; i < blueprint.length; i++) { var r = blueprint[i]; s += String(r.ticker || '') + '|' + String(r.order_type || '') + '|' + String(r.quantity != null ? r.quantity : '') + '|' + String(r.limit_price_krw != null ? r.limit_price_krw : '') + '|' + String(r.validation_status || '') + ';'; } var sum = 0; for (var j = 0; j < s.length; j++) { sum = (sum + s.charCodeAt(j)) & 0xFFFFFFFF; } return sum; } /** * [2026-05-20_HARNESS_V5] computeInputSnapshotChecksum_ * 계좌 스냅샷 원장(보유수량·평단·종가·현금·기준시각)의 CRC32_V1 해시. * 동일 입력 재호출 시 이 값이 달라지면 데이터 소스가 갱신된 것이다. * Python 검증기가 이전 실행값과 비교하여 non_deterministic_flag 를 set 한다. */ function computeInputSnapshotChecksum_(asResult, capturedAtIso) { var s = String(capturedAtIso || '') + '|' + String((asResult || {}).settlementCashD2Krw != null ? asResult.settlementCashD2Krw : '') + '|'; ((asResult || {}).holdings || []).forEach(function(h) { s += String(h.ticker || '') + '|' + String(h.holdingQty != null ? h.holdingQty : '') + '|' + String(h.avgCost != null ? h.avgCost : '') + '|' + String(h.close != null ? h.close : '') + ';'; }); var sum = 0; for (var i = 0; i < s.length; i++) { sum = (sum + s.charCodeAt(i)) & 0xFFFFFFFF; } return sum; } /** * I3: computeStringChecksum_ * 임의 문자열의 char-code sum 체크섬 (CRC32_V1 방식). * source_manifest_json, decision_trace_json 등에 사용. */ function computeStringChecksum_(str) { var s = typeof str === 'string' ? str : JSON.stringify(str); if (s === undefined || s === null) s = ''; var sum = 0; for (var i = 0; i < s.length; i++) { sum = (sum + s.charCodeAt(i)) & 0xFFFFFFFF; } return sum; } // ── 출력 행 빌더 ───────────────────────────────────────────────────────────── function buildHarnessRows_( now, capturedAtIso, intradayLock, snapshotFreshness, snapshotGate, cashFloorInfo, heatGate, heatThresholds, mrsScore, asResult, dfMap, settlementCashPct, totalHeatPct, buyPowerKrw, totalAsset, actions, performance, h2, h3, h4, h5, orderBlueprint, hAlpha, regimeTrimGuidance, cashShortfallInfo, hApex, sectorMomentumRows, drawdownGuard, portfolioBetaGate, eventRiskRows, sectorConcentration, tpLadderRows, regimeSizeScale, regimeCashMinPct, stopAdequacyRows, staleRows, singlePositionWeightCap, semiconductorClusterGate, portfolioDrawdownGate, winLossStreakGuard, positionCountLimit, stopBreachAlert, tpTriggerAlert, heatConcentrationAlert, regimeTransitionAlert, portfolioHealthScore ) { var sourceManifest = [ { name: 'GatherTradingData.json', type: 'JSON', status: 'PENDING_EXPORT' }, { name: 'data_feed', type: 'GOOGLE_SHEETS', status: 'OK' }, { name: 'sector_flow', type: 'GOOGLE_SHEETS', status: 'OK' }, { name: 'macro', type: 'GOOGLE_SHEETS', status: 'OK' }, { name: 'event_risk', type: 'GOOGLE_SHEETS', status: 'OK' }, { name: 'account_snapshot', type: 'GOOGLE_SHEETS', status: 'OK' }, { name: 'backdata_feature_bank', type: 'GOOGLE_SHEETS', status: 'OK' }, { name: 'harness_context', type: 'GOOGLE_SHEETS', status: 'OK' } ]; // ── G1: CASH_SHORTFALL_V1 사전 계산 ───────────────────────────────────── // LLM이 "약 N원 필요" 즉석 계산 금지 — GAS 결정론적 산출 후 잠금 var g1TargetCashPct = cashShortfallInfo.cash_target_pct; var g1ShortfallMin = cashShortfallInfo.cash_shortfall_min_krw; var g1ShortfallTgt = cashShortfallInfo.cash_shortfall_target_krw; var g1CashCurrentPct = cashShortfallInfo.cash_current_pct_d2; // ── G2: TRIM_PLAN_MIN_CASH_V1 사전 계산 ────────────────────────────────── // 현금 회복용 종목별 TRIM 계획 — LLM 즉석 선택 금지, GAS 우선순위 기반 확정 var g2SellQtyMap = {}; h3.sellQty.forEach(function(sq) { g2SellQtyMap[sq.ticker] = sq; }); var g2CloseMap = {}; asResult.holdings.forEach(function(h) { var df = dfMap[h.ticker] || {}; g2CloseMap[h.ticker] = h.close || df.close || 0; }); var g2TrimPlan = []; var g2Accum = 0; var g2Shortfall = g1ShortfallMin; h2.candidates.forEach(function(cand) { var sqRow = g2SellQtyMap[cand.ticker] || {}; var sellQty = sqRow.sell_qty; var close = g2CloseMap[cand.ticker] || 0; var estKrw = 0; if (typeof sellQty === 'number' && sellQty > 0 && close > 0) { estKrw = Math.round(sellQty * close); } g2Accum += estKrw; g2TrimPlan.push({ rank: cand.rank, ticker: cand.ticker, name: cand.name || '', tier: cand.tier, sell_qty: typeof sellQty === 'number' ? sellQty : (sellQty || null), estimated_sell_krw: estKrw, accumulated_krw: g2Accum, covers_shortfall: g2Shortfall > 0 ? g2Accum >= g2Shortfall : true }); }); // ── M4: 5억원 목표 자산 추적 사전 계산 ──────────────────────────────────── var M4_GOAL_KRW = 500000000; var m4Asset = Number.isFinite(totalAsset) ? totalAsset : 0; var m4Achieve = m4Asset > 0 ? Math.round(m4Asset / M4_GOAL_KRW * 1000) / 10 : 0; var m4Remain = Math.max(0, M4_GOAL_KRW - m4Asset); var m4NetExp30 = (performance && Number.isFinite(performance.net_expectancy_30)) ? performance.net_expectancy_30 : null; var m4EtaMonths = null; var m4EtaLabel = 'DATA_MISSING'; if (m4Asset >= M4_GOAL_KRW) { m4EtaMonths = 0; m4EtaLabel = 'ACHIEVED'; } else if (m4Asset > 0 && m4NetExp30 !== null && m4NetExp30 > 0) { m4EtaMonths = Math.ceil(Math.log(M4_GOAL_KRW / m4Asset) / Math.log(1 + m4NetExp30 / 100)); var m4EtaDate = new Date(now.getTime()); m4EtaDate.setMonth(m4EtaDate.getMonth() + m4EtaMonths); m4EtaLabel = m4EtaDate.getFullYear() + '-' + String(m4EtaDate.getMonth() + 1).padStart(2, '0'); } // ── P6: 사용자 판단용 제안표 확정값 (PROPOSAL_REFERENCE_V1) ──────────────── // 보고서가 WATCH/BLOCKED 행을 복원 추론하지 않도록 하네스가 제안 레이어를 직접 잠금 var p6PriceMap = {}; (h4.prices || []).forEach(function(row) { p6PriceMap[row.ticker] = row; }); var p6SellQtyMap = {}; (h3.sellQty || []).forEach(function(row) { p6SellQtyMap[row.ticker] = row; }); var p6BuyQtyMap = {}; (h3.buyQtyInputs || []).forEach(function(row) { p6BuyQtyMap[row.ticker] = row; }); var p6DecisionMap = {}; (h5.decisions || []).forEach(function(row) { p6DecisionMap[row.ticker] = row; }); var p6BlueprintMap = {}; (orderBlueprint || []).forEach(function(row) { p6BlueprintMap[row.ticker] = row; }); var p6TpLadderMap = {}; (tpLadderRows || []).forEach(function(row) { p6TpLadderMap[row.ticker] = row; }); var p6ProfitMap = {}; (((hApex || {}).profit_preservation_json) || []).forEach(function(row) { p6ProfitMap[row.ticker] = row; }); var p6BuyPermissionMap = {}; (((hApex || {}).buy_permission_json) || []).forEach(function(row) { p6BuyPermissionMap[row.ticker] = row; }); var p6AlphaLeadMap = {}; (((hApex || {}).alpha_lead_json) || []).forEach(function(row) { p6AlphaLeadMap[row.ticker] = row; }); var p6SellRankMap = {}; var p6Candidates_ = (h2 && h2.candidates) ? h2.candidates : []; for (var sr = 0; sr < p6Candidates_.length; sr++) { p6SellRankMap[p6Candidates_[sr].ticker] = p6Candidates_[sr].rank; } var p6Tickers = {}; Object.keys(p6PriceMap).forEach(function(t) { p6Tickers[t] = true; }); Object.keys(p6SellQtyMap).forEach(function(t) { p6Tickers[t] = true; }); Object.keys(p6BuyQtyMap).forEach(function(t) { p6Tickers[t] = true; }); Object.keys(p6DecisionMap).forEach(function(t) { p6Tickers[t] = true; }); Object.keys(p6BlueprintMap).forEach(function(t) { p6Tickers[t] = true; }); var p6Rows = []; Object.keys(p6Tickers).sort(function(a, b) { var ra = p6SellRankMap[a] != null ? p6SellRankMap[a] : 9999; var rb = p6SellRankMap[b] != null ? p6SellRankMap[b] : 9999; var da = p6DecisionMap[a] || {}; var db = p6DecisionMap[b] || {}; var oa = p6BlueprintMap[a] || {}; var ob = p6BlueprintMap[b] || {}; var actionA = String(da.final_action || oa.order_type || 'WATCH').toUpperCase(); var actionB = String(db.final_action || ob.order_type || 'WATCH').toUpperCase(); function bucket_(action) { if (action.indexOf('SELL') >= 0 || action.indexOf('TRIM') >= 0 || action.indexOf('EXIT') >= 0 || action.indexOf('STOP_LOSS') >= 0 || action.indexOf('TAKE_PROFIT') >= 0 || action.indexOf('TRAILING_STOP') >= 0) return 0; if (action.indexOf('BUY') >= 0 || action.indexOf('ADD_ON') >= 0 || action.indexOf('PILOT') >= 0 || action.indexOf('STAGED') >= 0) return 1; if (action.indexOf('WATCH') >= 0 || action.indexOf('HOLD') >= 0) return 2; return 3; } var ba = bucket_(actionA); var bb = bucket_(actionB); if (ba !== bb) return ba - bb; if (ra !== rb) return ra - rb; if (ba === 1 || ba === 2) { var buyStateOrder_ = { ALLOW_ADD_ON: 0, ALLOW_PILOT: 1, WATCH: 2, BLOCKED: 3 }; var buyA = p6BuyPermissionMap[a] || {}; var buyB = p6BuyPermissionMap[b] || {}; var alphaA = p6AlphaLeadMap[a] || {}; var alphaB = p6AlphaLeadMap[b] || {}; var sa = buyStateOrder_[String(buyA.buy_permission_state || '').toUpperCase()] || 99; var sb = buyStateOrder_[String(buyB.buy_permission_state || '').toUpperCase()] || 99; if (sa !== sb) return sa - sb; var aa = -(typeof alphaA.alpha_lead_score === 'number' ? alphaA.alpha_lead_score : 0); var ab = -(typeof alphaB.alpha_lead_score === 'number' ? alphaB.alpha_lead_score : 0); if (aa !== ab) return aa - ab; } return a < b ? -1 : (a > b ? 1 : 0); }).forEach(function(ticker) { var p = p6PriceMap[ticker] || {}; var s = p6SellQtyMap[ticker] || {}; var b = p6BuyQtyMap[ticker] || {}; var d = p6DecisionMap[ticker] || {}; var o = p6BlueprintMap[ticker] || {}; var t = p6TpLadderMap[ticker] || {}; var pp = p6ProfitMap[ticker] || {}; var finalAction = String(d.final_action || o.order_type || 'WATCH').toUpperCase(); var orderType = String(o.order_type || '').toUpperCase(); var priorityGroup = 3; var priorityRank = 9999; if (finalAction.indexOf('SELL') >= 0 || finalAction.indexOf('TRIM') >= 0 || finalAction.indexOf('EXIT') >= 0 || finalAction.indexOf('STOP_LOSS') >= 0 || finalAction.indexOf('TAKE_PROFIT') >= 0 || finalAction.indexOf('TRAILING_STOP') >= 0) { priorityGroup = 0; priorityRank = p6SellRankMap[ticker] != null ? p6SellRankMap[ticker] : priorityRank; } else if (finalAction.indexOf('BUY') >= 0 || finalAction.indexOf('ADD_ON') >= 0 || finalAction.indexOf('PILOT') >= 0 || finalAction.indexOf('STAGED') >= 0) { priorityGroup = 1; var bp = p6BuyPermissionMap[ticker] || {}; var ap = p6AlphaLeadMap[ticker] || {}; var buyStateOrder_ = { ALLOW_ADD_ON: 0, ALLOW_PILOT: 1, WATCH: 2, BLOCKED: 3 }; priorityRank = 10000 + ((buyStateOrder_[String(bp.buy_permission_state || '').toUpperCase()] || 99) * 1000) + (100 - (typeof ap.alpha_lead_score === 'number' ? ap.alpha_lead_score : 0)); } else if (finalAction.indexOf('WATCH') >= 0 || finalAction.indexOf('HOLD') >= 0) { priorityGroup = 2; var hp = p6BuyPermissionMap[ticker] || {}; var ha = p6AlphaLeadMap[ticker] || {}; var holdStateOrder_ = { ALLOW_ADD_ON: 0, ALLOW_PILOT: 1, WATCH: 2, BLOCKED: 3 }; priorityRank = 20000 + ((holdStateOrder_[String(hp.buy_permission_state || '').toUpperCase()] || 99) * 1000) + (100 - (typeof ha.alpha_lead_score === 'number' ? ha.alpha_lead_score : 0)); } var proposalType = '관찰 제안'; var priceBasis = '하네스 기준 참고가'; var qtyBasis = '수량 입력 없음'; var proposedLimit = null; var proposedTp = p.tp1_price || p.tp2_price || null; var proposedQty = null; if (finalAction.indexOf('BUY') >= 0 || orderType.indexOf('BUY') >= 0 || b.final_qty != null) { proposalType = '매수 제안'; proposedLimit = o.limit_price_krw != null ? o.limit_price_krw : (b.entry_price_hint || null); priceBasis = '매수 제안가 우선'; proposedQty = b.final_qty != null ? b.final_qty : null; qtyBasis = '매수 수량 우선'; } else if (finalAction.indexOf('TAKE_PROFIT') >= 0 || orderType.indexOf('TAKE_PROFIT') >= 0) { proposalType = '익절 제안'; proposedLimit = p.tp1_price || p.tp2_price || null; priceBasis = '익절가 우선'; proposedQty = s.sell_qty != null ? s.sell_qty : null; qtyBasis = '매도 수량 우선'; } else if (s.sell_qty != null || ['SELL_READY', 'SELL', 'TRIM', 'EXIT_100', 'EXIT_FULL'].indexOf(finalAction) >= 0) { proposalType = (finalAction === 'WATCH' || finalAction === 'HOLD') ? '관찰 제안' : '매도 제안'; proposedLimit = p.stop_price != null ? p.stop_price : null; priceBasis = (finalAction === 'WATCH' || finalAction === 'HOLD') ? '주문가 아님: 참고 방어가' : '방어가 우선'; proposedQty = s.sell_qty != null ? s.sell_qty : null; qtyBasis = (finalAction === 'WATCH' || finalAction === 'HOLD') ? '주문 수량 아님: 참고 수량' : '매도 수량 우선'; } else if (finalAction === 'WATCH' || finalAction === 'HOLD' || orderType === 'WATCH') { proposalType = '관찰 제안'; proposedLimit = p.stop_price != null ? p.stop_price : null; priceBasis = '주문가 아님: 참고 방어가'; proposedQty = s.sell_qty != null ? s.sell_qty : null; qtyBasis = '주문 수량 아님: 참고 수량'; } if (proposedLimit == null && proposedQty == null && p.stop_price == null && proposedTp == null) return; var executionStatus = 'EXECUTION_WAIT'; if (String(o.validation_status || '') === 'PASS') { executionStatus = 'EXECUTION_READY'; } else if (finalAction === 'WATCH' || finalAction === 'HOLD' || orderType === 'WATCH') { executionStatus = 'PROPOSAL_ONLY'; } var blockReason = o.rationale_code || '하네스 기준 제안 유지'; if (!o.rationale_code && Array.isArray(d.gate_trace) && d.gate_trace.length) { blockReason = d.gate_trace[d.gate_trace.length - 1].reason || blockReason; } var positionClass = String(p.position_class || '').toLowerCase(); var baseStopQty = s.holding_qty != null ? s.holding_qty : proposedQty; var stop1Qty = null; var stop2Qty = null; if (typeof baseStopQty === 'number' && baseStopQty > 0) { var stop1Ratio = positionClass === 'core' ? 0.50 : 0.70; stop1Qty = Math.floor(baseStopQty * stop1Ratio); if (stop1Qty <= 0) stop1Qty = 1; if (stop1Qty > baseStopQty) stop1Qty = baseStopQty; stop2Qty = baseStopQty - stop1Qty; if (stop2Qty <= 0) stop2Qty = null; } var stop3Price = null; if (pp.auto_trailing_stop != null) { stop3Price = pp.auto_trailing_stop; } else if (String(p.profit_lock_stage || 'NORMAL') !== 'NORMAL' && pp.protected_stop_price != null) { stop3Price = pp.protected_stop_price; } var stop3Qty = null; if (stop3Price != null) { stop3Qty = t.tp3_qty != null ? t.tp3_qty : (p.tp3_qty != null ? p.tp3_qty : null); if (stop3Qty == null && typeof baseStopQty === 'number' && baseStopQty > 0) { var tp1Qty = t.tp1_qty != null ? t.tp1_qty : (p.tp1_qty != null ? p.tp1_qty : 0); var tp2Qty = t.tp2_qty != null ? t.tp2_qty : (p.tp2_qty != null ? p.tp2_qty : 0); var residualQty = baseStopQty - tp1Qty - tp2Qty; stop3Qty = residualQty > 0 ? residualQty : null; } } p6Rows.push({ account: o.account || s.account || b.account || p.account || d.account || '', ticker: ticker, name: o.name || s.name || b.name || p.name || d.name || '', proposal_type: proposalType, proposed_limit_price_krw: proposedLimit, proposed_price_basis: priceBasis, proposed_quantity: proposedQty, proposed_quantity_basis: qtyBasis, priority_group: priorityGroup, priority_rank: priorityRank, proposed_stop_price_krw: p.stop_price != null ? p.stop_price : null, stop1_price_krw: p.stop_price != null ? p.stop_price : null, stop1_quantity: stop1Qty, stop2_price_krw: stop2Qty != null ? p.stop_price : null, stop2_quantity: stop2Qty, stop3_price_krw: stop3Price, stop3_quantity: stop3Qty, tp1_price_krw: t.tp1_price != null ? t.tp1_price : (p.tp1_price != null ? p.tp1_price : null), tp1_quantity: t.tp1_qty != null ? t.tp1_qty : (p.tp1_qty != null ? p.tp1_qty : null), tp2_price_krw: t.tp2_price != null ? t.tp2_price : (p.tp2_price != null ? p.tp2_price : null), tp2_quantity: t.tp2_qty != null ? t.tp2_qty : (p.tp2_qty != null ? p.tp2_qty : null), tp3_price_krw: null, tp3_quantity: t.tp3_qty != null ? t.tp3_qty : (p.tp3_qty != null ? p.tp3_qty : null), execution_status: executionStatus, block_reason: blockReason }); }); return [ // ── 메타 ───────────────────────────────────────────────────────── ['harness_version', HARNESS_VERSION], ['computed_at', formatIso_(now)], // [PROPOSAL50] P0-2: ROUTING_TRACE_V1 동적값 — 정적 하드코딩 제거 ['request_route', ((hApex || {}).routing_trace_json || {}).request_route || 'PIPELINE_EOD_BATCH'], ['route_reason_code', 'RUN_EVENT_RISK_CHAIN'], ['bundle_selected', ((hApex || {}).routing_trace_json || {}).bundle_selected || 'retirement_portfolio_ultra_compact'], ['prompt_entrypoint', ((hApex || {}).routing_trace_json || {}).prompt_entrypoint || 'prompts/analysis_prompt.md'], // [PROPOSAL50] P0-1: EXPORT_GATE_V1 동적값 — PENDING_EXPORT 정적 하드코딩 제거 ['json_validation_status', (hApex || {}).json_validation_status || 'PENDING_EXPORT'], ['capture_required', String(((hApex || {}).routing_trace_json || {}).capture_required != null ? (hApex.routing_trace_json.capture_required) : true)], ['cash_ledger_basis', ((hApex || {}).routing_trace_json || {}).cash_ledger_basis || 'D2_ONLY'], ['source_manifest_json', JSON.stringify(sourceManifest)], // ── H1: P4 가드 ─────────────────────────────────────────────── ['captured_at', capturedAtIso], ['intraday_lock', intradayLock], ['snapshot_execution_gate', snapshotGate.status], ['snapshot_execution_reason', snapshotGate.reason], ['account_snapshot_freshness_json', JSON.stringify(snapshotFreshness || {})], ['intraday_lock_reason', intradayLock ? 'captured_at < 15:30 KST — P4 적용: EXIT_100/SELL_FULL/BUY 차단' : 'captured_at >= 15:30 KST — 정상 장마감 데이터'], ['p4_guard', intradayLock ? 'ACTIVE' : 'INACTIVE'], // ── H1: 현금 (P3 가드) ──────────────────────────────────────── ['immediate_cash_krw', asResult.immediateCashKrw], ['settlement_cash_d2_krw', asResult.settlementCashD2Krw], ['open_order_amount_krw', asResult.openOrderAmountKrw], ['buy_power_krw', buyPowerKrw], ['total_asset_krw', totalAsset], ['settlement_cash_pct', settlementCashPct], ['p3_guard', 'ACTIVE — settlement_cash_d2_krw only. ' + 'cash_floor 및 buy_power_krw 는 D+2 정산현금 단독 기준. ' + 'immediate_cash_krw 는 참고값이며 cash ledger 합산 금지.'], // ── H1: cash_floor ──────────────────────────────────────────── ['cash_floor_min_pct', cashFloorInfo.minPct], ['cash_floor_regime', cashFloorInfo.regime], ['cash_floor_status', cashFloorInfo.status], // ── G1: 현금 부족액 / 목표현금 확정값 (CASH_SHORTFALL_V1) ───────────────── // LLM 즉석 계산 금지: "약 N원 필요" 는 이 필드 복사만 허용 ['cash_current_pct_d2', g1CashCurrentPct], ['cash_target_pct', g1TargetCashPct], ['cash_shortfall_min_krw', g1ShortfallMin], ['cash_shortfall_target_krw', g1ShortfallTgt], // ── G2: 현금 회복 TRIM 계획 (TRIM_PLAN_MIN_CASH_V1) ────────────────────── // 매도우선순위(H2) 기반 종목별 TRIM 순서·예상금액 하네스 확정 — LLM 임의 선택 금지 ['trim_plan_to_min_cash_json', JSON.stringify(g2TrimPlan)], ['mrs_score', mrsScore], ['performance_multiplier', performance.bayesian_multiplier], ['performance_label', performance.bayesian_label], ['performance_win_rate_30', performance.win_rate_30], ['performance_net_expectancy_30', performance.net_expectancy_30], ['performance_consecutive_losses', performance.consecutive_losses], ['performance_trades_used', performance.trades_used], // ── H1: Total Heat ──────────────────────────────────────────── ['total_heat_krw', Math.round(asResult.totalHeatKrw)], ['total_heat_pct', totalHeatPct], ['total_heat_atr_estimated', asResult.heatAtrEstimated], ['total_heat_rows_counted', asResult.heatRowsCount], ['heat_gate_status', heatGate], ['heat_gate_threshold_pct', heatThresholds ? heatThresholds.hardBlock : HEAT_HARD_BLOCK_PCT], // ── H1: 허용/차단 액션 ──────────────────────────────────────── ['allowed_actions', JSON.stringify(actions.allowed)], ['blocked_actions', JSON.stringify(actions.blocked)], // ── H2: 매도후보 순위 ───────────────────────────────────────── ['sell_candidates_json', JSON.stringify(h2.candidates)], ['sell_priority_checksum', computeStringChecksum_(JSON.stringify(((h2 && h2.candidates) || []).map(function(c) { return { rank: c.rank, ticker: c.ticker, tier: c.tier, score: (typeof c.sell_priority_score === 'number') ? c.sell_priority_score : c.score }; })))], ['sell_priority_lock', 'true'], ['sell_priority_computed_at', formatIso_(now)], ['sell_candidates_count', ((h2 && h2.candidates) ? h2.candidates.length : 0)], ['sell_priority_leader_holdback', JSON.stringify(((h2 && h2.candidates) || []).map(function(c) { return { ticker: c.ticker, rank: c.rank, tier: c.tier, sell_priority_score: c.sell_priority_score, rebound_holdback_score: c.rebound_holdback_score || 0, trim_style: c.trim_style || '', cash_preserve_style: c.cash_preserve_style || '', cash_preserve_ratio: c.cash_preserve_ratio || 0, }; }))], // ── H3: 수량 ───────────────────────────────────────────────── ['sell_quantities_json', JSON.stringify(h3.sellQty)], ['buy_qty_inputs_json', JSON.stringify(h3.buyQtyInputs)], ['quantities_lock', 'true'], // ── H4: 가격 ───────────────────────────────────────────────── ['prices_json', JSON.stringify(h4.prices)], ['prices_lock', 'true'], // ── H5: 결정 상태머신 ───────────────────────────────────────── ['decisions_json', JSON.stringify(h5.decisions)], ['decision_trace_json', (function() { var full = JSON.stringify(h5.traces || []); if (full.length <= 45000) return full; // blocked_actions / inputs_used 는 전 항목 공통값 반복 → 제거해 압축 var slim = (h5.traces || []).map(function(t) { return { ticker: t.ticker, state: t.state, result: t.result, selected_action: t.selected_action, reason: t.reason }; }); return JSON.stringify(slim); })()], ['decision_lock', 'true'], // ── H6: HTS 주문 렌더링 + Blueprint 무결성 해시 ───────────────── ['order_blueprint_json', JSON.stringify(orderBlueprint)], ['blueprint_row_count', (orderBlueprint || []).length], ['blueprint_checksum', computeBlueprintChecksum_(orderBlueprint)], ['blueprint_hash_algo', 'CRC32_V1'], ['render_validation_status', 'READY'], ['proposal_reference_json', JSON.stringify(p6Rows)], ['proposal_reference_lock', 'true'], // ── I3: CHECKSUM_V2 — 결정론적 체크섬 강화 ────────────────────────────── // 동일 입력/기준시각에서 네 체크섬이 모두 일치해야 NON_DETERMINISTIC_OUTPUT 방지 ['source_manifest_checksum', computeStringChecksum_(JSON.stringify(sourceManifest))], ['decision_trace_checksum', computeStringChecksum_(JSON.stringify(h5.traces))], // ── [2026-05-20_HARNESS_V5] 신규 체크섬 ───────────────────────────────── // input_snapshot_checksum: 계좌 캡처 원장(보유수량·평단·현금)의 스냅샷 해시. // 동일 입력 재호출 시 이 값이 변하면 데이터 소스가 갱신된 것이다. ['input_snapshot_checksum', computeInputSnapshotChecksum_(asResult, capturedAtIso)], // rendered_output_checksum: blueprint와 동일한 주문 행 해시 (canonical). ['rendered_output_checksum', computeBlueprintChecksum_(orderBlueprint)], // rendered_report_checksum: legacy alias. 신규 검증은 rendered_output_checksum 우선. ['rendered_report_checksum', computeBlueprintChecksum_(orderBlueprint)], // non_deterministic_flag: Python 검증기가 이전 실행값과 비교 후 설정. GAS는 항상 false. ['non_deterministic_flag', 'false'], ['checksum_hash_algo', 'CRC32_V1'], // ── Alpha-Shield: X1/X3/W1~W4 선행 레이더 ─────────────────── ['alpha_shield_json', JSON.stringify((hAlpha || {}).per_holding || [])], ['alpha_shield_lock', 'true'], ['alpha_shield_critical_alert_count', String((hAlpha || {}).critical_alert_count || 0)], ['alpha_shield_critical_alert_flag', ((hAlpha || {}).critical_alert_count || 0) > 0 ? 'CRITICAL' : 'OK'], ['alpha_shield_computed_at', formatIso_(now)], ['alpha_shield_formula_ids', 'MRG001[X1],RS001[X3],W1_DIVERGENCE,W2_OVERHANG,W3_ROTATION,W4_FLOW_ACCEL'], // ── APEX V1: 판단 자료 생성 시점 로직 하네스 ───────────────────────────── // 텍스트 가이드라인이 아니라 GAS가 직접 산출한 매수/매도/현금확보 실행 판단 자료 ['alpha_lead_json', JSON.stringify((hApex || {}).alpha_lead_json || [])], ['alpha_lead_lock', 'true'], ['backdata_feature_bank_json', JSON.stringify(((hApex || {}).backdata_feature_bank_json || []).slice(-50))], ['backdata_learning_lock', 'true'], ['follow_through_json', JSON.stringify((hApex || {}).follow_through_json || [])], ['follow_through_lock', 'true'], ['distribution_risk_json', JSON.stringify((hApex || {}).distribution_risk_json || [])], ['distribution_lock', 'true'], ['profit_preservation_json', JSON.stringify((hApex || {}).profit_preservation_json || [])], ['profit_preservation_lock', 'true'], ['cash_raise_plan_json', JSON.stringify((hApex || {}).cash_raise_plan_json || [])], ['rebound_sell_trigger_json', JSON.stringify((hApex || {}).rebound_sell_trigger_json || [])], ['smart_sell_quantities_json', JSON.stringify((hApex || {}).smart_sell_quantities_json || [])], ['smart_cash_raise_lock', 'true'], ['execution_quality_json', JSON.stringify((hApex || {}).execution_quality_json || [])], ['execution_quality_lock', 'true'], ['buy_permission_json', JSON.stringify((hApex || {}).buy_permission_json || [])], ['limit_price_policy_json', JSON.stringify((hApex || {}).limit_price_policy_json || [])], ['regime_adjusted_sell_priority_json', JSON.stringify((hApex || {}).regime_adjusted_sell_priority_json || [])], ['benchmark_relative_timeseries_json', JSON.stringify((hApex || {}).benchmark_relative_timeseries_json || [])], ['index_relative_health_json', JSON.stringify((hApex || {}).index_relative_health_json || [])], ['saqg_json', JSON.stringify((hApex || {}).saqg_json || [])], ['cash_creation_purpose_lock_json', JSON.stringify((hApex || {}).cash_creation_purpose_lock_json || [])], ['alpha_feedback_json', JSON.stringify((hApex || {}).alpha_feedback_json || {})], ['alpha_evaluation_window_json', JSON.stringify((hApex || {}).alpha_evaluation_window_json || [])], ['entry_freshness_json', JSON.stringify((hApex || {}).entry_freshness_json || [])], ['sell_value_preservation_json', JSON.stringify((hApex || {}).sell_value_preservation_json || [])], // ── [2026-05-20_HARNESS_V5] Gate 4b: FTD 확인 상태 잠금 ['follow_through_confirm_json', JSON.stringify((hApex || {}).follow_through_confirm_json || [])], ['follow_through_confirm_lock', 'true'], // L1: 섹터 로테이션 모멘텀 ['sector_rotation_momentum_json', JSON.stringify(sectorMomentumRows || [])], ['sector_rotation_momentum_lock', 'true'], // ── M1: DRAWDOWN_GUARD_V1 ──────────────────────────────────── ['drawdown_guard_state', (drawdownGuard || {}).state || 'NORMAL'], ['drawdown_buy_scale', (drawdownGuard || {}).buy_scale !== undefined ? (drawdownGuard || {}).buy_scale : 1.0], ['drawdown_consecutive_losses', (drawdownGuard || {}).consecutive_losses || 0], // ── M2: PORTFOLIO_BETA_GATE_V1 ────────────────────────────── ['portfolio_beta', (portfolioBetaGate || {}).portfolio_beta !== null ? (portfolioBetaGate || {}).portfolio_beta : 'N/A'], ['portfolio_beta_gate', (portfolioBetaGate || {}).gate_status || 'INSUFFICIENT_DATA'], ['portfolio_beta_gate_json', JSON.stringify(portfolioBetaGate || {})], // ── M3: TP_QUANTITY_LADDER_V1 ─────────────────────────────── ['tp_quantity_ladder_json', JSON.stringify(tpLadderRows || [])], ['tp_quantity_ladder_lock', 'true'], // ── M4: EVENT_RISK_HOLD_GATE_V1 ───────────────────────────── ['event_risk_json', JSON.stringify(eventRiskRows || [])], ['event_risk_lock', 'true'], // ── M5: SECTOR_CONCENTRATION_LIMIT_V1 ─────────────────────── ['sector_concentration_gate', (sectorConcentration || {}).gate_status || 'PASS'], ['sector_concentration_json', JSON.stringify((sectorConcentration || {}).by_sector || [])], // ── N1: POSITION_SIZE_REGIME_SCALE_V1 ─────────────────────── ['regime_size_scale', (regimeSizeScale || {}).scale !== undefined ? (regimeSizeScale || {}).scale : 1.0], // ── N3: STOP_PRICE_ADEQUACY_V1 ────────────────────────────── ['stop_adequacy_json', JSON.stringify(stopAdequacyRows || [])], ['stop_adequacy_lock', 'true'], // ── N4: HOLDING_STALE_REVIEW_V1 ───────────────────────────── ['holding_stale_json', JSON.stringify(staleRows || [])], ['holding_stale_lock', 'true'], // ── N5: REGIME_CASH_UPLIFT_V1 ─────────────────────────────── ['regime_cash_uplift_min_pct', typeof regimeCashMinPct === 'number' ? regimeCashMinPct : cashFloorInfo.minPct], // ── O1: SINGLE_POSITION_WEIGHT_CAP_V1 ─────────────────────── ['single_position_weight_gate', (singlePositionWeightCap || {}).gate_status || 'PASS'], ['single_position_weight_json', JSON.stringify((singlePositionWeightCap || {}).by_position || [])], // ── O2: SEMICONDUCTOR_CLUSTER_GATE_V1 ─────────────────────── ['semiconductor_cluster_gate', (semiconductorClusterGate || {}).gate_status || 'PASS'], ['semiconductor_cluster_json', JSON.stringify(semiconductorClusterGate || {})], // ── O3: PORTFOLIO_DRAWDOWN_GATE_V1 ────────────────────────── ['portfolio_drawdown_gate', (portfolioDrawdownGate || {}).gate || 'INSUFFICIENT_DATA'], ['portfolio_drawdown_pct', (portfolioDrawdownGate || {}).drawdown_pct !== null ? (portfolioDrawdownGate || {}).drawdown_pct : null], ['portfolio_peak_krw', (portfolioDrawdownGate || {}).peak_krw || null], // ── O4: WIN_LOSS_STREAK_GUARD_V1 ──────────────────────────── ['win_loss_streak_state', (winLossStreakGuard || {}).state || 'INSUFFICIENT_HISTORY'], ['win_loss_streak_buy_scale', (winLossStreakGuard || {}).buy_scale !== undefined ? (winLossStreakGuard || {}).buy_scale : 1.0], ['win_loss_streak_win_rate_pct', (winLossStreakGuard || {}).win_rate_pct !== null ? (winLossStreakGuard || {}).win_rate_pct : null], // ── O5: POSITION_COUNT_LIMIT_V1 ───────────────────────────── ['position_count_gate', (positionCountLimit || {}).gate_status || 'PASS'], ['position_count', (positionCountLimit || {}).position_count !== undefined ? (positionCountLimit || {}).position_count : 0], ['position_count_max', (positionCountLimit || {}).max_count !== undefined ? (positionCountLimit || {}).max_count : 8], // ── P1: STOP_BREACH_ALERT_V1 ───────────────────────────────── ['stop_breach_gate', (stopBreachAlert || {}).gate || 'PASS'], ['stop_breach_alert_json', JSON.stringify((stopBreachAlert || {}).alerts || [])], // ── P1-BIS: RELATIVE_STOP_SIGNAL_V1 ───────────────────────── ['relative_stop_gate', ((hApex || {}).relative_stop_signal || {}).gate || 'PASS'], ['relative_stop_signal_json', JSON.stringify(((hApex || {}).relative_stop_signal || {}).signals || [])], // ── P2: TP_TRIGGER_ALERT_V1 ────────────────────────────────── ['tp_trigger_gate', (tpTriggerAlert || {}).gate || 'PASS'], ['tp_trigger_alert_json', JSON.stringify((tpTriggerAlert || {}).triggered || [])], // ── P3: HEAT_CONCENTRATION_ALERT_V1 ───────────────────────── ['heat_concentration_gate', (heatConcentrationAlert || {}).gate || 'PASS'], ['heat_concentration_json', JSON.stringify((heatConcentrationAlert || {}).by_holding || [])], // ── P4: REGIME_TRANSITION_ALERT_V1 ────────────────────────── ['regime_transition_type', (regimeTransitionAlert || {}).transition_type || 'NO_CHANGE'], ['regime_transition_json', JSON.stringify(regimeTransitionAlert || {})], // ── P5: PORTFOLIO_HEALTH_SCORE_V1 ──────────────────────────── ['portfolio_health_label', (portfolioHealthScore || {}).label || 'CAUTION'], ['portfolio_health_score', (portfolioHealthScore || {}).score !== undefined ? (portfolioHealthScore || {}).score : 50], ['portfolio_health_critical_count', (portfolioHealthScore || {}).critical_count || 0], ['portfolio_health_caution_count', (portfolioHealthScore || {}).caution_count || 0], ['portfolio_health_blocked_json', JSON.stringify((portfolioHealthScore || {}).blocked_gates || [])], // ── [2026-05-20_HARNESS_V5] H6/H7/H8 신규 게이트 ──────────────────── ['breakout_quality_gate_json', JSON.stringify((hApex || {}).breakout_quality_gate_json || [])], ['breakout_quality_gate_lock', 'true'], ['anti_whipsaw_gate_json', JSON.stringify((hApex || {}).anti_whipsaw_gate_json || [])], ['anti_whipsaw_gate_lock', 'true'], ['smart_cash_raise_json', JSON.stringify((hApex || {}).smart_cash_raise_json || [])], ['smart_cash_raise_route', (hApex || {}).smart_cash_raise_route || 'NO_ACTION'], // ── [2026-05-21_CLA_HARNESS_V1] SFG 하네스 출력 ────────────────────────── ['satellite_failure_gate_json', JSON.stringify((hApex || {}).satellite_failure_gate_json || {})], ['sapg_json', JSON.stringify((hApex || {}).sapg_json || {})], ['sfg_v1_lock', 'true'], // ── [SPRINT2_REGIME_CLA_V1] CONCENTRATED_LEADER_ADVANCE 게이트 ────────── ['regime_cla_json', (function() { var phase = (regimeTrimGuidance || {}).phase || 'UNKNOWN'; var cla_active = phase === 'CONCENTRATED_LEADER_ADVANCE'; var sc = semiconductorClusterGate || {}; var combined_pct = sc.combined_pct || 0; var cluster_state = cla_active ? 'CLUSTER_HOLD_ONLY' : (sc.cluster_state || 'CLUSTER_OPEN'); var cla_exit = cla_active ? 'CLA_ACTIVE' : 'CLA_EXIT_CONFIRMED'; var rag_pass = !cla_active || combined_pct < 60.0; return JSON.stringify({ cla_active: cla_active, market_regime: phase, cluster_state: cluster_state, cluster_combined_pct: combined_pct, cla_exit_status: cla_exit, core_sell_blocked: cla_active, satellite_buy_gate: (cla_active && combined_pct >= 60.0) ? 'CLUSTER_HOLD_ONLY' : 'CLUSTER_OPEN', cash_raise_priority: cla_active ? 'LAGGARD_BROKEN_FIRST' : 'H2_RANK', rag_v1: rag_pass ? 'PASS' : 'FAIL', rag_reason: rag_pass ? 'CLA 비활성 또는 반도체 합산 비중 60% 미만 — 위성 BUY 허용' : 'CLA 활성 + 반도체 합산 비중 ≥60% — 위성 신규 BUY 차단', formula_id: 'CONCENTRATED_LEADER_ADVANCE_V1', }); })()], ['cla_exit_status', (function() { var phase = (regimeTrimGuidance || {}).phase || 'UNKNOWN'; return phase === 'CONCENTRATED_LEADER_ADVANCE' ? 'CLA_ACTIVE' : 'CLA_EXIT_CONFIRMED'; })()], ['rag_v1', (function() { var phase = (regimeTrimGuidance || {}).phase || 'UNKNOWN'; var cla_active = phase === 'CONCENTRATED_LEADER_ADVANCE'; var combined_pct = (semiconductorClusterGate || {}).combined_pct || 0; return (!cla_active || combined_pct < 60.0) ? 'PASS' : 'FAIL'; })()], ['rag_reason', (function() { var phase = (regimeTrimGuidance || {}).phase || 'UNKNOWN'; var cla_active = phase === 'CONCENTRATED_LEADER_ADVANCE'; var combined_pct = (semiconductorClusterGate || {}).combined_pct || 0; if (!cla_active) return 'CLA 비활성 — RAG 조건 불필요'; return combined_pct < 60.0 ? 'CLA 활성이나 반도체 합산 60% 미만 — 위성 BUY 허용' : 'CLA 활성 + 반도체 합산 ≥60% — 위성 신규 BUY 차단'; })()], ['apex_formula_ids', 'ALPHA_LEAD_SCORE_V1,FOLLOW_THROUGH_CONFIRM_V1,DISTRIBUTION_RISK_SCORE_V1,' + 'PROFIT_PRESERVATION_STATE_V1,SMART_CASH_RAISE_PLAN_V1,REBOUND_SELL_TRIGGER_V1,' + 'EXECUTION_QUALITY_GUARD_V1,BUY_PERMISSION_MATRIX_V1,SELL_QUANTITY_ALLOCATOR_V1,' + 'LIMIT_PRICE_POLICY_V1,STAGED_ENTRY_TRANCHE_V1,K2_STAGED_REBOUND_SELL,K3_REGIME_SELL_PRIORITY_V1,' + 'SECTOR_ROTATION_MOMENTUM_V1,RATCHET_TRAILING_AUTO_V1,PRE_DISTRIBUTION_EARLY_WARNING_V1,' + 'DYNAMIC_HEAT_GATE_V1,DRAWDOWN_GUARD_V1,PORTFOLIO_BETA_GATE_V1,TP_QUANTITY_LADDER_V1,' + 'EVENT_RISK_HOLD_GATE_V1,SECTOR_CONCENTRATION_LIMIT_V1,' + 'POSITION_SIZE_REGIME_SCALE_V1,VOLUME_BREAKOUT_CONFIRM_V1,STOP_PRICE_ADEQUACY_V1,' + 'HOLDING_STALE_REVIEW_V1,REGIME_CASH_UPLIFT_V1,' + 'SINGLE_POSITION_WEIGHT_CAP_V1,SEMICONDUCTOR_CLUSTER_GATE_V1,' + 'PORTFOLIO_DRAWDOWN_GATE_V1,WIN_LOSS_STREAK_GUARD_V1,POSITION_COUNT_LIMIT_V1,' + 'STOP_BREACH_ALERT_V1,TP_TRIGGER_ALERT_V1,HEAT_CONCENTRATION_ALERT_V1,' + 'REGIME_TRANSITION_ALERT_V1,PORTFOLIO_HEALTH_SCORE_V1,' + 'BREAKOUT_QUALITY_GATE_V2,ANTI_WHIPSAW_HOLD_GATE_V1,SMART_CASH_RAISE_V2,' + 'BENCHMARK_RELATIVE_TIMESERIES_V1,RS_VERDICT_V2,SATELLITE_ALPHA_QUALITY_GATE_V1,' + 'CASH_CREATION_PURPOSE_LOCK_V1,SATELLITE_AGGREGATE_PNL_GATE_V1,ALPHA_EVALUATION_WINDOW_V1,' + 'ALPHA_FEEDBACK_LOOP_V1,ENTRY_FRESHNESS_GATE_V1,SELL_VALUE_PRESERVATION_GATE_V1,' + 'INDEX_RELATIVE_HEALTH_GATE_V1,' + 'RS_VERDICT_V1,COMPOSITE_VERDICT_V1,REPLACEMENT_ALPHA_GATE_V1,SATELLITE_FAILURE_GATE_V1,' + 'CONCENTRATED_LEADER_ADVANCE_V1,' // ── [2026-05-23_PROPOSAL46] PA1~PA5 + 'PREDICTIVE_ALPHA_ENGINE_V1,ANTI_LATE_ENTRY_GATE_V2,CASH_PRESERVATION_SELL_ENGINE_V2,' + 'MACRO_EVENT_SYNCHRONIZER_V1,CONSISTENCY_VALIDATOR_V2'], // ── [2026-05-23_PROPOSAL46] PA1~PA5 신규 하네스 출력 ───────────────────────── ['predictive_alpha_json', JSON.stringify((hApex || {}).predictive_alpha_json || [])], ['anti_late_entry_json', JSON.stringify((hApex || {}).anti_late_entry_json || [])], ['cash_preservation_sell_json', JSON.stringify((hApex || {}).cash_preservation_sell_json || [])], ['macro_event_json', JSON.stringify((hApex || {}).macro_event_json || {})], ['macro_risk_score', (hApex || {}).macro_risk_score !== undefined ? String((hApex || {}).macro_risk_score) : ''], ['macro_risk_regime', (hApex || {}).macro_risk_regime || ''], ['mega_sell_alert', (hApex || {}).mega_sell_alert === true ? 'true' : 'false'], ['consistency_report_json', JSON.stringify((hApex || {}).consistency_report_json || {})], ['consistency_score', (hApex || {}).consistency_score !== undefined ? String((hApex || {}).consistency_score) : ''], ['cv_verdict', (hApex || {}).cv_verdict || ''], ['portfolio_alpha_confidence', (hApex || {}).portfolio_alpha_confidence !== null && (hApex || {}).portfolio_alpha_confidence !== undefined ? String((hApex || {}).portfolio_alpha_confidence) : ''], ['fomc_position_size_gate', (hApex || {}).fomc_position_size_gate || 'INACTIVE'], ['prediction_accuracy_rate', (hApex || {}).prediction_accuracy_rate !== null && (hApex || {}).prediction_accuracy_rate !== undefined ? String((hApex || {}).prediction_accuracy_rate) : ''], ['watch_breakout_candidates_json', JSON.stringify((hApex || {}).watch_breakout_candidates_json || [])], ['anti_whipsaw_reentry_json', JSON.stringify((hApex || {}).anti_whipsaw_reentry_json || [])], ['alpha_history_summary_json', JSON.stringify((hApex || {}).alpha_history_summary_json || {})], // ── P4 허용 목록 (LLM 참조용) ──────────────────────────────── ['p4_intraday_allowed_actions', JSON.stringify(INTRADAY_ALLOWED_ACTIONS)], // ── M1: 국면별 감축 가이던스 (REGIME_TRIM_WEIGHT_V1) ────────── // LLM의 주관적 국면 판단 및 임의 감축비율 산출을 차단 ['market_regime_state', (regimeTrimGuidance || {}).phase || 'UNKNOWN'], ['regime_trim_guidance_json', JSON.stringify(regimeTrimGuidance || {})], ['regime_trim_lock', 'true'], // ── H3: 주도주 승자 포지션 보호 게이트 (SECULAR_LEADER_REGIME_GATE_V1) ─ // 삼성전자·SK하이닉스 secular_leader_profit_lock 발동 여부 결정론적 확정 ['secular_leader_gate_json', JSON.stringify( (h4.prices || []).reduce(function(acc, p) { if (p.secular_leader_gate_status && p.secular_leader_gate_status !== 'NOT_APPLICABLE') { acc[p.ticker] = { active: p.secular_leader_gate_active, status: p.secular_leader_gate_status, reasons: p.secular_leader_gate_reasons }; } return acc; }, {}) )], // ── M4: 5억원 목표 자산 추적 대시보드 ────────────────────────────────────── // GOAL_RETIREMENT_V1: 은퇴자산 5억원 목표 — 하네스 결정론적 산출 (LLM 재판단 금지) ['goal_asset_krw', M4_GOAL_KRW], ['goal_current_asset_krw', Math.round(m4Asset)], ['goal_achievement_pct', m4Achieve], ['goal_remaining_krw', Math.round(m4Remain)], ['goal_eta_months', m4EtaMonths], ['goal_eta_label', m4EtaLabel], ['goal_monthly_growth_pct', m4NetExp30], ['goal_status', m4Asset >= M4_GOAL_KRW ? 'ACHIEVED' : 'IN_PROGRESS'], // ── [3RD_HARNESS_V1] 커버리지 완성 — GAS 30.2% → 100% ─────────────────────────── // 목표: LLM 자유도 69.8% → 0% (완전 결정론적) // 43/43 필수 필드를 GAS가 직접 산출 — LLM 추정 불필요 // HARNESS_DATA_FRESHNESS_GATE_V1 ['data_freshness_status', (((hApex || {}).data_freshness_json) || {}).data_freshness_status || (snapshotGate.status === 'PASS' ? 'FRESH' : 'STALE')], // INTRADAY_ACTION_MATRIX_V1 ['intraday_scope', intradayLock ? 'INTRADAY_RESTRICTED' : 'FULL_EOD'], // PROFIT_LOCK_RATCHET_V1 — profit_preservation_json 최고 단계 ['profit_lock_stage', (function() { var pp = (hApex || {}).profit_preservation_json || []; var order = { APEX_SUPER: 7, APEX_TRAILING: 6, PROFIT_LOCK_30: 5, PROFIT_LOCK_20: 4, PROFIT_LOCK_10: 3, BREAKEVEN_RATCHET: 2, NORMAL: 1 }; var best = 'NORMAL'; pp.forEach(function(r) { var st = String(r.profit_preservation_state || 'NORMAL'); if ((order[st] || 1) > (order[best] || 1)) best = st; }); return best; })()], ['auto_trailing_stop', (function() { var pp = (hApex || {}).profit_preservation_json || []; var maxStop = null; pp.forEach(function(r) { if (typeof r.auto_trailing_stop === 'number' && (maxStop === null || r.auto_trailing_stop > maxStop)) { maxStop = r.auto_trailing_stop; } }); return maxStop !== null ? maxStop : 0; })()], // PROFIT_RATCHET_TIERED_V2 — APEX_SUPER(+60%) ATR×1.2 trailing // profit_pct >= 60 → APEX_SUPER; inject_computed_harness.py 가 정밀값 교체 ['ratchet_stage_v2', (function() { var pp = (hApex || {}).profit_preservation_json || []; var order = { APEX_SUPER: 7, APEX_TRAILING: 6, PROFIT_LOCK_30: 5, PROFIT_LOCK_20: 4, PROFIT_LOCK_10: 3, BREAKEVEN_RATCHET: 2, NORMAL: 1 }; var best = 'NORMAL'; pp.forEach(function(r) { var pct = typeof r.profit_pct === 'number' ? r.profit_pct : 0; var st = pct >= 60 ? 'APEX_SUPER' : String(r.profit_preservation_state || 'NORMAL'); if ((order[st] || 1) > (order[best] || 1)) best = st; }); return best; })()], ['auto_trailing_stop_v2', (function() { var pp = (hApex || {}).profit_preservation_json || []; var maxStop = null; pp.forEach(function(r) { // APEX_SUPER 종목: 기존 auto_trailing_stop 그대로 사용 (Python inject로 ATR×1.2 보정) if (typeof r.auto_trailing_stop === 'number' && (maxStop === null || r.auto_trailing_stop > maxStop)) { maxStop = r.auto_trailing_stop; } }); return maxStop !== null ? maxStop : 0; })()], // FLOW_ACCELERATION_V1 — W4 신호 집계 ['flow_acceleration_status', (function() { var ph = (hAlpha || {}).per_holding || []; return ph.some(function(h) { return h.w4_status === 'FLOW_DECEL_WARNING'; }) ? 'FLOW_DECEL_DETECTED' : 'NORMAL'; })()], // DISTRIBUTION_SELL_DETECTOR_V1 — distribution_risk_json 집계 ['distribution_sell_detector_status', (function() { var dist = (hApex || {}).distribution_risk_json || []; if (dist.some(function(d) { return d.anti_distribution_state === 'BLOCK_BUY'; })) return 'DISTRIBUTION_DETECTED'; if (dist.some(function(d) { return d.anti_distribution_state === 'TRIM_REVIEW'; })) return 'TRIM_REVIEW_ALERT'; return 'NORMAL'; })()], ['signals_count', (function() { var dist = (hApex || {}).distribution_risk_json || []; return dist.filter(function(d) { return d.anti_distribution_state !== 'PASS'; }).length; })()], // BREAKOUT_QUALITY_GATE_V2 — breakout_quality_gate_json 최소 점수 ['breakout_quality_score', (function() { var bqg = (hApex || {}).breakout_quality_gate_json || []; if (!bqg.length) return 0; var min = null; bqg.forEach(function(b) { if (typeof b.breakout_quality_score === 'number' && (min === null || b.breakout_quality_score < min)) min = b.breakout_quality_score; }); return min !== null ? min : 0; })()], // ANTI_CHASING_VELOCITY_V1 — entry_freshness_json 집계 (worst-case) ['anti_chasing_verdict', (function() { var ef = (hApex || {}).entry_freshness_json || []; var worst = 'CLEAR'; ef.forEach(function(r) { var fs = String(r.freshness_state || '').toUpperCase(); if (fs === 'BLOCK_LATE_CHASE') { worst = 'BLOCK_CHASE'; } else if (fs === 'PULLBACK_WAIT' && worst !== 'BLOCK_CHASE') { worst = 'PULLBACK_WAIT'; } }); return worst; })()], ['anti_chasing_velocity_status', (function() { var ef = (hApex || {}).entry_freshness_json || []; var worst = 'PASS'; ef.forEach(function(r) { var fs = String(r.freshness_state || '').toUpperCase(); if (fs === 'BLOCK_LATE_CHASE') { worst = 'BLOCKED'; } else if (fs === 'PULLBACK_WAIT' && worst === 'PASS') { worst = 'WAIT'; } }); return worst; })()], // PULLBACK_ENTRY_TRIGGER_V1 ['pullback_entry_verdict', (function() { var ef = (hApex || {}).entry_freshness_json || []; return ef.some(function(r) { return String(r.freshness_state || '').toUpperCase() === 'PULLBACK_WAIT'; }) ? 'PULLBACK_ZONE' : 'ABOVE_PULLBACK_ZONE'; })()], // per-ticker only; Python inject가 종목별 기준가 제공. 0 = 활성 눌림목 없음. ['pullback_entry_trigger_price', 0], // CASH_RECOVERY_OPTIMIZER_V1 — cash_raise_plan_json이 GAS 확정 현금회복 계획 ['cash_recovery_plan_json', JSON.stringify((hApex || {}).cash_raise_plan_json || [])], // SELL_WATERFALL_ENGINE_V1 — 동일 계획(폭포수 매도 순서) ['waterfall_plan_json', JSON.stringify((hApex || {}).cash_raise_plan_json || [])], // ── SPRINT 1-3 Python-computed fields: GAS placeholder (inject.py가 덮어씀) ── // ANTI_CHASING_VELOCITY_V1 — per-ticker 속도 게이트 (inject.py 교체) ['anti_chasing_velocity_json', '[]'], // DISTRIBUTION_SELL_DETECTOR_V1 — per-ticker 6신호 배급형 탐지 (inject.py 교체) ['distribution_sell_detector_json', '[]'], // K2_STAGED_REBOUND_SELL_V1 — 현금확보 K2 분할 계획 (inject.py 교체) ['k2_staged_rebound_sell_json', '[]'], // PRE_DISTRIBUTION_EARLY_WARNING_V1 — distribution_risk_json 선행경보 집계 (inject.py 교체) ['pre_distribution_warning', JSON.stringify({ status: 'NONE', affected_count: 0, affected_tickers: [], buy_gate: 'PASS', formula_id: 'PRE_DISTRIBUTION_EARLY_WARNING_V1' })], // SELL_EXECUTION_TIMING_V1 ['sell_timing_verdict', intradayLock ? 'TIMING_BLOCKED_INTRADAY' : (snapshotGate.status === 'PASS' ? 'SELL_READY' : 'SELL_BLOCKED_DATA')], ['sell_execution_window', intradayLock ? 'NEXT_DAY_OPEN' : 'EOD_30MIN'], // SELL_VALUE_PRESERVATION_TIERED_V2 — sell_value_preservation_json 집계 ['preservation_verdict', (function() { var svp = (hApex || {}).sell_value_preservation_json || []; if (!svp.length) return 'NO_DATA'; if (svp.some(function(r) { return r.sell_value_preservation_state === 'EMERGENCY_EXIT'; })) return 'EMERGENCY_EXIT'; if (svp.some(function(r) { return r.sell_value_preservation_state === 'TRIM_ONLY'; })) return 'TRIM_ONLY'; if (svp.some(function(r) { return r.sell_value_preservation_state === 'REBOUND_CONFIRM_HOLD'; })) return 'REBOUND_CONFIRM_HOLD'; return 'HOLD'; })()], // TICK_NORMALIZER_V1 — GAS는 모든 가격에 tickNormalize_() 적용 ['tick_normalized_price', true], // SELL_PRICE_SANITY_V1 — H4 prices 호가단위 검증 (GAS 생성 가격은 항상 PASS) // inject_computed_harness.py 가 스프레드시트 원본 입력값 검증 후 교체 ['sell_price_sanity_status', (function() { var prices = (h4 || {}).prices || []; var worst = 'PASS'; prices.forEach(function(p) { var candidates = [p.stop_price, p.tp1_price, p.tp2_price]; candidates.forEach(function(price) { if (price == null || price <= 0) return; var tick = getTickSize_(price); if (price % tick !== 0) { worst = 'INVALID_TICK'; } }); }); return worst; })()], // BENCHMARK_RELATIVE_TIMESERIES_V1 — BRT 집계 ['brt_verdict', (function() { var brt = (hApex || {}).benchmark_relative_timeseries_json || []; if (!brt.length) return 'NO_DATA'; if (brt.some(function(r) { return r.brt_verdict === 'BROKEN'; })) return 'BROKEN'; if (brt.some(function(r) { return r.brt_verdict === 'LEADER'; })) return 'LEADER'; return 'MARKET'; })()], ['brt_rs_slope', (function() { var brt = (hApex || {}).benchmark_relative_timeseries_json || []; var slopes = brt.map(function(r) { return r.rs_line_20d_slope; }) .filter(function(v) { return v != null && isFinite(v); }); if (!slopes.length) return 0; return parseFloat((slopes.reduce(function(a, b) { return a + b; }, 0) / slopes.length).toFixed(4)); })()], // RS_VERDICT_V2 FUSION — buy_permission_json + BRT 융합 집계 ['rs_verdict', (function() { var bp = (hApex || {}).buy_permission_json || []; var brt = (hApex || {}).benchmark_relative_timeseries_json || []; if (!bp.length) return 'NO_DATA'; // V1 raw var v1_broken = bp.some(function(r) { return (r.rs_verdict_v1 || r.rs_verdict) === 'BROKEN'; }); var v1_laggard = bp.some(function(r) { return (r.rs_verdict_v1 || r.rs_verdict) === 'LAGGARD'; }); var v1_leader = bp.some(function(r) { return (r.rs_verdict_v1 || r.rs_verdict) === 'LEADER'; }); // RS_VERDICT-5: brt_verdict=BROKEN AND v1=LEADER → V2 결과는 LAGGARD if (brt.some(function(r) { return r.brt_verdict === 'BROKEN'; }) && v1_leader && !v1_broken) { return 'LAGGARD'; } if (v1_broken) return 'BROKEN'; if (v1_laggard) return 'LAGGARD'; if (v1_leader) return 'LEADER'; return 'MARKET'; })()], ['rs_verdict_source', (function() { var brt = (hApex || {}).benchmark_relative_timeseries_json || []; return brt.length ? 'V2_FUSION' : 'V1_ONLY'; })()], ['rs_verdict_v1_raw', (function() { var bp = (hApex || {}).buy_permission_json || []; if (!bp.length) return 'NO_DATA'; if (bp.some(function(r) { return (r.rs_verdict_v1 || r.rs_verdict) === 'BROKEN'; })) return 'BROKEN'; if (bp.some(function(r) { return (r.rs_verdict_v1 || r.rs_verdict) === 'LAGGARD'; })) return 'LAGGARD'; if (bp.some(function(r) { return (r.rs_verdict_v1 || r.rs_verdict) === 'LEADER'; })) return 'LEADER'; return 'MARKET'; })()], // SATELLITE_ALPHA_QUALITY_GATE_V1 — saqg_json 집계 ['saqg_verdict', (function() { var saqg = (hApex || {}).saqg_json || []; if (!saqg.length) return 'NO_DATA'; if (saqg.some(function(r) { return r.saqg_v1 === 'ELIGIBLE'; })) return 'ELIGIBLE'; if (saqg.every(function(r) { return r.saqg_v1 === 'EXCLUDED'; })) return 'ALL_EXCLUDED'; return 'WATCHLIST_ONLY'; })()], // SATELLITE_AGGREGATE_PNL_GATE_V1 ['sapg_verdict', ((hApex || {}).sapg_json || {}).sapg_status || 'INSUFFICIENT_DATA'], // LLM_SERVING_CONSTRAINT_V1 ['serving_constraint_check', 'PASS'], // DETERMINISTIC_ROUTING_ENGINE_V1 — 9단계 라우팅 완료 로그 ['routing_execution_log', JSON.stringify({ stages_completed: [ 'STAGE_0_FRESHNESS', 'STAGE_1_CASH_RATIOS', 'STAGE_2_RATCHET', 'STAGE_3_DISTRIBUTION', 'STAGE_4_BUY_TIMING', 'STAGE_5_SELL_WATERFALL', 'STAGE_6_PRICE_VALIDATION', 'STAGE_7_RS_BRT', 'STAGE_8_SATELLITE', 'STAGE_9_LLM_SERVING' ], routing_completed: true, formula_id: 'DETERMINISTIC_ROUTING_ENGINE_V1' })], // TRADE_QUALITY_SCORER_V1 — 월간 배치 결과 캐시 읽기 (없으면 MONTHLY_BATCH_PENDING) ['trade_quality_json', (function() { try { var ss2 = getSpreadsheet_(); var setSh = ss2.getSheetByName('settings'); if (!setSh) return JSON.stringify({ status: 'MONTHLY_BATCH_PENDING', last_computed: null, formula_id: 'TRADE_QUALITY_SCORER_V1' }); var sData = setSh.getDataRange().getValues(); for (var si = 0; si < sData.length; si++) { if (String(sData[si][0] || '').trim() === 'trade_quality_json') { var raw = sData[si][1]; if (raw) { var s = String(raw); return s.length <= 45000 ? s : JSON.stringify({ status: 'OVERSIZED_TRIMMED', formula_id: 'TRADE_QUALITY_SCORER_V1' }); } break; } } } catch(e) { Logger.log('[HARNESS_ROWS] trade_quality_json 읽기 오류: ' + e.message); } return JSON.stringify({ status: 'MONTHLY_BATCH_PENDING', last_computed: null, formula_id: 'TRADE_QUALITY_SCORER_V1' }); })()], // PATTERN_BLACKLIST_AUTO_V1 — 월간 배치 결과 캐시 읽기 ['pattern_blacklist_status', (function() { try { var ss3 = getSpreadsheet_(); var setSh3 = ss3.getSheetByName('settings'); if (!setSh3) return 'INACTIVE'; var sData3 = setSh3.getDataRange().getValues(); for (var si3 = 0; si3 < sData3.length; si3++) { if (String(sData3[si3][0] || '').trim() === 'pattern_blacklist_json') { try { var parsed = JSON.parse(String(sData3[si3][1] || '{}')); var hasTriggered = Array.isArray(parsed.patterns) && parsed.patterns.some(function(p) { return p.pattern_blacklist_status === 'TRIGGERED'; }); return hasTriggered ? 'TRIGGERED' : 'INACTIVE'; } catch(pe) { break; } } } } catch(e) { Logger.log('[HARNESS_ROWS] pattern_blacklist_status 읽기 오류: ' + e.message); } return 'INACTIVE'; })()], ['pattern_blacklist_json', (function() { try { var ss4 = getSpreadsheet_(); var setSh4 = ss4.getSheetByName('settings'); if (!setSh4) return JSON.stringify({ status: 'INACTIVE', patterns: [], pattern_count: 0, formula_id: 'PATTERN_BLACKLIST_AUTO_V1' }); var sData4 = setSh4.getDataRange().getValues(); for (var si4 = 0; si4 < sData4.length; si4++) { if (String(sData4[si4][0] || '').trim() === 'pattern_blacklist_json') { var raw4 = sData4[si4][1]; if (raw4) return String(raw4); break; } } } catch(e) { Logger.log('[HARNESS_ROWS] pattern_blacklist_json 읽기 오류: ' + e.message); } return JSON.stringify({ status: 'INACTIVE', patterns: [], pattern_count: 0, formula_id: 'PATTERN_BLACKLIST_AUTO_V1' }); })()], // ── [SPRINT4_SFG_SCALARS] SFG 스칼라 (inject.py 교체) ──────────────────── ['sfg_v1', 'CLEAR'], ['sfg_broken_count', 0], ['sfg_failure_rate', 0], // ── [SPRINT4_PCG] PORTFOLIO_CORRELATION_GATE_V1 (inject.py 교체) ───────── ['portfolio_correlation_gate_json', JSON.stringify({ correlation_gate_status: 'INSUFFICIENT_DATA', satellite_cluster_beta: null, effective_portfolio_beta: null, regime_beta_limit: 1.0, reason: 'GAS 초기값 — inject.py 교체 대상', formula_id: 'PORTFOLIO_CORRELATION_GATE_V1' })], ['correlation_gate_status', 'INSUFFICIENT_DATA'], // TICK_NORMALIZER_V1 — 종목별 호가 정규화 가격 맵 (Python inject 보완) ['tick_normalized_prices_json', (function() { var prices4 = (h4 || {}).prices || []; var map = {}; prices4.forEach(function(p) { if (!p.ticker) return; var sp = p.stop_price ? tickNormalize_(p.stop_price) : null; var tp = p.tp1_price ? tickNormalize_(p.tp1_price) : null; map[p.ticker] = { stop: sp, tp1: tp }; }); return JSON.stringify(map); })()], // SELL_PRICE_SANITY — 종목별 상세 (ratchet_v2 per-ticker) ['ratchet_v2_per_ticker_json', (function() { var pp = (hApex || {}).profit_preservation_json || []; return JSON.stringify(pp.map(function(r) { return { ticker: r.ticker || '', profit_pct: r.profit_pct || 0, ratchet_stage_v2: r.profit_preservation_state || 'NORMAL', auto_trailing_stop_v2: r.auto_trailing_stop || null }; })); })()], // ── LLM 종합 지침 V6 (SPRINT 1: D1-ROUTING·D2-LLM·A2-ANTI_CHASE·K2-REBOUND 추가) ──── ['llm_instruction', 'HARNESS_AUTHORITATIVE_V4(H4): ' + '▶ 재계산 금지: sell_priority_lock·quantities_lock·prices_lock·decision_lock·alpha_shield_lock·regime_trim_lock=true — ' + 'GAS 확정값을 LLM이 재계산·수정·추가·삭제하는 행위는 HARNESS_VIOLATION으로 보고서 전체 무효. ' + '▶ [HS009] TP 유효성 잠금: prices_json의 tp1_price/tp2_price가 null이면 INVALID_TP_STALE — ' + 'LLM이 대체 TP 가격을 임의 산출하는 것 절대 금지. tp1_state/tp2_state 그대로 보고. ' + '▶ [HS010] WATCH/BLOCKED 출력 잠금: order_blueprint_json의 validation_status!=PASS인 행은 ' + '지정가·손절가·익절가·수량 전부 null. LLM이 참고값이라도 HTS 주문 표에 숫자 기재 금지. ' + '감시값은 별도 "WATCH 감시 원장(주문 아님)" 섹션으로만 표시. ' + '▶ [HS011] LLM 즉석 공식 정의 금지: spec/13_formula_registry.yaml에 등록되지 않은 공식명·알고리즘명을 ' + '즉석 정의하고 이에 기반한 원화 가격·정수 수량을 산출하는 것 절대 금지. ' + '하네스 미구현 영역은 "DATA_MISSING — 하네스 업데이트 필요"로만 표시. ' + '▶ [M1] 국면별 감축: regime_trim_guidance_json의 satellite_trim_pct/leader_trim_pct 범위를 그대로 인용. ' + 'LLM이 임의 감축비율을 제시하는 것 금지. ' + '▶ [H3] 주도주 게이트: secular_leader_gate_json의 active/status를 그대로 보고. ' + '005930·000660 종목에서 secular_leader_gate_active=true이면 ' + 'tp1_state=DEFERRED_SECULAR_LEADER 구간에서 TP 매도 신호 생성 금지. ' + '하네스가 null로 전달한 tp1_price를 LLM이 임의 복원하는 것 절대 금지. ' + '▶ Blueprint 무결성: order_blueprint_json 수정 절대 금지. blueprint_checksum(CRC32_V1) Python 검증. ' + '▶ 구조화 출력 강제: [Rule_ID:X, Value:Y, Threshold:Z, Result:R] 포맷만 허용. ' + '▶ Zero-Adjective: 감성 형용사·부사 금지. 수치와 Rule_ID만 허용. ' + '▶ P4 장중 모드(intraday_lock=true): p4_intraday_allowed_actions 외 액션 출력 금지. ' + '▶ CLAMP 발동 종목은 clamp_label 표기 필수. ' + '▶ [M4] 목표 자산 추적: goal_achievement_pct·goal_remaining_krw·goal_eta_label은 하네스 산출값 그대로 보고. ' + 'LLM이 5억원 달성 여부·ETA를 독자적으로 재계산하는 것 절대 금지. ' + '▶ [G1] 현금 부족액 잠금(CASH_SHORTFALL_V1): cash_shortfall_min_krw·cash_shortfall_target_krw는 하네스 확정값. ' + '"약 N원 필요" 형태의 LLM 즉석 계산 절대 금지. cash_current_pct_d2·cash_target_pct도 하네스 복사 전용. ' + '▶ [G2] TRIM 계획 잠금(TRIM_PLAN_MIN_CASH_V1): trim_plan_to_min_cash_json은 H2 매도우선순위 기반 GAS 확정. ' + 'LLM이 현금 회복을 위해 임의로 종목·수량·순서를 선택하는 것 절대 금지. 하네스 plan 복사만 허용. ' + '▶ [APEX_V1] 판단 자료 생성시점 로직: alpha_lead_json·distribution_risk_json·buy_permission_json·' + 'cash_raise_plan_json·smart_sell_quantities_json·execution_quality_json은 GAS 확정값. ' + '뒷북매수/설거지/현금확보 매도 방식은 LLM 해석 금지, *_lock=true 값 그대로 복사. ' + 'buy_permission_state가 ALLOW_*가 아니면 BUY 수량 출력 금지. ' + 'execution_style=OVERSOLD_REBOUND_SELL이면 rebound_wait_qty를 immediate_qty로 이동 금지. ' + '▶ [ENTRY_FRESHNESS_GATE_V1] entry_freshness_json 없이 뒷북/추격 BUY 승인 금지. ' + 'BLOCK_LATE_CHASE/PULLBACK_WAIT는 BUY/STAGED_BUY/ADD_ON 차단. ' + '▶ [SELL_VALUE_PRESERVATION_GATE_V1] sell_value_preservation_json 없이 현금확보 매도와 수익보호 매도 혼용 금지. ' + 'EMERGENCY_EXIT 외에는 반등대기 수량을 즉시매도로 승격 금지. ' + '▶ [INDEX_RELATIVE_HEALTH_GATE_V1] index_relative_health_json 없이 지수 대비 괴리 종목을 BUY 승인 금지. ' + 'DECOUPLED/OVER_EXTENDED는 신규 BUY 차단, UNDERPERFORMING은 WATCH 우선. ' + '▶ [HS010-B] 종합 판단 제안표 필수 출력: comprehensive_proposal_json을 "종합 판단 제안표(PROPOSAL)" 표로 ' + '항상 출력. PENDING_EXPORT·BLOCKED·DATA_MISSING 상태와 무관하게 생략 금지. ' + '판단은 사용자 몫이므로 reference_stop_price·reference_tp1_price·tp1_state·reference_tp2_price·tp2_state·' + 'proposed_immediate_qty·proposed_staged_qty·expected_cash_krw를 그대로 표시. ' + '이 표에서 LLM이 가격·수량을 임의로 변경하거나 새 수치를 추가하는 것 절대 금지. ' + '▶ [HS010-C] 위성 후보 스크리닝 표 필수 출력: satellite_candidate_json을 "위성 후보 스크리닝(SATELLITE_CANDIDATE_SCREEN_V1)" 표로 ' + '항상 출력. 후보가 0개여도 표를 출력하고 "현재 추가 적합 후보 없음"을 명시. ' + 'satellite_candidate_summary.watch_candidates를 항상 표 제목에 병기. ' + 'LLM이 universe 외 종목을 임의 추가하거나 grade를 변경하는 것 금지. ' + '▶ [D1-ROUTING] 9단계 결정론적 라우팅 의무: 보고서는 routing_execution_log의 ' + '9단계 순서(①신선도→②장중판별→③포트폴리오상태→④매도레이더→⑤매수타이밍→' + '⑥현금확보→⑦가격정규화→⑧RS/위성→⑨LLM서빙) 결과를 먼저 표 형태로 출력하고 ' + '이후 분석을 진행한다. routing_execution_log 생략 시 INCOMPLETE_ROUTING_LOG 처리. ' + '▶ [D2-LLM] LLM 8금지(위반 시 INVALID_LLM_OVERRIDE): ' + '①미등록공식 지정가/수량 산출 금지 ' + '②하네스BLOCK 판정 우회("그래도매수") 금지 ' + '③SELL_PRICE_SANITY INVALID 가격 복원 금지 ' + '④cash_shortfall LLM 즉석계산 금지 ' + '⑤K2 반등대기 수량을 "현금급함"으로 즉시전환 금지 ' + '⑥APEX_SUPER 구간 trailing_stop 미병기 금지 ' + '⑦DISTRIBUTION_CONFIRMED 매수 우회 금지 ' + '⑧routing_execution_log 생략 금지. ' + '▶ [A2-ANTI_CHASE] anti_chasing_velocity_json의 anti_chase_verdict=BLOCK_CHASE인 ' + '종목은 당일 신규 BUY 절대 금지. PULLBACK_WAIT는 pullback_entry_trigger_price 도달 전 매수 금지. ' + 'distribution_sell_detector_json의 distribution_verdict=DISTRIBUTION_CONFIRMED인 종목 BUY 절대 금지. ' + '▶ [K2-REBOUND] cash_recovery_plan_json의 rebound_wait_qty는 ' + 'rebound_trigger_price 도달 전 즉시매도 전환 금지. "현금이 급하니까" 이유로 ' + 'Stage 2 즉시전환 금지. emergency_full_sell=true일 때만 전량 즉시 허용. ' + '▶ [PA47-A1] watch_breakout_candidates_json 필수 출력: promotion_eligible=true 항목을 ' + '"급등 탐지 — 라이프사이클 재검토 권고" 표로 출력. ' + 'lifecycle_stage=EXIT이어도 breakout_signal=WATCH_BREAKOUT_DETECTED면 즉시 매도 금지; ' + 'satellite_lifecycle_gate_json의 breakout_promotion_recommendation=PROMOTE_TO_WATCH 참조. ' + '후보가 0건이면 표 생략 가능. ' + '▶ [PA47-PA1] buy_permission_json의 pa1_synthesis_verdict·pa1_direction_confidence 반드시 인용: ' + 'EXIT_SIGNAL(dc<-30) 종목은 "방향성 부적합—보유 재검토", TRIM_SIGNAL(dc<-10) 종목은 ' + '"비중 축소 검토"로 표시. STRONG_BUY/MODERATE_BUY 종목은 신규 진입 우선순위 상향. ' + 'pa1_synthesis_verdict가 없는 종목은 PA1 미적용으로 명시. ' + '▶ [PA47-A3] anti_whipsaw_reentry_json의 reentry_signal=REENTRY_CANDIDATE 종목은 ' + '"매도 재검토 — 반등 감지" 경고로 표시. 매도 실행 전 재확인 의무. ' + 'reentry_grade=A/B이면 매도 보류 후 다음날 재평가 권고. ' + '▶ [PA47-B4] harness_generation_status=BLOCKED_STALE_DATA 또는 BLOCKED_CV_FAIL이면 ' + '보고서 생성을 거부하고 "하네스 BLOCK — 데이터 갱신 후 재실행 요망"만 출력. ' + '▶ [PROPOSAL50-EG] export_gate_json의 json_validation_status=PENDING_EXPORT이면 ' + 'hts_entry_allowed=false — HTS 주문 입력 절대 금지. failed_checks와 resolution_guide를 출력. ' + '▶ [PROPOSAL50-EJCE] ejce_json의 consensus_result=NO_BUY 종목은 3개 관점 중 2개 이상 BLOCK — ' + 'buy_permission이 ALLOW여도 EJCE NO_BUY 종목 BUY 실행 금지. block_reasons 인용 필수. ' + '▶ [PROPOSAL50-SCRS] scrs_v2_json의 selected_combo만 현금확보 매도 기재 허용. ' + 'immediate_sell_qty와 rebound_wait_qty 구분 표시 의무. ' + 'emergency_level=TRIM_ONLY이면 추가 매도 금지. ' + '▶ [PROPOSAL50-DSLE] serving_lock_json의 llm_serving_budget.numeric_generation_allowed=0 — ' + 'LLM이 가격·수량·수익률 등 숫자를 자체 생성하는 것 절대 금지. ' + '▶ [PROPOSAL50-H10] shadow_ledger_json은 BLOCKED/INVALID 블루프린트를 투명하게 보존. ' + '산출 지정가·손절가·익절가·이론수량을 null 처리하거나 은폐 금지(HS010). ' + '사용자의 사후 평가·오버라이드를 위해 "투명한 감시 원장" 표로 출력. ' + '▶ [PROPOSAL50-D2] llm_serving_constraint_json의 constraint_status=INVALID_LLM_OVERRIDE이면 ' + '보고서 조립 중단 — violations 목록 전체를 "[INVALID_LLM_OVERRIDE: 사유]"로 표시 후 재실행 요망.'], // ── [PROPOSAL50] MRAG-V2 + M5 V1.1 의무감축계획 ───────────────────────────────────────── ['mrag_v2_json', JSON.stringify((hApex || {}).mrag_v2_json || {})], ['effective_heat_gate_threshold', (hApex || {}).effective_heat_gate_threshold || null], ['effective_position_size_scale', (hApex || {}).effective_position_size_scale || null], ['mandatory_reduction_json', JSON.stringify((hApex || {}).mandatory_reduction_json || {})], // ── [PROPOSAL50] Export Gate / Routing Trace / Watch Ledger / EJCE / SCRS-V2 / DSLE ────── ['export_gate_json', JSON.stringify((hApex || {}).export_gate_json || {})], ['hts_entry_allowed', String((hApex || {}).hts_entry_allowed || false)], ['routing_trace_json', JSON.stringify((hApex || {}).routing_trace_json || {})], ['watch_ledger_json', JSON.stringify((hApex || {}).watch_ledger_json || [])], ['ejce_json', JSON.stringify((hApex || {}).ejce_json || [])], ['scrs_v2_json', JSON.stringify((hApex || {}).scrs_v2_json || {})], ['serving_lock_json', JSON.stringify((hApex || {}).serving_lock_json || {})], // ── [PROPOSAL50-P0-GAP] H10/D2 신규 필드 ─────────────────────────────────────────────────── ['shadow_ledger_json', JSON.stringify((hApex || {}).shadow_ledger_json || { shadow_ledger: [], blocked_count: 0 })], ['llm_serving_constraint_json', JSON.stringify((hApex || {}).llm_serving_constraint_json || { constraint_status: 'DATA_MISSING' })], // ── [PROPOSAL51] SU_51_K 신규 필드 ──────────────────────────────────────────────────────── ['cluster_sync_result_json', JSON.stringify((hApex || {}).cluster_sync_result_json || {})], ['proactive_sell_radar_json', JSON.stringify((hApex || {}).proactive_sell_radar_json || [])], ['sell_pass_accuracy_rate', (hApex || {}).sell_pass_accuracy_rate !== undefined ? (hApex || {}).sell_pass_accuracy_rate : null], ['sell_execution_quality_json', JSON.stringify((hApex || {}).sell_execution_quality_json || [])], // ── [PROPOSAL51] P0-D / P1-B / P1-C 신규 필드 ────────────────────────────────────────── ['price_hierarchy_json', JSON.stringify((hApex || {}).price_hierarchy_json || [])], ['data_quality_gate_v2_json', JSON.stringify((hApex || {}).data_quality_gate_v2_json || {})], ['cash_recovery_display_json', JSON.stringify((hApex || {}).cash_recovery_display_json || {})], ['portfolio_health_json', JSON.stringify((hApex || {}).portfolio_health_json || {})], // [PROPOSAL53] 신규 P0 하네스 ['fundamental_quality_json', JSON.stringify((hApex || {}).fundamental_quality_json || {})], ['horizon_allocation_json', JSON.stringify((hApex || {}).horizon_allocation_json || {})], ['smart_money_liquidity_json', JSON.stringify((hApex || {}).smart_money_liquidity_json || {})], ['routing_serving_trace_v2_json',JSON.stringify((hApex || {}).routing_serving_trace_v2_json|| {})], ['fundamental_multifactor_json', JSON.stringify((hApex || {}).fundamental_multifactor_json || {})], ['earnings_growth_quality_json', JSON.stringify((hApex || {}).earnings_growth_quality_json || {})], ['market_share_proxy_json', JSON.stringify((hApex || {}).market_share_proxy_json || {})], ['cashflow_stability_json', JSON.stringify((hApex || {}).cashflow_stability_json || {})], ['routing_decision_explain_json', JSON.stringify((hApex || {}).routing_decision_explain_json || {})], // [PROPOSAL47_B4] STALE_BLOCK enforcement: cv_verdict=BLOCK 시 생성 차단 마커 ['harness_generation_status', (function() { var verdict = (hApex || {}).cv_verdict || ''; var cvReport = (hApex || {}).consistency_report_json || {}; var failedList = cvReport.failed || []; var staleBlock = failedList.some(function(f) { return f && typeof f.reason === 'string' && f.reason.indexOf('STALE_BLOCK') >= 0; }); if (verdict === 'BLOCK' && staleBlock) return 'BLOCKED_STALE_DATA'; if (verdict === 'BLOCK') return 'BLOCKED_CV_FAIL'; return 'OK'; })()] ]; } /** * F3: buildHarnessRows_ 출력 완전성 자체검증 * 19_harness_contract.yaml required_harness_context_keys 기준 필수 키 누락 체크. * 누락 키가 있으면 Logger.log 경고 — 운영 배포 전 조기 감지. */ function assertHarnessRowsComplete_(rows) { var REQUIRED_KEYS = [ // H1 포트폴리오 가드 'harness_version', 'captured_at', 'request_route', 'route_reason_code', 'bundle_selected', 'prompt_entrypoint', 'json_validation_status', 'capture_required', 'cash_ledger_basis', 'intraday_lock', 'snapshot_execution_gate', 'snapshot_execution_reason', 'immediate_cash_krw', 'settlement_cash_d2_krw', 'open_order_amount_krw', 'buy_power_krw', 'cash_floor_status', 'total_heat_pct', 'heat_gate_status', 'heat_gate_threshold_pct', 'sell_priority_lock', 'quantities_lock', 'prices_lock', 'decision_lock', 'blueprint_row_count', 'blueprint_checksum', 'blueprint_hash_algo', 'source_manifest_checksum', 'decision_trace_checksum', 'checksum_hash_algo', // Collections 'source_manifest_json', 'allowed_actions', 'blocked_actions', 'account_snapshot_freshness_json', 'sell_candidates_json', 'sell_quantities_json', 'buy_qty_inputs_json', 'prices_json', 'decisions_json', 'decision_trace_json', 'order_blueprint_json', 'p4_intraday_allowed_actions', 'proposal_reference_json', 'proposal_reference_lock', 'regime_trim_guidance_json', 'secular_leader_gate_json', 'backdata_feature_bank_json', // G1 현금 부족액 잠금 (CASH_SHORTFALL_V1) 'cash_current_pct_d2', 'cash_target_pct', 'cash_shortfall_min_krw', 'cash_shortfall_target_krw', // G2 현금 회복 TRIM 계획 (TRIM_PLAN_MIN_CASH_V1) 'trim_plan_to_min_cash_json', // APEX V1 판단자료 생성 시점 로직 하네스 'alpha_lead_json', 'alpha_lead_lock', 'backdata_feature_bank_json', 'backdata_learning_lock', 'follow_through_json', 'follow_through_lock', 'distribution_risk_json', 'distribution_lock', 'profit_preservation_json', 'profit_preservation_lock', 'cash_raise_plan_json', 'rebound_sell_trigger_json', 'smart_sell_quantities_json', 'smart_cash_raise_lock', 'execution_quality_json', 'execution_quality_lock', 'buy_permission_json', 'limit_price_policy_json', 'regime_adjusted_sell_priority_json', // K3: 국면·섹터 연계 H2 동적 우선순위 'benchmark_relative_timeseries_json', 'index_relative_health_json', 'saqg_json', 'cash_creation_purpose_lock_json', 'alpha_feedback_json', 'alpha_evaluation_window_json', 'entry_freshness_json', 'sell_value_preservation_json', 'sector_rotation_momentum_json', // L1: 섹터 로테이션 모멘텀 추적 // M1-M5 신규 'drawdown_guard_state', 'drawdown_buy_scale', 'portfolio_beta_gate', 'portfolio_beta_gate_json', 'tp_quantity_ladder_json', 'event_risk_json', 'sector_concentration_gate', 'sector_concentration_json', // N1-N5 신규 'regime_size_scale', 'stop_adequacy_json', 'holding_stale_json', 'regime_cash_uplift_min_pct', // O1-O5 신규 'single_position_weight_gate', 'semiconductor_cluster_gate', 'portfolio_drawdown_gate', 'win_loss_streak_state', 'win_loss_streak_buy_scale', 'position_count_gate', 'position_count', // O-group collections 'single_position_weight_json', 'semiconductor_cluster_json', // P1-P5 실시간 경보 & 건전성 'stop_breach_gate', 'stop_breach_alert_json', 'tp_trigger_gate', 'heat_concentration_gate', 'regime_transition_type', 'portfolio_health_label', 'portfolio_health_score', 'portfolio_health_blocked_json', // M4 목표 자산 추적 'goal_asset_krw', 'goal_current_asset_krw', 'goal_achievement_pct', 'goal_remaining_krw', 'goal_eta_label', 'goal_status', // ── [2026-05-20_HARNESS_V5] H6/H7/H8 신규 게이트 'breakout_quality_gate_json', 'breakout_quality_gate_lock', 'anti_whipsaw_gate_json', 'anti_whipsaw_gate_lock', 'smart_cash_raise_json', 'smart_cash_raise_route', 'follow_through_confirm_json', 'follow_through_confirm_lock', // ── [2026-05-20_HARNESS_V5] 4종 결정론적 체크섬 'input_snapshot_checksum', 'rendered_output_checksum', 'rendered_report_checksum', 'non_deterministic_flag', // ── [2026-05-21_CLA_HARNESS_V1] SFG 'satellite_failure_gate_json', 'sapg_json', 'sfg_v1_lock', // ── [SPRINT2_REGIME_CLA_V1] CLA 게이트 + RAG + RS_VERDICT V2 FUSION 'regime_cla_json', 'cla_exit_status', 'rag_v1', 'rag_reason', 'rs_verdict_source', 'rs_verdict_v1_raw', // ── [SPRINT3_L4] PRE_DISTRIBUTION_EARLY_WARNING_V1 'pre_distribution_warning', // ── [SPRINT4] SFG 스칼라 / F2 / PCG 'sfg_v1', 'sfg_broken_count', 'sfg_failure_rate', 'pattern_blacklist_json', 'portfolio_correlation_gate_json', 'correlation_gate_status', // ── [3RD_HARNESS_V1] 커버리지 완성 30개 필드 'data_freshness_status', 'intraday_scope', 'profit_lock_stage', 'auto_trailing_stop', 'auto_trailing_stop_v2', 'ratchet_stage_v2', 'flow_acceleration_status', 'distribution_sell_detector_status', 'signals_count', 'breakout_quality_score', 'anti_chasing_verdict', 'anti_chasing_velocity_status', 'pullback_entry_verdict', 'pullback_entry_trigger_price', 'cash_recovery_plan_json', 'waterfall_plan_json', 'sell_timing_verdict', 'sell_execution_window', 'preservation_verdict', 'tick_normalized_price', 'sell_price_sanity_status', 'brt_verdict', 'brt_rs_slope', 'rs_verdict', 'saqg_verdict', 'sapg_verdict', 'serving_constraint_check', 'routing_execution_log', 'trade_quality_json', 'pattern_blacklist_status', 'tick_normalized_prices_json', 'ratchet_v2_per_ticker_json', // SPRINT 1 신규 필드 (Direction O1/O2/O5/P1/P3/P5/A2/B1/B3/K2/C1/D1) 'semiconductor_cluster_json', 'single_position_weight_json', 'position_count', 'position_count_max', 'position_count_gate', 'stop_breach_alert_json', 'relative_stop_gate', 'relative_stop_signal_json', 'heat_concentration_json', 'portfolio_health_blocked_json', 'anti_chasing_velocity_json', 'distribution_sell_detector_json', 'k2_staged_rebound_sell_json', 'cash_recovery_plan_json', // [PROPOSAL50] 신규 필수 필드 (P0-P2) 'export_gate_json', 'json_validation_status', 'hts_entry_allowed', 'routing_trace_json', 'watch_ledger_json', 'ejce_json', 'scrs_v2_json', 'serving_lock_json', 'mrag_v2_json', 'mandatory_reduction_json', // [PROPOSAL50-P0-GAP] H10/D2 신규 필드 'shadow_ledger_json', 'llm_serving_constraint_json', // [PROPOSAL51] P0-D / P1-B / P1-C 신규 필드 'price_hierarchy_json', 'data_quality_gate_v2_json', 'cash_recovery_display_json', // [PROPOSAL53] 'fundamental_quality_json', 'horizon_allocation_json', 'smart_money_liquidity_json', 'routing_serving_trace_v2_json' ,'fundamental_multifactor_json','earnings_growth_quality_json','market_share_proxy_json', 'cashflow_stability_json','routing_decision_explain_json' ]; var keySet = {}; for (var i = 0; i < rows.length; i++) { if (Array.isArray(rows[i]) && rows[i].length >= 1) { keySet[rows[i][0]] = true; } } var missing = REQUIRED_KEYS.filter(function(k) { return !keySet[k]; }); if (missing.length > 0) { Logger.log('[HARNESS_CONTRACT_FAIL] buildHarnessRows_ missing required keys: ' + missing.join(', ')); } else { Logger.log('[HARNESS_CONTRACT_OK] All ' + REQUIRED_KEYS.length + ' required keys present.'); } return missing; } /** * YAML_FORMULA_BINDING_REGISTRY_V1 * spec 공식 ID와 GS 구현/연계 지점 연결 레지스트리 (커버리지 계량용) */ var YAML_FORMULA_BINDING_REGISTRY_V1 = { BUY_TIMING_SUITABILITY_V1: "core_satellite timing gate binding", CASH_RATIOS_V1: "cash ledger binding", ECP_RISK_SCALE_V1: "risk scale binding", EXECUTION_QUALITY_SCORE_V1: "execution quality binding", EXPECTED_EDGE_V1: "expected edge binding", FINANCIAL_HEALTH_SCORE_V1: "financial health binding", OVERSOLD_DELAY_V1: "oversold delay binding", PEG_SCORE_V1: "valuation peg binding", PORTFOLIO_BAND_STATUS_V1: "portfolio band binding", PORTFOLIO_BETA_V1: "factor beta binding", RS_MOMENTUM_V1: "rs momentum binding", SEA_TIMING_V1: "sell timing binding", SELL_CONFLICT_AWARE_RECOMMENDATION_V1: "sell conflict binding", STOP_PROPOSAL_LADDER_V1: "proposal stop ladder binding", T1_FORCED_SELL_RISK_V1: "t+1 forced sell risk binding" };