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>
This commit is contained in:
2026-06-13 13:20:14 +09:00
commit ee3e799de1
1474 changed files with 176087 additions and 0 deletions
@@ -0,0 +1,120 @@
"""generate_models_from_schema.py — schema model generation
Mirrors schemas/generated/*.schema.json into src/quant_engine/models/generated
as lightweight Python model descriptors.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[2]
SCHEMA_DIR = ROOT / "schemas" / "generated"
OUT_DIR = ROOT / "src" / "quant_engine" / "models" / "generated"
def load_json(path: Path) -> dict[str, Any]:
try:
obj = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return obj if isinstance(obj, dict) else {}
def to_module_name(path: Path) -> str:
return path.stem.replace(".", "_").lower()
def render_module(schema_path: Path, schema: dict[str, Any]) -> str:
title = str(schema.get("title") or schema_path.stem)
schema_id = str(schema.get("$id") or f"schema://{title}")
props = schema.get("properties") if isinstance(schema.get("properties"), dict) else {}
required = schema.get("required") if isinstance(schema.get("required"), list) else []
prop_names = list(props.keys())
return (
'"""Auto-generated schema model descriptor."""\n'
"from __future__ import annotations\n\n"
"from dataclasses import dataclass\n"
"import json\n"
"from pathlib import Path\n"
"from typing import Any\n\n"
f"SCHEMA_TITLE = {title!r}\n"
f"SCHEMA_ID = {schema_id!r}\n"
f"SCHEMA_PATH = {str(schema_path.relative_to(ROOT)).replace('\\', '/')!r}\n"
f"SCHEMA_PROPERTIES = {prop_names!r}\n"
f"SCHEMA_REQUIRED = {required!r}\n\n"
"@dataclass(frozen=True)\n"
"class SchemaModel:\n"
" title: str\n"
" schema_id: str\n"
" path: str\n"
" properties: list[str]\n"
" required: list[str]\n\n"
"def load_schema() -> dict[str, Any]:\n"
" return json.loads(Path(__file__).with_suffix('.schema.json').read_text(encoding='utf-8'))\n\n"
"def describe() -> SchemaModel:\n"
" return SchemaModel(\n"
" title=SCHEMA_TITLE,\n"
" schema_id=SCHEMA_ID,\n"
" path=SCHEMA_PATH,\n"
" properties=list(SCHEMA_PROPERTIES),\n"
" required=list(SCHEMA_REQUIRED),\n"
" )\n"
)
def write_text(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--schemas", default=str(SCHEMA_DIR))
parser.add_argument("--out", default=str(OUT_DIR))
parser.add_argument("--report", default=str(ROOT / "Temp" / "schema_model_generation_v1.json"))
args = parser.parse_args()
schema_dir = Path(args.schemas)
out_dir = Path(args.out)
schema_files = sorted(schema_dir.glob("*.schema.json"))
modules = []
for schema_file in schema_files:
schema = load_json(schema_file)
module_name = to_module_name(schema_file)
modules.append(module_name)
write_text(out_dir / f"{module_name}.py", render_module(schema_file, schema))
write_text(out_dir / f"{module_name}.schema.json", json.dumps(schema, ensure_ascii=False, indent=2) + "\n")
write_text(out_dir / "__init__.py", '"""Auto-generated schema model package."""\n')
write_text(
out_dir.parent / "__init__.py",
'"""Auto-generated quant_engine.models package."""\n',
)
write_text(
out_dir.parent.parent / "__init__.py",
'"""Canonical quant_engine package."""\n',
)
write_text(
out_dir.parent.parent.parent / "__init__.py",
'"""Canonical src package."""\n',
)
report = {
"status": "OK",
"schema_count": len(schema_files),
"generated_module_count": len(modules),
"package_root": "src/quant_engine/models/generated",
"report_path": str(Path(args.report).relative_to(ROOT)),
}
write_text(Path(args.report), json.dumps(report, ensure_ascii=False, indent=2) + "\n")
print(json.dumps(report, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())