Files
QuantEngineByItz/tools/validate_alpha_execution_harness.py
kjh2064 ee3e799de1 feat: 리밸런싱 엔진 V1 + GAS 버그 수정 (2026-06-13)
주요 변경:
- tools/build_rebalance_engine_v1.py: REBALANCE_ENGINE_V1 신규
  * account_snapshot 직접 합산(_build_snap_position_map) → 소수주 분리 행 병합
  * 레짐 소스 macro.REGIME_PRELIM 최우선 (GAS 와 동일)
- src/gas_adapter_parts/gdf_06_rebalance.gs: runRebalanceSheet_() 신규
  * Logger.log / getSpreadsheet_() 로 run_all 연동 수정
- src/gas_adapter_parts/gdc_01_fetch_fundamentals.gs
  * _mergePositionRecord_(): 소수주 중복 행 합산 신규
  * parseInt → parseFloat (qty, availQty)
- src/gas_adapter_parts/gdf_01_price_metrics.gs
  * 미보유 종목 SELL_READY → WATCH_EXIT_SIGNAL
- spec/41_release_dag.yaml: build_rebalance_sheet 노드 추가 (step_count 63)
- spec/51_formula_lifecycle_registry.yaml: REBALANCE_ENGINE_V1 등록

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:20:14 +09:00

672 lines
32 KiB
Python

