Files
QuantEngineByItz/tools/validate_strategy_tests_contract.py
T
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

315 lines
13 KiB
Python

from __future__ import annotations
import sys
from pathlib import Path
from typing import Any
import yaml
ROOT = Path(__file__).resolve().parents[1]
TESTS_PATH = ROOT / "tests" / "strategy_tests.yaml"
REQUIRED_CASES = {
"T023_OVERSOLD_REBOUND_50_50_SPLIT": {
"triggered_rule": "spec/13b_harness_formulas.yaml:K2_STAGED_REBOUND_SELL_V1",
"expected_keys": ["immediate_sell_qty", "rebound_wait_qty", "k2_emergency"],
},
"T024_OVERSOLD_REBOUND_EMERGENCY_FULL_EXIT": {
"triggered_rule": "spec/13b_harness_formulas.yaml:K2_STAGED_REBOUND_SELL_V1",
"expected_keys": ["immediate_sell_qty", "rebound_wait_qty", "k2_emergency"],
},
"T025_PROFIT_PROTECT_TRIM_LIMIT_POLICY": {
"triggered_rule": "spec/13b_harness_formulas.yaml:LIMIT_PRICE_POLICY_V1",
"expected_keys": ["sell_limit_price"],
},
"T026_WAIT_PILOT_SETUP_NO_POSITION": {
"triggered_rule": "spec/13b_harness_formulas.yaml:STAGED_ENTRY_TRANCHE_V1",
"expected_keys": ["tranche_phase", "current_tranche_allowed_pct", "next_tranche_condition"],
},
"T027_PA1_EXIT_SIGNAL_BLOCKS_BUY_PERMISSION": {
"triggered_rule": "spec/13b_harness_formulas.yaml:PREDICTIVE_ALPHA_ENGINE_V1",
"expected_keys": ["pa1_synthesis_verdict", "buy_permission_state"],
},
"T028_PA2_BLOCKED_LATE_ENTRY_FORCES_BLOCK": {
"triggered_rule": "spec/13b_harness_formulas.yaml:ANTI_LATE_ENTRY_GATE_V2",
"expected_keys": ["anti_late_entry_grade", "buy_permission_state"],
},
"T029_PA3_PORTFOLIO_ALPHA_CONFIDENCE_NEGATIVE_BLOCK": {
"triggered_rule": "spec/13b_harness_formulas.yaml:CASH_PRESERVATION_SELL_ENGINE_V2",
"expected_keys": ["buy_permission_state", "portfolio_alpha_confidence"],
},
"T030_PA5_CONSISTENCY_VALIDATOR_PASSES": {
"triggered_rule": "spec/13b_harness_formulas.yaml:CONSISTENCY_VALIDATOR_V2",
"expected_keys": ["consistency_score_min", "cv_verdict"],
},
"T031_WATCHLIST_ONLY_DOWNGRADES_PILOT": {
"triggered_rule": "spec/13b_harness_formulas.yaml:BUY_PERMISSION_MATRIX_V1",
"expected_keys": ["buy_permission_state"],
},
"T032_DISTRIBUTION_EXIT_STYLE": {
"triggered_rule": "spec/13b_harness_formulas.yaml:SMART_CASH_RAISE_PLAN_V1",
"expected_keys": ["execution_style"],
},
"T033_PROFIT_LOCK_20_CAP_35": {
"triggered_rule": "spec/13b_harness_formulas.yaml:SELL_VALUE_PRESERVATION_GATE_V1",
"expected_keys": ["immediate_sell_qty_max"],
},
"T034_SAQG_EXCLUDED_FORCES_BLOCKED": {
"triggered_rule": "spec/13b_harness_formulas.yaml:SATELLITE_ALPHA_QUALITY_GATE_V1",
"expected_keys": ["buy_permission_state", "blocked_reason"],
},
"T035_FOMC_7D_GATE_ACTIVE": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["fomc_position_size_gate", "fomc_size_limit"],
},
"T036_PA1_ANTITHESIS_BOOST_APPLIES_UNDER_60": {
"triggered_rule": "spec/13b_harness_formulas.yaml:PREDICTIVE_ALPHA_ENGINE_V1",
"expected_keys": ["antithesis_score_original", "antithesis_weight_boost"],
},
"T037_WATCH_BREAKOUT_PROMOTE_TO_WATCH": {
"triggered_rule": "spec/13b_harness_formulas.yaml:WATCH_BREAKOUT_REALTIME_GATE_V1",
"expected_keys": ["breakout_promotion_recommendation", "breakout_signal"],
},
"T038_ANTI_WHIPSAW_REENTRY_MARKS_CANDIDATE": {
"triggered_rule": "spec/13b_harness_formulas.yaml:ANTI_WHIPSAW_REENTRY_GATE_V1",
"expected_keys": ["reentry_candidate"],
},
"T039_FOMC_8D_GATE_INACTIVE": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["fomc_position_size_gate", "fomc_size_limit"],
"expected_values": {
"fomc_position_size_gate": "INACTIVE",
"fomc_size_limit": 1.0,
},
},
"T040_PA1_ANTITHESIS_NO_BOOST_AT_60": {
"triggered_rule": "spec/13b_harness_formulas.yaml:PREDICTIVE_ALPHA_ENGINE_V1",
"expected_keys": ["antithesis_weight_boost"],
"expected_values": {
"antithesis_weight_boost": 0,
},
},
"T041_PA1_ANTITHESIS_BOOST_AT_59_9": {
"triggered_rule": "spec/13b_harness_formulas.yaml:PREDICTIVE_ALPHA_ENGINE_V1",
"expected_keys": ["antithesis_weight_boost"],
"expected_values": {
"antithesis_weight_boost": 10,
},
},
"T042_PREDICTION_ACCURACY_ONE_PERCENT_PARSE": {
"triggered_rule": "spec/13b_harness_formulas.yaml:PREDICTIVE_ALPHA_ENGINE_V1",
"expected_keys": ["prediction_accuracy_rate", "antithesis_weight_boost"],
"expected_values": {
"prediction_accuracy_rate": 100,
"antithesis_weight_boost": 0,
},
},
"T043_PREDICTION_ACCURACY_POINT_NINE_NINE_PARSE": {
"triggered_rule": "spec/13b_harness_formulas.yaml:PREDICTIVE_ALPHA_ENGINE_V1",
"expected_keys": ["prediction_accuracy_rate", "antithesis_weight_boost"],
"expected_values": {
"prediction_accuracy_rate": 99,
"antithesis_weight_boost": 10,
},
},
"T044_MACRO_ELEVATED_AT_40": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["macro_risk_score", "macro_risk_regime", "heat_gate_adj"],
"expected_values": {
"macro_risk_score": 40,
"macro_risk_regime": "MACRO_ELEVATED",
"heat_gate_adj": -1,
},
},
"T045_MACRO_CRITICAL_AT_60": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["macro_risk_score", "macro_risk_regime", "heat_gate_adj"],
"expected_values": {
"macro_risk_score": 60,
"macro_risk_regime": "MACRO_CRITICAL",
"heat_gate_adj": -3,
},
},
"T046_MACRO_USD_KRW_WEAK_AT_1481": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["macro_risk_score", "macro_risk_regime", "heat_gate_adj"],
"expected_values": {
"macro_risk_score": 15,
"macro_risk_regime": "MACRO_FAVORABLE",
"heat_gate_adj": 1,
},
},
"T047_MACRO_VIX_ELEVATED_AT_21": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["macro_risk_score", "macro_risk_regime", "heat_gate_adj"],
"expected_values": {
"macro_risk_score": 10,
"macro_risk_regime": "MACRO_FAVORABLE",
"heat_gate_adj": 1,
},
},
"T048_MACRO_FOREIGN_SELL_HIGH_AT_5": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["macro_risk_score", "macro_risk_regime", "heat_gate_adj"],
"expected_values": {
"macro_risk_score": 15,
"macro_risk_regime": "MACRO_FAVORABLE",
"heat_gate_adj": 1,
},
},
"T049_MACRO_FOREIGN_SELL_MEGA_AT_10": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["macro_risk_score", "macro_risk_regime", "heat_gate_adj"],
"expected_values": {
"macro_risk_score": 20,
"macro_risk_regime": "MACRO_NEUTRAL",
"heat_gate_adj": 0,
},
},
"T050_WATCH_BREAKOUT_PROMOTION_ELIGIBLE": {
"triggered_rule": "spec/13b_harness_formulas.yaml:WATCH_BREAKOUT_REALTIME_GATE_V1",
"expected_keys": ["breakout_signal", "promotion_eligible", "promotion_block_reason"],
"expected_values": {
"breakout_signal": "WATCH_BREAKOUT_DETECTED",
"promotion_eligible": True,
"promotion_block_reason": None,
},
},
"T051_ANTI_WHIPSAW_REENTRY_GRADE_A": {
"triggered_rule": "spec/13b_harness_formulas.yaml:ANTI_WHIPSAW_REENTRY_GATE_V1",
"expected_keys": ["reentry_grade", "reentry_signal"],
"expected_values": {
"reentry_grade": "A",
"reentry_signal": "REENTRY_CANDIDATE",
},
},
"T052_CONSISTENCY_WARNING_BOUNDARY": {
"triggered_rule": "spec/13b_harness_formulas.yaml:CONSISTENCY_VALIDATOR_V2",
"expected_keys": ["consistency_score", "cv_verdict"],
"expected_values": {
"consistency_score": 92,
"cv_verdict": "WARNING",
},
},
"T053_CONSISTENCY_BLOCK_BOUNDARY": {
"triggered_rule": "spec/13b_harness_formulas.yaml:CONSISTENCY_VALIDATOR_V2",
"expected_keys": ["consistency_score", "cv_verdict"],
"expected_values": {
"consistency_score": 83,
"cv_verdict": "BLOCK",
},
},
"T054_MACRO_FOMC_NEAR_AT_5": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["macro_risk_score", "macro_risk_regime", "heat_gate_adj"],
"expected_event_tags": ["FOMC_WEEK"],
"expected_values": {
"macro_risk_score": 15,
"macro_risk_regime": "MACRO_FAVORABLE",
"heat_gate_adj": 1,
},
},
"T055_MACRO_MEGA_SELL_ALERT_TRIGGER": {
"triggered_rule": "spec/13b_harness_formulas.yaml:MACRO_EVENT_SYNCHRONIZER_V1",
"expected_keys": ["mega_sell_alert", "buy_gate_block_until"],
"expected_event_tags": ["MEGA_SELL_ALERT"],
"expected_values": {
"mega_sell_alert": True,
"buy_gate_block_until": "NON_EMPTY",
},
},
"T056_WATCH_BREAKOUT_BLOCKED_BY_LATE_ENTRY": {
"triggered_rule": "spec/13b_harness_formulas.yaml:WATCH_BREAKOUT_REALTIME_GATE_V1",
"expected_keys": ["breakout_signal", "promotion_eligible", "promotion_block_reason"],
"expected_values": {
"breakout_signal": "WATCH_BREAKOUT_DETECTED",
"promotion_eligible": False,
"promotion_block_reason": "ANTI_LATE_ENTRY_GRADE_F",
},
},
"T057_ANTI_WHIPSAW_REENTRY_GRADE_B": {
"triggered_rule": "spec/13b_harness_formulas.yaml:ANTI_WHIPSAW_REENTRY_GATE_V1",
"expected_keys": ["reentry_grade", "reentry_signal"],
"expected_values": {
"reentry_grade": "B",
"reentry_signal": "REENTRY_CANDIDATE",
},
},
"T058_WATCH_BREAKOUT_LOW_VELOCITY_MONITORED": {
"triggered_rule": "spec/13b_harness_formulas.yaml:WATCH_BREAKOUT_REALTIME_GATE_V1",
"expected_keys": ["breakout_signal", "promotion_eligible", "promotion_block_reason"],
"expected_values": {
"breakout_signal": "WATCH_BREAKOUT_MONITORED",
"promotion_eligible": False,
"promotion_block_reason": "LOW_VELOCITY",
},
},
"T059_ANTI_WHIPSAW_REENTRY_GRADE_C": {
"triggered_rule": "spec/13b_harness_formulas.yaml:ANTI_WHIPSAW_REENTRY_GATE_V1",
"expected_keys": ["reentry_grade", "reentry_signal"],
"expected_values": {
"reentry_grade": "C",
"reentry_signal": "REENTRY_CANDIDATE",
},
},
}
def _ensure_utf8_stdio() -> None:
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
if sys.stderr.encoding and sys.stderr.encoding.lower() not in ("utf-8", "utf8"):
sys.stderr = open(sys.stderr.fileno(), mode="w", encoding="utf-8", buffering=1)
def _load_tests() -> list[dict[str, Any]]:
payload = yaml.safe_load(TESTS_PATH.read_text(encoding="utf-8"))
tests = payload.get("tests") if isinstance(payload, dict) else []
return tests if isinstance(tests, list) else []
def main() -> int:
_ensure_utf8_stdio()
tests = _load_tests()
test_map = {
str(item.get("id")): item
for item in tests
if isinstance(item, dict) and item.get("id")
}
errors: list[str] = []
for case_id, meta in REQUIRED_CASES.items():
item = test_map.get(case_id)
if item is None:
errors.append(f"missing_case:{case_id}")
continue
expected = item.get("expected") or {}
if expected.get("triggered_rule") != meta["triggered_rule"]:
errors.append(f"{case_id}:triggered_rule={expected.get('triggered_rule')}")
for key in meta["expected_keys"]:
if key not in expected:
errors.append(f"{case_id}:missing_expected_key:{key}")
for key, value in (meta.get("expected_values") or {}).items():
if expected.get(key) != value:
errors.append(f"{case_id}:{key}={expected.get(key)}")
expected_event_matrix = expected.get("event_matrix") or []
for tag in meta.get("expected_event_tags") or []:
event_matrix = expected_event_matrix
if not isinstance(event_matrix, list) or not any(
isinstance(row, dict) and row.get("event") == tag for row in event_matrix
):
errors.append(f"{case_id}:missing_event_tag:{tag}")
if errors:
for error in errors:
print(error)
print("STRATEGY_TESTS_CONTRACT_FAIL")
return 1
print("STRATEGY_TESTS_CONTRACT_OK")
return 0
if __name__ == "__main__":
raise SystemExit(main())