Files
QuantEngineByItz/gas_harness_rows.gs

1457 lines
86 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"
};