"""
validate_alpha_execution_harness.py
APEX Alpha Preservation Execution Harness V1/V5 전용 검증기.
기본 validate_harness_context.py는 기존 JSON 호환성을 위해 APEX 필드를 optional로 본다.
이 도구는 GAS/Harness V5 전환 후 APEX 필드를 의무 검증하거나, 부분 도입 상태를 감사할 때 사용한다.
Usage:
python tools/validate_alpha_execution_harness.py <json> [--strict]
python tools/validate_alpha_execution_harness.py <json> --check breakout_quality_gate
python tools/validate_alpha_execution_harness.py <json> --check anti_whipsaw_gate
python tools/validate_alpha_execution_harness.py <json> --check smart_cash_raise_v2
python tools/validate_alpha_execution_harness.py <json> --check determinism
python tools/validate_alpha_execution_harness.py <json> --check cla_harness
cla_harness 체크 항목 (Section 13):
[1] market_regime_state enum 검증 (ADVANCE|PULLBACK_IN_UPTREND|DISTRIBUTION|BREAKDOWN|UNKNOWN)
[2] CLA(CLUSTER_HOLD_ONLY) 레짐 시 코어 종목(005930/000660/229200) SELL 차단 (REGIME_CLA-1)
[3] semiconductor_cluster_json.cluster_state enum 검증
[4] buy_permission_json rs_verdict enum 검증 (LEADER|MARKET|LAGGARD|BROKEN|UNKNOWN)
[5] buy_permission_json composite_verdict enum 검증 (5가지 판정값)
[6] satellite_failure_gate_json.sfg_v1 enum 검증 (TRIGGERED|CLEAR)
[7] sfg_v1=TRIGGERED 시 위성 ALLOW_* 차단 (SFG-2)
[8] buy_permission_json.rag_v1 enum 검증 (PASS|FAIL|EXEMPT)
[9] rag_v1=FAIL 시 ALLOW_* 불일치 오류 (RAG-2)
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
VALID_CHECK_MODES = {"breakout_quality_gate", "anti_whipsaw_gate", "smart_cash_raise_v2", "determinism", "cla_harness", "brt_harness", "factor_cap"}
REQUIRED_V5_KEYS = [
"breakout_quality_gate_json",
"breakout_quality_gate_lock",
"anti_whipsaw_gate_json",
"anti_whipsaw_gate_lock",
"smart_cash_raise_json",
"smart_cash_raise_route",
]
REQUIRED_APEX_KEYS = [
"alpha_lead_lock",
"alpha_lead_json",
"follow_through_lock",
"follow_through_json",
"distribution_lock",
"distribution_risk_json",
"profit_preservation_lock",
"profit_preservation_json",
"smart_cash_raise_lock",
"cash_raise_plan_json",
"rebound_sell_trigger_json",
"smart_sell_quantities_json",
"execution_quality_lock",
"execution_quality_json",
"buy_permission_json",
"limit_price_policy_json",
"alpha_feedback_json",
]
BUY_ACTIONS = {"BUY", "STAGED_BUY", "ADD_ON"}
VALID_BUY_PERMISSION = {"ALLOW_PILOT", "ALLOW_ADD_ON", "WATCH", "BLOCKED"}
# ── [2026-05-21_CLA_HARNESS_V1] Section 13 체크 상수 ──────────────────────────
VALID_MARKET_REGIME_STATES = {
"ADVANCE", "PULLBACK_IN_UPTREND", "DISTRIBUTION", "BREAKDOWN", "UNKNOWN"
}
VALID_RS_VERDICTS = {"LEADER", "MARKET", "LAGGARD", "BROKEN", "UNKNOWN"}
VALID_COMPOSITE_VERDICTS = {
"PRIME_CANDIDATE", "WATCH_CANDIDATE", "REDUCE_CANDIDATE", "EXIT_REVIEW", "CLOSE_POSITION"
}
VALID_BRT_VERDICTS = {"LEADER", "MARKET", "LAGGARD", "BROKEN", "UNKNOWN"}
VALID_SAQG_STATES = {"ELIGIBLE", "WATCHLIST_ONLY", "EXCLUDED", "EXEMPT"}
VALID_SAPG_STATUSES = {"PASS", "SAPG_ALERT", "SAPG_CRITICAL", "INSUFFICIENT_DATA"}
SEMICONDUCTOR_CORE_TICKERS = {"005930", "000660", "229200"} # 삼성전자, SK하이닉스, KODEX반도체
def load_harness(path: Path) -> dict[str, Any]:
payload = json.loads(path.read_text(encoding="utf-8"))
if isinstance(payload, dict) and isinstance(payload.get("data"), dict):
maybe = payload["data"].get("_harness_context")
if isinstance(maybe, dict):
return maybe
return payload
def parse_jsonish(value: Any) -> Any:
if isinstance(value, (list, dict)):
return value
if isinstance(value, str) and value.strip():
return json.loads(value)
return value
def to_number(value: Any) -> float | None:
if isinstance(value, (int, float)):
return float(value)
if isinstance(value, str):
text = value.strip()
if not text:
return None
try:
return float(text)
except ValueError:
return None
return None
def as_rows(harness: dict[str, Any], key: str, errors: list[str]) -> list[dict[str, Any]]:
value = parse_jsonish(harness.get(key))
if not isinstance(value, list):
errors.append(f"{key}: must be a list")
return []
rows: list[dict[str, Any]] = []
for idx, item in enumerate(value):
if not isinstance(item, dict):
errors.append(f"{key}[{idx}]: must be an object")
continue
rows.append(item)
return rows
def validate_required_keys(harness: dict[str, Any], errors: list[str]) -> None:
for key in REQUIRED_APEX_KEYS:
if key not in harness:
errors.append(f"missing APEX key: {key}")
def validate_buy_blocks(harness: dict[str, Any], errors: list[str]) -> None:
permissions = as_rows(harness, "buy_permission_json", errors)
permission_by_ticker = {str(r.get("ticker")): r for r in permissions if r.get("ticker")}
for idx, row in enumerate(permissions):
state = row.get("buy_permission_state")
if state not in VALID_BUY_PERMISSION:
errors.append(f"buy_permission_json[{idx}].buy_permission_state invalid: {state!r}")
tranche = row.get("max_tranche_pct")
if state == "ALLOW_PILOT" and isinstance(tranche, (int, float)) and tranche > 30:
errors.append(f"buy_permission_json[{idx}].max_tranche_pct exceeds 30 for ALLOW_PILOT")
late_chase = to_number(row.get("late_chase_risk_score"))
if late_chase is not None and not (0 <= late_chase <= 100):
errors.append(f"buy_permission_json[{idx}].late_chase_risk_score must be in [0,100]")
follow_score = to_number(row.get("follow_through_score"))
if follow_score is not None and not (0 <= follow_score <= 100):
errors.append(f"buy_permission_json[{idx}].follow_through_score must be in [0,100]")
orders = parse_jsonish(harness.get("order_blueprint_json"))
if isinstance(orders, list):
for idx, order in enumerate(orders):
if not isinstance(order, dict):
continue
ticker = str(order.get("ticker") or "")
order_type = str(order.get("order_type") or "")
validation = str(order.get("validation_status") or "")
state = (permission_by_ticker.get(ticker) or {}).get("buy_permission_state")
if order_type in BUY_ACTIONS and validation == "PASS" and state not in {"ALLOW_PILOT", "ALLOW_ADD_ON"}:
errors.append(
f"order_blueprint_json[{idx}]: BUY PASS emitted while buy_permission_state={state!r}"
)
def validate_distribution_blocks(harness: dict[str, Any], errors: list[str]) -> None:
distribution = as_rows(harness, "distribution_risk_json", errors)
blocked = {
str(row.get("ticker"))
for row in distribution
if row.get("anti_distribution_state") == "BLOCK_BUY"
or (isinstance(row.get("distribution_risk_score"), (int, float)) and row["distribution_risk_score"] >= 70)
}
orders = parse_jsonish(harness.get("order_blueprint_json"))
if isinstance(orders, list):
for idx, order in enumerate(orders):
if not isinstance(order, dict):
continue
if str(order.get("ticker")) in blocked and str(order.get("order_type")) in BUY_ACTIONS:
errors.append(f"order_blueprint_json[{idx}]: BUY action exists for distribution BLOCK_BUY ticker")
def validate_cash_raise(harness: dict[str, Any], errors: list[str]) -> None:
rows = as_rows(harness, "cash_raise_plan_json", errors)
for idx, row in enumerate(rows):
style = row.get("execution_style")
immediate = row.get("immediate_qty")
rebound = row.get("rebound_wait_qty")
cap_pct = row.get("immediate_qty_cap_pct")
if immediate is not None and not isinstance(immediate, int):
errors.append(f"cash_raise_plan_json[{idx}].immediate_qty must be integer or null")
if rebound is not None and not isinstance(rebound, int):
errors.append(f"cash_raise_plan_json[{idx}].rebound_wait_qty must be integer or null")
if cap_pct is not None and not isinstance(cap_pct, int):
errors.append(f"cash_raise_plan_json[{idx}].immediate_qty_cap_pct must be integer or null")
emergency = row.get("emergency_full_sell") is True
if style == "OVERSOLD_REBOUND_SELL" and not (isinstance(rebound, int) and rebound > 0) and not emergency:
errors.append(
f"cash_raise_plan_json[{idx}]: OVERSOLD_REBOUND_SELL requires rebound_wait_qty > 0 "
f"unless emergency_full_sell=true"
)
def validate_execution_quality(harness: dict[str, Any], errors: list[str]) -> None:
quality_rows = as_rows(harness, "execution_quality_json", errors)
quality_by_ticker = {str(row.get("ticker")): row for row in quality_rows if row.get("ticker")}
for idx, row in enumerate(quality_rows):
status = row.get("execution_quality_status")
if status not in {"PASS", "BLOCKED", "BLOCKED_ADV_3PCT", "SPLIT_REQUIRED", "BLOCKED_SPREAD"}:
errors.append(f"execution_quality_json[{idx}].execution_quality_status invalid: {status!r}")
orders = parse_jsonish(harness.get("order_blueprint_json"))
if isinstance(orders, list):
for idx, order in enumerate(orders):
if not isinstance(order, dict):
continue
if str(order.get("validation_status")) != "PASS":
continue
ticker = str(order.get("ticker") or "")
quality = quality_by_ticker.get(ticker)
if quality and quality.get("execution_quality_status") != "PASS":
errors.append(
f"order_blueprint_json[{idx}]: PASS order while execution_quality_status={quality.get('execution_quality_status')!r}"
)
for idx, row in enumerate(quality_rows):
split_count = row.get("split_count")
if split_count is not None and not isinstance(split_count, int):
errors.append(f"execution_quality_json[{idx}].split_count must be integer or null")
def validate_alpha_feedback_loop(harness: dict[str, Any], errors: list[str]) -> None:
payload = parse_jsonish(harness.get("alpha_feedback_json"))
if not isinstance(payload, dict):
errors.append("alpha_feedback_json: must be an object")
return
if payload.get("formula_id") != "ALPHA_FEEDBACK_LOOP_V1":
errors.append(f"alpha_feedback_json.formula_id must be ALPHA_FEEDBACK_LOOP_V1, found={payload.get('formula_id')!r}")
if not isinstance(payload.get("cases_analyzed"), int) or payload["cases_analyzed"] < 0:
errors.append("alpha_feedback_json.cases_analyzed must be a non-negative integer")
if not isinstance(payload.get("grade_count"), int) or payload["grade_count"] < 0:
errors.append("alpha_feedback_json.grade_count must be a non-negative integer")
if payload.get("status") not in {"ANALYZED", "DATA_MISSING", "DATA_INSUFFICIENT"}:
errors.append(f"alpha_feedback_json.status invalid: {payload.get('status')!r}")
if payload.get("recommended_filter_adjustments") is not None and not isinstance(payload.get("recommended_filter_adjustments"), list):
errors.append("alpha_feedback_json.recommended_filter_adjustments must be a list")
if payload.get("grade_summary") is not None and not isinstance(payload.get("grade_summary"), list):
errors.append("alpha_feedback_json.grade_summary must be a list")
if payload.get("status") == "ANALYZED" and payload.get("cases_analyzed", 0) < 10:
errors.append("alpha_feedback_json: ANALYZED requires cases_analyzed >= 10")
# ── [2026-05-20_HARNESS_V5] 신규 검증 함수 ──────────────────────────────────
def validate_breakout_quality_gate(harness: dict[str, Any], errors: list[str]) -> None:
"""H6: BREAKOUT_QUALITY_GATE_V2 — 뒷박 차단 게이트 검증."""
if "breakout_quality_gate_json" not in harness:
errors.append("missing V5 key: breakout_quality_gate_json")
return
rows = as_rows(harness, "breakout_quality_gate_json", errors)
valid_gates = {"PILOT_ALLOWED", "WATCH_COOLING_OFF", "BLOCKED_LATE_CHASE"}
blocked_tickers: set[str] = set()
for idx, row in enumerate(rows):
gate = row.get("breakout_quality_gate")
score = row.get("breakout_quality_score")
if gate not in valid_gates:
errors.append(f"breakout_quality_gate_json[{idx}].breakout_quality_gate invalid: {gate!r}")
if score is not None and not (0 <= float(score) <= 100):
errors.append(f"breakout_quality_gate_json[{idx}].breakout_quality_score must be 0-100")
if gate == "BLOCKED_LATE_CHASE":
blocked_tickers.add(str(row.get("ticker") or ""))
orders = parse_jsonish(harness.get("order_blueprint_json"))
if isinstance(orders, list):
for idx, order in enumerate(orders):
if not isinstance(order, dict):
continue
if str(order.get("ticker")) in blocked_tickers and str(order.get("order_type")) in BUY_ACTIONS:
errors.append(
f"order_blueprint_json[{idx}]: BUY exists for BLOCKED_LATE_CHASE ticker (QEH009)"
)
def validate_anti_whipsaw_gate(harness: dict[str, Any], errors: list[str]) -> None:
"""H7: ANTI_WHIPSAW_HOLD_GATE_V1 — 가짜 매도 차단 게이트 검증."""
if "anti_whipsaw_gate_json" not in harness:
errors.append("missing V5 key: anti_whipsaw_gate_json")
return
rows = as_rows(harness, "anti_whipsaw_gate_json", errors)
valid_gates = {
"WHIPSAW_SUSPECTED",
"INCONCLUSIVE",
"CONFIRMED_SELL",
"WHIPSAW_CONFIRMED",
"WHIPSAW_WEAKENING",
"WHIPSAW_AUTO_RELEASED",
}
whipsaw_tickers: set[str] = set()
for idx, row in enumerate(rows):
gate = row.get("anti_whipsaw_gate")
score = row.get("anti_whipsaw_score")
hold_days = row.get("anti_whipsaw_hold_days")
if gate not in valid_gates:
errors.append(f"anti_whipsaw_gate_json[{idx}].anti_whipsaw_gate invalid: {gate!r}")
if score is not None and not (-50 <= float(score) <= 100):
errors.append(f"anti_whipsaw_gate_json[{idx}].anti_whipsaw_score must be in [-50,100]")
if gate == "WHIPSAW_SUSPECTED" and hold_days != 1:
errors.append(f"anti_whipsaw_gate_json[{idx}]: WHIPSAW_SUSPECTED must have hold_days=1")
if gate == "WHIPSAW_SUSPECTED":
whipsaw_tickers.add(str(row.get("ticker") or ""))
orders = parse_jsonish(harness.get("order_blueprint_json"))
SELL_ACTIONS = {"SELL", "TRIM", "EXIT_100", "EXIT_FULL"}
if isinstance(orders, list):
for idx, order in enumerate(orders):
if not isinstance(order, dict):
continue
ticker = str(order.get("ticker") or "")
order_type = str(order.get("order_type") or "")
qty = order.get("quantity")
if (ticker in whipsaw_tickers and order_type in SELL_ACTIONS
and str(order.get("validation_status")) == "PASS"):
errors.append(
f"order_blueprint_json[{idx}]: full SELL emitted for WHIPSAW_SUSPECTED ticker (QEH010)"
)
reentry_rows = parse_jsonish(harness.get("anti_whipsaw_reentry_json"))
if isinstance(reentry_rows, list):
for idx, row in enumerate(reentry_rows):
if not isinstance(row, dict):
continue
tier = row.get("sell_tier")
if tier not in {1, 2}:
errors.append(
f"anti_whipsaw_reentry_json[{idx}]: sell_tier must be 1 or 2, found={tier!r} (QEH010-TIER)"
)
signal = row.get("reentry_signal")
if signal not in {"REENTRY_CANDIDATE"}:
errors.append(
f"anti_whipsaw_reentry_json[{idx}].reentry_signal invalid: {signal!r}"
)
def validate_smart_cash_raise_v2(harness: dict[str, Any], errors: list[str]) -> None:
"""H8: SMART_CASH_RAISE_V2 — 4경로 현금확보 라우터 검증."""
if "smart_cash_raise_json" not in harness:
errors.append("missing V5 key: smart_cash_raise_json")
return
rows = as_rows(harness, "smart_cash_raise_json", errors)
valid_routes = {"ROUTE_A", "ROUTE_B", "ROUTE_C", "ROUTE_D", "NO_ACTION"}
portfolio_route = str(harness.get("smart_cash_raise_route") or "NO_ACTION")
if portfolio_route not in valid_routes:
errors.append(f"smart_cash_raise_route invalid: {portfolio_route!r}")
for idx, row in enumerate(rows):
route = row.get("smart_cash_raise_route")
if route not in valid_routes:
errors.append(f"smart_cash_raise_json[{idx}].smart_cash_raise_route invalid: {route!r}")
rebound_wait = row.get("rebound_wait_pct")
if route == "ROUTE_B" and rebound_wait != 50:
errors.append(f"smart_cash_raise_json[{idx}]: ROUTE_B must have rebound_wait_pct=50")
if route == "ROUTE_D":
rationale = str(row.get("rationale") or "")
emergency = row.get("emergency_full_sell") is True
stop_gate = str(row.get("stop_breach_gate") or "")
if not emergency and stop_gate != "BREACH" and (
"emergency" not in rationale.lower() and "breach" not in rationale.lower()
):
errors.append(
f"smart_cash_raise_json[{idx}]: ROUTE_D requires emergency or breach rationale (QEH011)"
)
def validate_cla_harness(harness: dict[str, Any], errors: list[str]) -> None:
"""CLA_HARNESS_V1: CLA 레짐 위성 실패 게이트·RAG·RS 판정 검증."""
# ── [Section 13-1] market_regime_state enum 검증 ─────────────────────────
regime_state = harness.get("market_regime_state")
if regime_state is not None and regime_state not in VALID_MARKET_REGIME_STATES:
errors.append(
f"market_regime_state invalid: {regime_state!r} "
f"(expected one of {sorted(VALID_MARKET_REGIME_STATES)})"
)
# ── [Section 13-3] semiconductor_cluster_json cluster_state enum 검증 ────
semi_json = parse_jsonish(harness.get("semiconductor_cluster_json"))
cluster_state: str | None = None
if isinstance(semi_json, dict):
cluster_state = str(semi_json.get("cluster_state") or "")
if cluster_state not in {"CLUSTER_HOLD_ONLY", "CLUSTER_OPEN", "CLUSTER_BLOCK", ""}:
errors.append(
f"semiconductor_cluster_json.cluster_state invalid: {cluster_state!r} "
"(expected CLUSTER_HOLD_ONLY | CLUSTER_OPEN | CLUSTER_BLOCK)"
)
# ── [Section 13-2] CLA 레짐 시 코어 종목 SELL 차단 (REGIME_CLA-1) ────────
if cluster_state == "CLUSTER_HOLD_ONLY":
decisions = parse_jsonish(harness.get("decisions_json"))
if isinstance(decisions, list):
for idx, dec in enumerate(decisions):
if not isinstance(dec, dict):
continue
ticker = str(dec.get("ticker") or "")
final_action = str(dec.get("final_action") or "").upper()
if ticker in SEMICONDUCTOR_CORE_TICKERS and final_action == "SELL":
errors.append(
f"decisions_json[{idx}] ticker={ticker}: SELL emitted for core "
"semiconductor in CLA (CLUSTER_HOLD_ONLY) regime — REGIME_CLA-1 violation"
)
# ── [Section 13-4/5] buy_permission_json per-row rs_verdict/composite_verdict enum ─
permissions = as_rows(harness, "buy_permission_json", []) # 별도 errors 수집 불필요
for idx, bp in enumerate(permissions):
rv = bp.get("rs_verdict")
if rv is not None and rv not in VALID_RS_VERDICTS:
errors.append(
f"buy_permission_json[{idx}].rs_verdict invalid: {rv!r} "
f"(expected one of {sorted(VALID_RS_VERDICTS)})"
)
cv = bp.get("composite_verdict")
if cv is not None and cv not in VALID_COMPOSITE_VERDICTS:
errors.append(
f"buy_permission_json[{idx}].composite_verdict invalid: {cv!r} "
f"(expected one of {sorted(VALID_COMPOSITE_VERDICTS)})"
)
# SFG-1: satellite_failure_gate_json 존재 및 sfg_v1 유효값 확인
if "satellite_failure_gate_json" not in harness:
errors.append("missing CLA key: satellite_failure_gate_json")
else:
sfg = parse_jsonish(harness.get("satellite_failure_gate_json"))
if not isinstance(sfg, dict):
errors.append("satellite_failure_gate_json: must be an object")
else:
sfg_v1 = sfg.get("sfg_v1")
if sfg_v1 not in {"TRIGGERED", "CLEAR"}:
errors.append(f"satellite_failure_gate_json.sfg_v1 invalid: {sfg_v1!r} (expected TRIGGERED|CLEAR)")
# SFG-2: TRIGGERED이면 위성 ALLOW_* buy_permission 없어야 함
if sfg_v1 == "TRIGGERED":
permissions = as_rows(harness, "buy_permission_json", errors)
for idx, bp in enumerate(permissions):
state = str(bp.get("buy_permission_state") or "")
pos_type = str(bp.get("position_type") or bp.get("cluster_label") or "")
is_satellite = pos_type.lower() in {"satellite", "위성"} or (
pos_type == "" and bp.get("core_flag") is False
)
if is_satellite and state.startswith("ALLOW_"):
errors.append(
f"buy_permission_json[{idx}]: satellite ALLOW_* ({state!r}) emitted while sfg_v1=TRIGGERED (SFG-2)"
)
# RAG-1: buy_permission_json RAG 필드 일관성
permissions = as_rows(harness, "buy_permission_json", errors)
for idx, bp in enumerate(permissions):
rag = bp.get("rag_v1")
if rag is not None and rag not in {"PASS", "FAIL", "EXEMPT"}:
errors.append(f"buy_permission_json[{idx}].rag_v1 invalid: {rag!r}")
# RAG-2: FAIL인데 ALLOW_* 상태면 오류
if rag == "FAIL" and str(bp.get("buy_permission_state") or "").startswith("ALLOW_"):
errors.append(
f"buy_permission_json[{idx}]: rag_v1=FAIL but buy_permission_state=ALLOW_* (RAG-2)"
)
# RS-1: decisions_json 내 rs_verdict 존재 여부 — data_feed 시트 컬럼으로 관리되므로 경고만 출력
decisions = parse_jsonish(harness.get("decisions_json"))
if isinstance(decisions, list):
missing_rs = sum(
1 for d in decisions
if isinstance(d, dict) and "rs_verdict" not in d and "composite_verdict" not in d
)
if missing_rs == len(decisions) and len(decisions) > 0:
# 전체 누락 시에만 경고 (data_feed 시트가 아직 갱신되지 않은 경우)
print(f"[WARN] RS-1: decisions_json {missing_rs}/{len(decisions)} rows lack rs_verdict (GAS re-run needed)")
def validate_determinism(harness: dict[str, Any], errors: list[str]) -> None:
"""결정론 검증: 하네스 락 필드와 JSON 출력 일관성 확인."""
lock_json_pairs = [
("breakout_quality_gate_lock", "breakout_quality_gate_json"),
("anti_whipsaw_gate_lock", "anti_whipsaw_gate_json"),
("alpha_lead_lock", "alpha_lead_json"),
("distribution_lock", "distribution_risk_json"),
("profit_preservation_lock", "profit_preservation_json"),
("smart_cash_raise_lock", "cash_raise_plan_json"),
("execution_quality_lock", "execution_quality_json"),
]
for lock_key, json_key in lock_json_pairs:
lock_val = harness.get(lock_key)
if str(lock_val).lower() == "true":
json_val = parse_jsonish(harness.get(json_key))
if json_val is None:
errors.append(f"determinism: {lock_key}=true but {json_key} is missing")
elif isinstance(json_val, list) and len(json_val) == 0:
errors.append(f"determinism: {lock_key}=true but {json_key} is empty list")
decision_lock = harness.get("decision_lock")
if str(decision_lock).lower() == "true":
decisions = parse_jsonish(harness.get("decisions_json"))
trace = parse_jsonish(harness.get("decision_trace_json"))
if isinstance(decisions, list) and isinstance(trace, list):
final_map: dict[str, set] = {}
for d in decisions:
if isinstance(d, dict) and d.get("ticker"):
final_map.setdefault(str(d["ticker"]), set()).add(d.get("final_action"))
for idx, t in enumerate(trace):
if not isinstance(t, dict):
continue
ticker = str(t.get("ticker") or "")
selected = t.get("selected_action")
allowed = final_map.get(ticker)
if selected and allowed and selected not in allowed:
errors.append(
f"determinism: decision_trace[{idx}].selected_action={selected!r} not in decisions_json.final_action={sorted(allowed)!r}"
)
def validate_brt_harness(harness: dict[str, Any], errors: list[str]) -> None:
brt = parse_jsonish(harness.get("benchmark_relative_timeseries_json"))
if not isinstance(brt, list):
errors.append("missing/invalid BRT key: benchmark_relative_timeseries_json")
else:
for idx, row in enumerate(brt):
if not isinstance(row, dict):
errors.append(f"benchmark_relative_timeseries_json[{idx}]: must be an object")
continue
verdict = row.get("brt_verdict")
if verdict not in VALID_BRT_VERDICTS:
errors.append(f"benchmark_relative_timeseries_json[{idx}].brt_verdict invalid: {verdict!r}")
index_rows = parse_jsonish(harness.get("index_relative_health_json"))
if not isinstance(index_rows, list):
errors.append("missing/invalid BRT key: index_relative_health_json")
else:
for idx, row in enumerate(index_rows):
if not isinstance(row, dict):
errors.append(f"index_relative_health_json[{idx}]: must be an object")
continue
state = row.get("relative_health_state")
if state not in {"HEALTHY", "OVER_EXTENDED", "UNDERPERFORMING", "DECOUPLED", "INSUFFICIENT_DATA"}:
errors.append(f"index_relative_health_json[{idx}].relative_health_state invalid: {state!r}")
saqg = parse_jsonish(harness.get("saqg_json"))
if not isinstance(saqg, list):
errors.append("missing/invalid BRT key: saqg_json")
else:
for idx, row in enumerate(saqg):
if not isinstance(row, dict):
errors.append(f"saqg_json[{idx}]: must be an object")
continue
state = row.get("saqg_v1")
if state not in VALID_SAQG_STATES:
errors.append(f"saqg_json[{idx}].saqg_v1 invalid: {state!r}")
sapg = parse_jsonish(harness.get("sapg_json"))
if not isinstance(sapg, dict):
errors.append("missing/invalid BRT key: sapg_json")
elif sapg.get("sapg_status") not in VALID_SAPG_STATUSES:
errors.append(f"sapg_json.sapg_status invalid: {sapg.get('sapg_status')!r}")
permissions = parse_jsonish(harness.get("buy_permission_json"))
if isinstance(permissions, list):
for idx, bp in enumerate(permissions):
if not isinstance(bp, dict):
continue
if bp.get("saqg_v1") in {"EXCLUDED"} and str(bp.get("buy_permission_state") or "").startswith("ALLOW_"):
errors.append(f"buy_permission_json[{idx}]: SAQG EXCLUDED but buy_permission_state=ALLOW_*")
if bp.get("saqg_v1") == "WATCHLIST_ONLY" and str(bp.get("buy_permission_state") or "").startswith("ALLOW_"):
errors.append(f"buy_permission_json[{idx}]: SAQG WATCHLIST_ONLY but buy_permission_state=ALLOW_*")
def main() -> int:
args = sys.argv[1:]
if not args or len(args) > 3:
print(__doc__)
return 1
json_path = Path(args[0])
strict = "--strict" in args
check_mode: str | None = None
if "--check" in args:
idx = args.index("--check")
if idx + 1 >= len(args):
print("--check requires a mode: breakout_quality_gate|anti_whipsaw_gate|smart_cash_raise_v2|determinism|cla_harness|brt_harness")
return 1
check_mode = args[idx + 1]
if check_mode not in VALID_CHECK_MODES:
print(f"Unknown --check mode: {check_mode!r}. Valid: {', '.join(sorted(VALID_CHECK_MODES))}")
return 1
harness = load_harness(json_path)
errors: list[str] = []
if check_mode == "breakout_quality_gate":
validate_breakout_quality_gate(harness, errors)
label = "BREAKOUT_QUALITY_GATE"
elif check_mode == "anti_whipsaw_gate":
validate_anti_whipsaw_gate(harness, errors)
label = "ANTI_WHIPSAW_GATE"
elif check_mode == "smart_cash_raise_v2":
validate_smart_cash_raise_v2(harness, errors)
label = "SMART_CASH_RAISE_V2"
elif check_mode == "determinism":
validate_determinism(harness, errors)
label = "DETERMINISM"
elif check_mode == "cla_harness":
validate_cla_harness(harness, errors)
label = "CLA_HARNESS"
elif check_mode == "brt_harness":
validate_brt_harness(harness, errors)
label = "BRT_HARNESS"
elif check_mode == "factor_cap":
# P1-1 (v11): PA1 단일팩터 50% 캡 검증
import json as _json
pa_path = json_path.parent / "predictive_alpha_engine_v2.json"
if not pa_path.exists():
pa_path = json_path.parent.parent / "Temp" / "predictive_alpha_engine_v2.json"
label = "FACTOR_CAP"
if pa_path.exists():
pa = _json.loads(pa_path.read_text(encoding="utf-8"))
audit = pa.get("factor_cap_audit", {})
max_share = float(audit.get("single_factor_max_share_pct") or 0)
pac_std = float(audit.get("pac_stddev") or 0)
if max_share > 50.0:
errors.append(f"single_factor_max_share_pct={max_share} > 50%")
if pac_std < 5.0:
errors.append(f"pac_stddev={pac_std} < 5.0")
else:
errors.append(f"predictive_alpha_engine_v2.json not found at {pa_path}")
else:
# Legacy / --strict mode
apex_present = any(key in harness for key in REQUIRED_APEX_KEYS)
if strict:
validate_required_keys(harness, errors)
# Also validate V5 keys in strict mode
for key in REQUIRED_V5_KEYS:
if key not in harness:
errors.append(f"missing V5 key: {key}")
validate_alpha_feedback_loop(harness, errors)
if errors:
print("ALPHA EXECUTION HARNESS FAIL")
for err in errors:
print(f"- {err}")
return 1
elif not apex_present:
print("ALPHA EXECUTION HARNESS SKIPPED: APEX fields not present (use --strict after GAS Harness V5 export)")
return 0
validate_buy_blocks(harness, errors)
validate_distribution_blocks(harness, errors)
validate_cash_raise(harness, errors)
validate_execution_quality(harness, errors)
validate_alpha_feedback_loop(harness, errors)
# V5 checks when keys present
if "breakout_quality_gate_json" in harness:
validate_breakout_quality_gate(harness, errors)
if "anti_whipsaw_gate_json" in harness:
validate_anti_whipsaw_gate(harness, errors)
if "smart_cash_raise_json" in harness:
validate_smart_cash_raise_v2(harness, errors)
if "satellite_failure_gate_json" in harness:
validate_cla_harness(harness, errors)
if "benchmark_relative_timeseries_json" in harness:
validate_brt_harness(harness, errors)
label = "ALPHA EXECUTION HARNESS"
if errors:
print(f"{label} FAIL")
for err in errors:
print(f"- {err}")
return 1
print(f"{label} OK")
return 0
if __name__ == "__main__":
raise SystemExit(main())