1457 lines
86 KiB
JavaScript
1457 lines
86 KiB
JavaScript
// 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"
|
||
};
|