Files
QuantEngineByItz/tools/validate_strategy_execution_locks_regression.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

161 lines
7.3 KiB
Python

from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
TMP_JSON = ROOT / "Temp" / "_strategy_execution_locks_regression_input.json"
RESULT_JSON = ROOT / "Temp" / "strategy_execution_locks_regression_result.json"
LADDER_JSON = ROOT / "Temp" / "execution_method_ladder_v1.json"
TEMP_FILES = {
"late": ROOT / "Temp" / "late_chase_attribution_v1.json",
"reb": ROOT / "Temp" / "rebound_sell_efficiency_v1.json",
"di": ROOT / "Temp" / "data_integrity_score_v1.json",
"dv": ROOT / "Temp" / "derivation_validity_score_v1.json",
"de": ROOT / "Temp" / "decision_evidence_score_v1.json",
"oq": ROOT / "Temp" / "outcome_quality_score_v1.json",
}
def _write_json(path: Path, payload: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
def _run_apply() -> dict[str, Any]:
cmd = [sys.executable, str(ROOT / "tools" / "apply_strategy_execution_locks.py"), "--json", str(TMP_JSON)]
proc = subprocess.run(cmd, cwd=str(ROOT), text=True, capture_output=True, encoding="utf-8", errors="replace")
if proc.returncode != 0:
raise RuntimeError(f"apply_strategy_execution_locks failed: {proc.stdout}\n{proc.stderr}")
payload = json.loads(TMP_JSON.read_text(encoding="utf-8"))
h = payload.get("hApex") if isinstance(payload.get("hApex"), dict) else {}
sel = h.get("strategy_execution_locks_v1_json") if isinstance(h.get("strategy_execution_locks_v1_json"), dict) else {}
return sel
def _build_execution_method_ladder() -> dict[str, Any]:
cmd = [
sys.executable,
str(ROOT / "tools" / "build_execution_method_ladder_v1.py"),
"--json",
str(ROOT / "GatherTradingData.json"),
"--out",
str(LADDER_JSON),
]
proc = subprocess.run(cmd, cwd=str(ROOT), text=True, capture_output=True, encoding="utf-8", errors="replace")
if proc.returncode != 0:
raise RuntimeError(f"build_execution_method_ladder_v1 failed: {proc.stdout}\n{proc.stderr}")
payload = json.loads(LADDER_JSON.read_text(encoding="utf-8"))
return payload
def _build_input() -> dict[str, Any]:
rows = [
{"ticker": "AAA001", "order_type": "BUY", "validation_status": "PASS", "quantity": 10, "order_qty": 10, "buy_qty": 10},
{"ticker": "AAA002", "order_type": "SELL", "validation_status": "PASS", "quantity": 10, "order_qty": 10, "sell_qty": 10},
{"ticker": "AAA003", "order_type": "STOP_LOSS", "validation_status": "PASS", "quantity": 8, "order_qty": 8, "sell_qty": 8},
]
return {
"data": {"_harness_context": {"order_blueprint_json": rows}},
"hApex": {"order_blueprint_json": rows},
}
def main() -> int:
backups: dict[str, str | None] = {}
for key, path in TEMP_FILES.items():
backups[key] = path.read_text(encoding="utf-8") if path.exists() else None
try:
_write_json(TMP_JSON, _build_input())
# Case 1: outcome low with sufficient eval -> buy block + sell scale
_write_json(TEMP_FILES["late"], {"formula_id": "LATE_CHASE_ATTRIBUTION_V1", "status": "PASS"})
_write_json(TEMP_FILES["reb"], {"formula_id": "REBOUND_SELL_EFFICIENCY_V1", "metrics": {"rebound_efficiency_score": 70.0}})
_write_json(TEMP_FILES["di"], {"formula_id": "DATA_INTEGRITY_SCORE_V1", "score": 100.0, "gate": "PASS"})
_write_json(TEMP_FILES["dv"], {"formula_id": "DERIVATION_VALIDITY_SCORE_V1", "score": 100.0, "gate": "PASS"})
_write_json(TEMP_FILES["de"], {"formula_id": "DECISION_EVIDENCE_SCORE_V1", "score": 100.0, "gate": "PASS"})
_write_json(
TEMP_FILES["oq"],
{
"formula_id": "OUTCOME_QUALITY_SCORE_V1",
"score": 40.0,
"gate": "CRITICAL_MODE",
"metrics": {"has_sufficient_eval": True},
},
)
sel1 = _run_apply()
if int(sel1.get("buy_block_count") or 0) <= 0:
raise RuntimeError("REGRESSION_FAIL: expected buy_block_count > 0 for low outcome score")
if int(sel1.get("sell_scale_count") or 0) <= 0:
raise RuntimeError("REGRESSION_FAIL: expected sell_scale_count > 0 for low outcome score")
# Case 2: decision evidence gate block -> hard block
_write_json(TEMP_FILES["de"], {"formula_id": "DECISION_EVIDENCE_SCORE_V1", "score": 70.0, "gate": "BLOCK"})
_write_json(TEMP_FILES["oq"], {"formula_id": "OUTCOME_QUALITY_SCORE_V1", "score": 95.0, "gate": "PASS"})
sel2 = _run_apply()
if int(sel2.get("hard_block_count") or 0) <= 0:
raise RuntimeError("REGRESSION_FAIL: expected hard_block_count > 0 for decision evidence block")
# Case 3: insufficient eval should suspend outcome locks
_write_json(
TEMP_FILES["oq"],
{
"formula_id": "OUTCOME_QUALITY_SCORE_V1",
"score": 30.0,
"gate": "INSUFFICIENT_EVAL",
"metrics": {"has_sufficient_eval": False},
},
)
_write_json(TEMP_FILES["de"], {"formula_id": "DECISION_EVIDENCE_SCORE_V1", "score": 100.0, "gate": "PASS"})
sel3 = _run_apply()
if str(sel3.get("outcome_lock_mode") or "") != "SUSPENDED_DUE_TO_INSUFFICIENT_EVAL":
raise RuntimeError("REGRESSION_FAIL: expected suspended outcome lock mode under insufficient eval")
ladder = _build_execution_method_ladder()
if int(ladder.get("market_order_default_count") or 0) != 0:
raise RuntimeError("REGRESSION_FAIL: expected market_order_default_count == 0")
if int(ladder.get("emergency_full_sell_without_flag_count") or 0) != 0:
raise RuntimeError("REGRESSION_FAIL: expected emergency_full_sell_without_flag_count == 0")
result = {
"status": "OK",
"formula_id": "STRATEGY_EXECUTION_LOCKS_REGRESSION_V1",
"case1": sel1,
"case2": sel2,
"case3": sel3,
"execution_method_ladder": ladder,
"assertions": {
"case1_buy_block_count_gt_0": int(sel1.get("buy_block_count") or 0) > 0,
"case1_sell_scale_count_gt_0": int(sel1.get("sell_scale_count") or 0) > 0,
"case2_hard_block_count_gt_0": int(sel2.get("hard_block_count") or 0) > 0,
"case3_outcome_lock_suspended": str(sel3.get("outcome_lock_mode") or "") == "SUSPENDED_DUE_TO_INSUFFICIENT_EVAL",
"ladder_market_order_default_count_eq_0": int(ladder.get("market_order_default_count") or 0) == 0,
"ladder_emergency_flag_missing_count_eq_0": int(ladder.get("emergency_full_sell_without_flag_count") or 0) == 0,
},
}
_write_json(RESULT_JSON, result)
print("STRATEGY_EXEC_LOCKS_REGRESSION_OK")
print(json.dumps(result, ensure_ascii=False))
return 0
finally:
for key, path in TEMP_FILES.items():
if backups[key] is None:
if path.exists():
path.unlink()
else:
path.write_text(backups[key] or "", encoding="utf-8")
if TMP_JSON.exists():
try:
TMP_JSON.unlink()
except FileNotFoundError:
pass
if __name__ == "__main__":
raise SystemExit(main())